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.
Files changed (283) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  15. package/.framework/functions/_shared/logging.ts +562 -224
  16. package/.framework/functions/_shared/middleware.ts +47 -44
  17. package/.framework/functions/admin-data.ts +39 -28
  18. package/.framework/functions/ai-agents.ts +10 -6
  19. package/.framework/functions/api-keys.ts +301 -122
  20. package/.framework/functions/apps.ts +43 -26
  21. package/.framework/functions/cil-ingest.ts +48 -15
  22. package/.framework/functions/debug-auth.ts +14 -8
  23. package/.framework/functions/deliveries.ts +39 -12
  24. package/.framework/functions/embeddings.ts +145 -76
  25. package/.framework/functions/integration-outbound.ts +67 -27
  26. package/.framework/functions/integrations.ts +254 -127
  27. package/.framework/functions/logs.ts +273 -133
  28. package/.framework/functions/observability.ts +277 -147
  29. package/.framework/functions/pipeline-executions.ts +1 -1
  30. package/.framework/functions/pipelines.ts +14 -9
  31. package/.framework/functions/prompt-configs.ts +7 -4
  32. package/.framework/functions/roles.ts +8 -5
  33. package/.framework/functions/security.ts +30 -17
  34. package/.framework/functions/system-cron.ts +240 -127
  35. package/.framework/functions/tests.ts +7 -6
  36. package/.framework/functions/timers.ts +310 -107
  37. package/.framework/functions/triggers.ts +28 -13
  38. package/.framework/functions/types.ts +13 -10
  39. package/.framework/migrations/069_log_stats_window.sql +54 -0
  40. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  41. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  42. package/.framework/migrations/072_slug_autofill.sql +77 -0
  43. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  44. package/.framework/src/apps/admin/index.tsx +1141 -775
  45. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  46. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  47. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  48. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  49. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  50. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  51. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  52. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  53. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  54. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  55. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  56. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  57. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  58. package/.framework/src/components/ui/sheet.tsx +24 -26
  59. package/.framework/src/components/ui/textarea.tsx +74 -48
  60. package/.framework/src/hooks/useEntityList.ts +12 -9
  61. package/.framework/src/hooks/useListSchema.ts +108 -58
  62. package/.framework/src/instructions/README.md +57 -0
  63. package/.framework/src/instructions/ai-agents.md +69 -0
  64. package/.framework/src/instructions/alerts.md +80 -0
  65. package/.framework/src/instructions/api-keys.md +87 -0
  66. package/.framework/src/instructions/apps.md +77 -0
  67. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  68. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  69. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  70. package/.framework/src/instructions/concepts/automation.md +186 -0
  71. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  72. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  73. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  74. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  75. package/.framework/src/instructions/concepts/observability.md +116 -0
  76. package/.framework/src/instructions/concepts/permissions.md +150 -0
  77. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  78. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  79. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  80. package/.framework/src/instructions/deliveries.md +59 -0
  81. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  82. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  83. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  84. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  85. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  86. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  87. package/.framework/src/instructions/integrations.md +96 -0
  88. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  89. package/.framework/src/instructions/invites.md +60 -0
  90. package/.framework/src/instructions/kb.md +80 -0
  91. package/.framework/src/instructions/logs.md +77 -0
  92. package/.framework/src/instructions/observability.md +61 -0
  93. package/.framework/src/instructions/operations/deployment.md +115 -0
  94. package/.framework/src/instructions/operations/migrations.md +125 -0
  95. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  96. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  97. package/.framework/src/instructions/operations/upgrade.md +116 -0
  98. package/.framework/src/instructions/pipeline-executions.md +62 -0
  99. package/.framework/src/instructions/pipelines.md +78 -0
  100. package/.framework/src/instructions/prompts.md +95 -0
  101. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  102. package/.framework/src/instructions/reference/api/auth.md +113 -0
  103. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  104. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  105. package/.framework/src/instructions/reference/cli.md +124 -0
  106. package/.framework/src/instructions/reference/database.md +132 -0
  107. package/.framework/src/instructions/reference/design-schema.md +280 -0
  108. package/.framework/src/instructions/reference/env-vars.md +133 -0
  109. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  110. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  111. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  112. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  113. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  114. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  115. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  116. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  117. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  118. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  119. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  120. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  121. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  122. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  123. package/.framework/src/instructions/reference/functions/index.md +103 -0
  124. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  125. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  126. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  127. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  128. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  129. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  130. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  131. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  132. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  133. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  134. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  135. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  136. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  137. package/.framework/src/instructions/reference/functions/security.md +140 -0
  138. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  139. package/.framework/src/instructions/reference/functions/system.md +106 -0
  140. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  141. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  142. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  143. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  144. package/.framework/src/instructions/reference/functions/types.md +146 -0
  145. package/.framework/src/instructions/reference/seeds.md +126 -0
  146. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  147. package/.framework/src/instructions/reference/spine-config.md +114 -0
  148. package/.framework/src/instructions/roles.md +80 -0
  149. package/.framework/src/instructions/runtime-detail.md +70 -0
  150. package/.framework/src/instructions/runtime-list.md +77 -0
  151. package/.framework/src/instructions/security.md +67 -0
  152. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  153. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  154. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  155. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  156. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  157. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  158. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  159. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  160. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  161. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  162. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  163. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  164. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  165. package/.framework/src/instructions/testing.md +59 -0
  166. package/.framework/src/instructions/timers.md +82 -0
  167. package/.framework/src/instructions/triggers.md +69 -0
  168. package/.framework/src/instructions/types.md +93 -0
  169. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  170. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  171. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  172. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  173. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  174. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  175. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  176. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  177. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  178. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  179. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  180. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  181. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  182. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  183. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  184. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  185. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  186. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  187. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  188. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  189. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  190. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  191. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  192. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  193. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  194. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  195. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  196. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  197. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  198. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  199. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  200. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  201. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  202. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  203. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  204. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  205. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  206. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  207. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  208. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  209. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  210. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  211. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  212. package/.framework/templates/agent/AGENTS.md +150 -0
  213. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  214. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  215. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  216. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  217. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  218. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  219. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  220. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  221. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  222. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  223. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  224. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  225. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  226. package/.framework/templates/project.json +4 -1
  227. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  228. package/.framework/tests/unit/agent-files.test.ts +355 -0
  229. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  230. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  231. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  232. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  233. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  234. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  235. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  236. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  237. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  238. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  239. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  240. package/.framework/tests/unit/system-cron.test.ts +240 -0
  241. package/.framework/tests/unit/timers.test.ts +159 -0
  242. package/README.md +125 -166
  243. package/config/tsconfig.cli.json +2 -2
  244. package/package.json +15 -4
  245. package/templates/project/package-lock.json +2 -2
  246. package/.framework/docs/admin-routes.md +0 -76
  247. package/.framework/docs/api-docs-progress.md +0 -38
  248. package/.framework/docs/api-governance.md +0 -146
  249. package/.framework/docs/api-testing-results.md +0 -212
  250. package/.framework/docs/assembly-launch-guide.md +0 -138
  251. package/.framework/docs/audit-results.md +0 -590
  252. package/.framework/docs/db-api-inventory.md +0 -95
  253. package/.framework/docs/gap-remediation-backlog.md +0 -103
  254. package/.framework/docs/ui-adoption-verification.md +0 -111
  255. package/.framework/docs/ui-api-coverage.md +0 -84
  256. package/.framework/docs/v2-compatibility-audit.md +0 -228
  257. package/.framework/mcp/index.ts +0 -133
  258. package/.framework/mcp/load-env.ts +0 -60
  259. package/.framework/mcp/shared/app-manifest.ts +0 -275
  260. package/.framework/mcp/shared/db.ts +0 -477
  261. package/.framework/mcp/shared/kb.ts +0 -118
  262. package/.framework/mcp/tools/architect.ts +0 -431
  263. package/.framework/mcp/tools/cil/context.ts +0 -345
  264. package/.framework/mcp/tools/cil/graph.ts +0 -242
  265. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  266. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  267. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  268. package/.framework/mcp/tools/cil/project.ts +0 -791
  269. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  270. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  271. package/.framework/mcp/tools/cil/search.ts +0 -97
  272. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  273. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  274. package/.framework/mcp/tools/kb/delete.ts +0 -139
  275. package/.framework/mcp/tools/kb/index.ts +0 -52
  276. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  277. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  278. package/.framework/mcp/tools/kb/publish.ts +0 -100
  279. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  280. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  281. package/.framework/mcp/tools/kb/search.ts +0 -101
  282. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  283. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: Pipelines
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/pipelines.ts
9
+ - .framework/functions/_shared/pipeline-runner.ts
10
+ - .framework/functions/pipeline-executions.ts
11
+ - .framework/src/pages/admin/PipelinesPage.tsx
12
+ - .framework/src/pages/admin/PipelineDetailPage.tsx
13
+ - .framework/migrations/053_builtin_action_seeds.sql
14
+ ---
15
+
16
+ ## What this is for
17
+
18
+ A **pipeline** is a multi-step workflow run by the pipeline runner. It can be triggered manually, on a schedule (via a timer), by a webhook, or by an event (via a trigger). This page controls pipeline metadata, trigger type, and the JSON `stages` the runner executes.
19
+
20
+ ## Fields and settings
21
+
22
+ - **Name** — string. Display name; missing name prevents saving.
23
+ - **Trigger Type** — select: `manual` (run by button), `scheduled` (invoked by a timer), `webhook` (HTTP call), `event` (Spine trigger event). Determines what causes a run.
24
+ - **Active** — checkbox. Inactive pipelines never run, regardless of trigger.
25
+ - **App ID** — select, optional. Owning app; blank = account-scoped.
26
+ - **Description** — string, optional.
27
+ - **Config (JSON)** — free-form runtime configuration passed to the runner.
28
+ - **Stages (JSON)** — required **array** of step definitions executed in order by the runner. Each stage is `{ "stage_type": "<action slug>", "config": {...}, "continue_on_error": false }` — `stage_type` is looked up in `public.actions` (`is_active`) at run time; an unknown slug fails the run with "Action not found for stage type".
29
+ - **Metadata (JSON)** — optional metadata.
30
+
31
+ **Downstream effects**
32
+
33
+ - The runner executes `stages` in array order; the JSON is the source of truth — there is no visual step editor. Stage config can embed `{{trigger.*}}` and `{{record.*}}` placeholders, resolved recursively at run time (`record` = the entity row that fired the event).
34
+ - A failed stage aborts the run unless the stage sets `continue_on_error: true`.
35
+ - Triggers and timers reference pipelines by ID. Changing `trigger_type` does not rewire existing triggers/timers — relink them separately.
36
+ - `scheduled` without a linked timer, or `event` without a matching trigger, means the pipeline never fires.
37
+
38
+ ## Minimal configuration
39
+
40
+ ```json
41
+ {
42
+ "name": "Notify on create",
43
+ "trigger_type": "event",
44
+ "is_active": true,
45
+ "stages": [
46
+ {
47
+ "stage_type": "send_notification",
48
+ "config": {
49
+ "recipients": ["role:admin"],
50
+ "message": "Record {{record.id}} created"
51
+ }
52
+ }
53
+ ]
54
+ }
55
+ ```
56
+
57
+ ## Full configuration
58
+
59
+ Multi-stage pipelines compose the seeded built-in action slugs — `update_item`, `create_record`, `http_request`, `send_notification`, `run_pipeline`, `search_knowledge`, `query_items`, `agent_inference`, `generate_embeddings`, `revoke_embeddings`, `create_interaction` — with `config` carrying shared settings (batch sizes, model names, endpoints) and `metadata` carrying ownership/version notes. Each stage's own `config` is merged over the action row's defaults.
60
+
61
+ ## Example
62
+
63
+ A support app ships an `auto_embed_on_publish` pipeline (`trigger_type: "event"`): stage 1 fetches the published record via `admin-data`, stage 2 calls an AI agent to generate the embedding, stage 3 writes it back. Two apps can each own a same-named pipeline — the **App** column disambiguates them in the list.
64
+
65
+ ## Core files
66
+
67
+ - `.framework/functions/pipelines.ts` — API
68
+ - `.framework/functions/pipeline-runner-background.ts` — background executor
69
+ - `.framework/functions/pipeline-executions.ts` — run history (`observability/executions`)
70
+ - `.framework/src/pages/admin/PipelinesPage.tsx` / `PipelineDetailPage.tsx` — admin UI
71
+ - Tables: `public.pipelines`, `public.pipeline_executions`
72
+
73
+ ## Agent notes
74
+
75
+ - "Runs today" and "last run" come from `pipeline_executions` — a pipeline with no executions shows `Never`.
76
+ - Stages must be a JSON **array**; a pasted object or string is the most common save-time mistake.
77
+ - The runner also implements `send_email`, `kb_ingest`, and `kb_revoke` handlers, but no `actions` rows are seeded for them — a stage using those slugs throws "Action not found" until you insert a matching `public.actions` row yourself.
78
+ - Activating/pausing is `is_active` — a paused pipeline keeps its triggers but they no-op.
@@ -0,0 +1,95 @@
1
+ ---
2
+ title: Prompt Configs
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/prompt-configs.ts
9
+ - .framework/functions/_shared/agent-runner.ts
10
+ - .framework/src/pages/admin/PromptConfigsPage.tsx
11
+ - .framework/src/pages/admin/PromptConfigDetailPage.tsx
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ A **prompt config** stores the prompt body, model settings, escalation rules, knowledge sources, available tools, and metadata that AI agents and chat interfaces use at runtime. Callers select a config by slug.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Name** *(required)* — display and lookup name.
21
+ - **Slug** *(required)* — unique key callers use to select this config.
22
+ - **Description** — optional purpose note.
23
+ - **App ID** — optional owning app.
24
+ - **System Prompt** — base system instructions (composes with the agent's own `system_prompt` at run time).
25
+ - **Context Template** — the prompt body. The agent runner substitutes only `{{user_message}}` and `{{conversation_history}}`; any other `{{placeholder}}` is left literal in the rendered prompt.
26
+ - **Model** — default `gpt-4o`. The model used for this prompt.
27
+ - **Temperature** — default `0.7`. `0` = deterministic; higher = more random.
28
+ - **Max Tokens** — default `4000`. Response token limit.
29
+ - **Is Multi Turn** — default `true`. Whether conversation history is included.
30
+ - **Max History Messages** — default `20`. Prior messages to include when multi-turn.
31
+ - **Confidence Threshold** — optional minimum confidence before acting.
32
+ - **Escalation Action / Target** — where to route low-confidence results.
33
+ - **Output Mode / Field** — how and where the model output is emitted.
34
+ - **Requires Review** — default `false`. Outputs need human review before use.
35
+ - **Knowledge Sources (JSON array)** — data sources the prompt can draw from.
36
+ - **Available Tools (JSON array)** — tool definitions the prompt may invoke.
37
+ - **Tool Constraints (JSON object)** — rules restricting tool use.
38
+ - **Metadata (JSON object)** — extra structured data.
39
+ - **Active** — default `true`. Inactive configs are not returned to callers.
40
+
41
+ **Downstream effects**
42
+
43
+ - Agents and pipelines resolve prompt configs by slug or by ID (`thread.data.prompt_config_id` → `agent.metadata.default_prompt_config_id`) — a missing or inactive config breaks those calls.
44
+ - `requires_review` + `escalation_*` define the human-in-the-loop path; without an escalation target, low-confidence output is just flagged.
45
+ - JSON fields must be valid and correctly shaped (arrays for sources/tools, objects for constraints/metadata).
46
+
47
+ ## Minimal configuration
48
+
49
+ ```json
50
+ {
51
+ "name": "Ticket reply draft",
52
+ "slug": "ticket-reply-draft",
53
+ "context_template": "Draft a reply to this message: {{user_message}}",
54
+ "model": "gpt-4o",
55
+ "is_active": true
56
+ }
57
+ ```
58
+
59
+ ## Full configuration
60
+
61
+ ```json
62
+ {
63
+ "name": "Ticket triage",
64
+ "slug": "ticket-triage",
65
+ "system_prompt": "You classify support tickets.",
66
+ "context_template": "Message: {{user_message}}\nHistory: {{conversation_history}}",
67
+ "model": "gpt-4o",
68
+ "temperature": 0.2,
69
+ "max_tokens": 1500,
70
+ "confidence_threshold": 0.75,
71
+ "escalation_action": "assign",
72
+ "escalation_target": "support-manager",
73
+ "requires_review": true,
74
+ "knowledge_sources": [{ "type": "kb", "slug": "support-kb" }],
75
+ "available_tools": ["search_kb", "set_field"],
76
+ "tool_constraints": { "max_calls": 3 },
77
+ "is_active": true
78
+ }
79
+ ```
80
+
81
+ ## Example
82
+
83
+ A support app ships `ticket-triage` (low temperature, tools constrained to `search_kb` + `set_field`, `requires_review`) and `customer-chat` (multi-turn, `max_history_messages: 30`, `output_mode` streaming). The triage pipeline resolves `ticket-triage` by slug for every new ticket.
84
+
85
+ ## Core files
86
+
87
+ - `.framework/functions/prompt-configs.ts` — API
88
+ - `.framework/src/pages/admin/PromptConfigsPage.tsx` / `PromptConfigDetailPage.tsx` — admin UI
89
+ - Table: `public.prompt_configs`
90
+
91
+ ## Agent notes
92
+
93
+ - The `is_default`/`prompt_type`/`category` columns do **not** exist — the dead UI for them was removed (F-013); use `is_system` and `output_mode`.
94
+ - Pair with an `ai_agents` record: the agent holds model/behavior, this config holds the prompt text.
95
+ - Keep templates small and pass data through placeholders — large embedded blobs defeat versioning.
@@ -0,0 +1,162 @@
1
+ ---
2
+ title: admin-data API
3
+ audience: developers, agents
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/admin-data.ts
9
+ - .framework/functions/_shared/middleware.ts
10
+ - .framework/functions/_shared/permissions.ts
11
+ - .framework/functions/_shared/resolve-ids.ts
12
+ - .framework/functions/_shared/trigger-engine.ts
13
+ - .framework/src/lib/api.ts
14
+ - .framework/src/hooks/useEntityRecord.ts
15
+ ---
16
+
17
+ # `admin-data` API
18
+
19
+ `POST|GET|PATCH|DELETE /api/admin-data` is the single generic CRUD endpoint
20
+ custom apps use for all runtime records. It resolves types, evaluates the
21
+ record's stamped `design_schema` permissions, stamps schemas on write,
22
+ soft-deletes, and fires triggers. Custom apps must route record CRUD through
23
+ it rather than shipping their own record functions — the shared permission,
24
+ type-resolution, audit, and trigger logic lives here.
25
+
26
+ Handler: `.framework/functions/admin-data.ts` via `createHandler`, so every
27
+ request is authenticated and wrapped in the standard envelope — see
28
+ [Authentication](auth.md) and [Errors and Envelopes](errors-and-envelopes.md).
29
+
30
+ ## Request model
31
+
32
+ Action and entity come from the **query string**; record fields come from the
33
+ JSON body (writes) or additional query params (reads). With no `action`, the
34
+ HTTP method picks the verb (`GET`+`id` → get, `GET` → list, `POST` → create,
35
+ `PATCH` → update, `DELETE` → remove).
36
+
37
+ | Action | Method | Params |
38
+ |---|---|---|
39
+ | `list` | GET | `entity` (required), `search`, `type_slug`, `type_slugs` (csv), `sort_field` (default `created_at`), `sort_direction` (`asc`/`desc`, default `desc`), `limit` (default 50), `offset`, `view`, `flag_only`, `exclude_flag`, plus any other `key=value` as `eq` filters (`is_active`/`is_verified`/`is_primary` coerce `'true'` → boolean) |
40
+ | `get` | GET | `entity`, `id`, `view`, `lineage=0` (items only — skips lineage attach) |
41
+ | `stats` | GET | `entity` → `{entity, count}` |
42
+ | `create` | POST | `entity` (query or body), `type_slug` **or** `type_id` (required for non-raw entities), record fields |
43
+ | `update` | PATCH | `entity`, `id`, record fields (partial) |
44
+ | `remove` | DELETE | `entity`, `id`, `soft` (default `'true'`) |
45
+
46
+ Valid entities: `accounts`, `people`, `items`, `threads`, `messages`,
47
+ `links`, `attachments`, `watchers`, `item_progress`, `logs`, `test_runs`,
48
+ `observability`, `test_cases`, `types`, `link_types`, `ai_agents`,
49
+ `prompt_configs`.
50
+
51
+ `logs`, `test_runs`, `observability`, `test_cases` are **raw entities**:
52
+ create takes a per-entity field allowlist, requires specific fields
53
+ (`logs`: `level`+`message`; `test_runs`: `suite`; `observability`:
54
+ `metric_name`+`metric_value`; `test_cases`: `slug`+`name`), and needs no
55
+ `type_id`.
56
+
57
+ ## Response shapes
58
+
59
+ All responses use the standard envelope `{data, error, meta:{requestId,
60
+ duration}}`. `data` is:
61
+
62
+ - `list` → array of sanitized records, or `{data, schema, view}` when
63
+ `view`+`type_slug` are passed and the type's `design_schema` resolves
64
+ - `get` → the sanitized record (+`parents`/`children`/`type_slug` lineage on
65
+ `items` unless `lineage=0`), or `{data, schema, view}` when `view` is
66
+ passed and the record carries `design_schema`
67
+ - `create`/`update` → the sanitized record
68
+ - `remove` → `{deleted: true, soft: true, data: <record>}` on soft delete;
69
+ `{deleted: true, soft: false}` on hard delete
70
+ - `stats` → `{entity, count}`
71
+
72
+ Sanitization applies `design_schema.fields.*.permissions`: fields the
73
+ principal cannot read are stripped before the row is returned.
74
+
75
+ ## Permission evaluation order
76
+
77
+ 1. `createHandler` resolves the principal (401 if anonymous).
78
+ 2. `create` → `resolveFirstSurfacePermissions(personId, accountId,
79
+ typeSlug, 'create', designSchema)`; machine principals with `*:*` scope
80
+ bypass. Denied → 403.
81
+ 3. `update` → `validateFirstSurfaceUpdatePermissions` on the **existing
82
+ record's** stamped `design_schema` (no `record_permissions` → deny); each
83
+ `data.<field>` written requires field `write`; sanitizes values through
84
+ `validation_schema`. `system_admin` bypasses.
85
+ 4. `remove` → `resolveFirstSurfacePermissions(..., 'delete',
86
+ existing.design_schema)`; `system_admin` bypasses.
87
+ 5. `list`/`get` run under `ctx.db` (RLS-scoped for humans, `adminDb` for
88
+ internal/system-admin), then per-record sanitization.
89
+
90
+ ## Stamping & account resolution on create
91
+
92
+ - `type_slug` resolves via `resolveTypeRecord(kind, slug)` (kind from
93
+ `ENTITY_TO_KIND`); `type_id` fetches the row and requires `is_active`.
94
+ One of the two is mandatory — "every runtime record must reference a type".
95
+ - The record is stamped with the type's `design_schema` and
96
+ `validation_schema` at insert; a missing `record_permissions` merges
97
+ `{all: [create,read,update,delete]}`.
98
+ - `account_id` in the body is a **request hint**, not a field — it sets the
99
+ target account when permitted; otherwise `ctx.accountId`.
100
+ - `scope: 'platform'` types are stamped with the `spine-system` account;
101
+ children created via `thread_id`/`target_id`/`parent_id` inherit the
102
+ parent's account and scope (missing parent → 403).
103
+ - `created_by`/`updated_by` stamp `people.id` for human principals;
104
+ `types`/`link_types` have no audit columns and skip the stamp.
105
+ - `checkTypeSlugRateLimit` enforces per-type create policies from
106
+ `apps.config.rate_limits['<type_slug>_create']`.
107
+
108
+ ## Soft delete
109
+
110
+ `remove` with `soft=true` (default) sets `is_active=false` + `updated_by`/
111
+ `updated_at` on `accounts`, `people`, `items`, `threads`, `messages`,
112
+ `watchers`, `item_progress`, `test_cases`. Any other entity — or
113
+ `soft=false` — performs a hard `DELETE`.
114
+
115
+ ## Visibility projection
116
+
117
+ Principals without internal-audience roles never see `visibility:
118
+ 'internal'` threads (filtered or 404), and internal `messages` are projected
119
+ to safe step summaries via `projectMessageForCustomer` — unless the parent
120
+ thread is `external`/`public`.
121
+
122
+ ## Triggers
123
+
124
+ `create`/`update`/`remove` await `fireCreateTriggers` / `fireUpdateTriggers`
125
+ / `fireDeleteTriggers` (errors logged, never fail the request). Update
126
+ triggers receive the previous row for field-change evaluation.
127
+
128
+ ## Examples
129
+
130
+ ```bash
131
+ # List open items of a type
132
+ GET /api/admin-data?action=list&entity=items&type_slug=ticket&status=open&limit=25
133
+
134
+ # Get one item without lineage
135
+ GET /api/admin-data?action=get&entity=items&id=<uuid>&lineage=0
136
+
137
+ # Create a ticket (type by slug)
138
+ POST /api/admin-data?action=create
139
+ {"entity": "items", "type_slug": "ticket", "title": "Login broken",
140
+ "data": {"priority": "high"}}
141
+
142
+ # Partial update — only listed data fields are written, each needs field 'write'
143
+ PATCH /api/admin-data?action=update&entity=items&id=<uuid>
144
+ {"data": {"priority": "low"}, "status": "resolved"}
145
+
146
+ # Soft then hard delete
147
+ DELETE /api/admin-data?action=remove&entity=items&id=<uuid>
148
+ DELETE /api/admin-data?action=remove&entity=items&id=<uuid>&soft=false
149
+ ```
150
+
151
+ Client-side, `.framework/src/hooks/useEntityRecord.ts` shows the canonical
152
+ call pattern through `apiFetch` (`.framework/src/lib/api.ts`), which attaches
153
+ `Authorization`, `X-Account-Id`, `traceparent`, and `x-app-id`.
154
+
155
+ ## Common failures
156
+
157
+ | Status | Cause |
158
+ |---|---|
159
+ | 400 | missing/invalid `entity`, missing `id`, missing `type_id`/`type_slug` on create, missing required raw-entity fields |
160
+ | 401 | no valid credential — see [Authentication](auth.md) |
161
+ | 403 | record/field permission denied; parent record not found or not accessible |
162
+ | 404-style | `get`/`update`/`remove` on an inaccessible row errors "Record not found or access denied" (RLS hides it) |
@@ -0,0 +1,113 @@
1
+ ---
2
+ title: API Authentication
3
+ audience: developers, agents
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/_shared/principal.ts
9
+ - .framework/functions/_shared/middleware.ts
10
+ - .framework/functions/_shared/hash-api-key.ts
11
+ - .framework/functions/_shared/db.ts
12
+ - .framework/functions/api-keys.ts
13
+ - .framework/migrations/004_core_rpc_and_broker.sql
14
+ - .framework/src/lib/api.ts
15
+ ---
16
+
17
+ # API Authentication
18
+
19
+ Every function wrapped by `createHandler` resolves a `Principal` from request
20
+ headers via `resolvePrincipal` (`.framework/functions/_shared/principal.ts`)
21
+ before your code runs. `createPublicHandler` resolves a principal too but
22
+ does not require one. `/api/*` paths proxy to Netlify functions via
23
+ `/.netlify/functions/:splat` (`.framework/public/_redirects`).
24
+
25
+ ## Credential resolution order
26
+
27
+ `resolvePrincipal` checks headers in this order — **first match wins**:
28
+
29
+ 1. **`X-Api-Key`** → machine principal. Looks up `api_keys` rows by
30
+ `key_prefix` (first 8 chars of the presented key) among `is_active` keys,
31
+ requires `expires_at` unset-or-future, verifies `key_hash` with a
32
+ timing-safe pbkdf2 compare (`.framework/functions/_shared/hash-api-key.ts`
33
+ — `pbkdf2$100000$sha256`), enforces per-key rate limit (`rate_limit`,
34
+ default 100 per `config.rate_limit_window_seconds`, default 60s) and a
35
+ per-IP ceiling (`rate_limit_hit` RPC, default 300/60s), then logs usage to
36
+ `api_key_usage_logs`. Scopes come from `key.scopes`; `*:*` grants
37
+ `system_admin`-equivalent reach. `key.machine_type` and `is_internal` ride
38
+ on the principal.
39
+ 2. **`X-Internal-Secret`** → internal invocation. Compared against
40
+ `INTERNAL_INVOKE_SECRET` with a timing-safe hash compare. When valid,
41
+ `X-Cron-Id` resolves a cron principal from `schedules` (validated by the
42
+ `validate_schedule_creator` RPC), `X-Trigger-Id` resolves the trigger's
43
+ action `default_machine_principal_id` from `api_keys`, and a bare secret
44
+ resolves `SYSTEM_PRINCIPAL` (`*:*` scope). **Without this secret the
45
+ `x-cron-id`/`x-trigger-id` headers are never honored** — they exist only
46
+ for trusted function-to-function calls.
47
+ 3. **`Authorization: Bearer <jwt>`** → human principal.
48
+ `supabase.auth.getUser` validates the JWT, `resolveInternalPersonId` maps
49
+ `auth.users.id` → `people.id` (auth_uid binding, then email fallback),
50
+ and the person's `roles.slug` becomes `principal.roles`. Results are
51
+ cached ~60s per token hash (max 500 entries); principals with neither
52
+ `account_id` nor `role_id` (mid-registration) are never cached.
53
+ 4. **Nothing** → `ANONYMOUS_PRINCIPAL` → `createHandler` returns 401.
54
+
55
+ ## Principal shape
56
+
57
+ ```ts
58
+ interface Principal {
59
+ id: string // people.id, api_keys.id, 'system', or 'anonymous'
60
+ type: 'human' | 'machine'
61
+ accountId: string | null
62
+ roles?: string[] // role slugs (humans; *:* machines get system_admin)
63
+ scopes?: string[] // machine scopes, e.g. 'items:read', '*:*'
64
+ machineType?: 'integration' | 'service_account' | 'internal' | 'timer'
65
+ isInternal?: boolean
66
+ provenance: { sourceType: 'jwt'|'api_key'|'cron'|'trigger'|'manual'|'webhook'|'timer', ... }
67
+ authContext?: { jwt?: string } // transport-only — never serialize
68
+ }
69
+ ```
70
+
71
+ `getPrincipalDb(principal)` picks the request's DB client: humans with a JWT
72
+ get an RLS-scoped client (`getUserDb`); machines/anonymous get `adminDb`.
73
+
74
+ ## Scope checking
75
+
76
+ `machineHasScope` honors exact `resource:action`, `resource:*`, and `*:*`
77
+ wildcards. `admin-data` create treats machine `*:*` as authorized; elsewhere
78
+ `isSystemAdmin` (human `system_admin` role or machine `*:*`) is the bypass.
79
+ `humanHasRole` matches role slugs.
80
+
81
+ ## Client headers that matter
82
+
83
+ | Header | Effect |
84
+ |---|---|
85
+ | `Authorization: Bearer <jwt>` | human auth |
86
+ | `X-Api-Key: <key>` | machine auth |
87
+ | `X-Internal-Secret: <secret>` | internal function-to-function auth (+ `X-Cron-Id`/`X-Trigger-Id`) |
88
+ | `X-Account-Id` | carried into `ctx` for account-scoped flows (set by `apiFetch` from `setAccountId`) |
89
+ | `X-App-Id` | app slug hint for app-scoped behavior (auto-set by `apiFetch` from the route prefix) |
90
+ | `X-Request-Id` | propagated request id (a UUID is generated when absent/invalid) |
91
+ | `traceparent`/`tracestate` | W3C trace context, echoed back on responses |
92
+
93
+ The browser client `apiFetch` (`.framework/src/lib/api.ts`) attaches
94
+ `Authorization`, `X-Account-Id`, `traceparent`, and `x-app-id`
95
+ automatically.
96
+
97
+ ## Failure modes
98
+
99
+ | Status | Cause |
100
+ |---|---|
101
+ | 401 `Authentication required` | no credential resolved, expired/invalid JWT (`auth.getUser` fails → anonymous), or `resolvePrincipal` threw (see below) |
102
+ | 401 `Invalid or inactive machine principal` | unknown `key_prefix`, inactive key, expired key, or hash mismatch — thrown by `resolveMachinePrincipal` |
103
+ | 401 `Rate limit exceeded for this API key` / `for this address` | per-key or per-IP ceilings hit — thrown as errors, surfaced via the 500-unless-statusCode path (these errors carry no `statusCode`, so check function logs when a 500 appears on auth) |
104
+ | 401 `Invalid or inactive schedule: <id>` / `Invalid trigger: <id>` | `x-cron-id`/`x-trigger-id` resolution failed after a valid internal secret |
105
+ | 401 `Person lookup error …` / `Person not found: <id>` | JWT verified but no `people` row exists (incomplete registration or deleted person) |
106
+ | 403 `Access denied` | principal resolved but matched `security_blocklist` (person/account/email/IP), checked by `isBlockedRequest` — fails open when the table is absent |
107
+ | 400 `Invalid JSON in request body` | body was present but unparseable |
108
+
109
+ ## Related
110
+
111
+ - [Errors and Envelopes](errors-and-envelopes.md) · [admin-data](admin-data.md)
112
+ - [API Keys](../../api-keys.md) · [Auth and API Keys](../../concepts/auth-and-api-keys.md)
113
+ - [Environment Variables](../env-vars.md) — `INTERNAL_INVOKE_SECRET`, `SPINE_IP_RATE_LIMIT`
@@ -0,0 +1,113 @@
1
+ ---
2
+ title: Errors and Response Envelopes
3
+ audience: developers, agents
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/_shared/middleware.ts
9
+ - .framework/functions/_shared/principal.ts
10
+ - .framework/functions/_shared/rate-limit.ts
11
+ - .framework/functions/_shared/tracing.ts
12
+ - .framework/src/lib/api.ts
13
+ ---
14
+
15
+ # Errors and Response Envelopes
16
+
17
+ Every function wrapped by `createHandler` or `createPublicHandler`
18
+ (`.framework/functions/_shared/middleware.ts`) returns the same envelope and
19
+ error shape. Custom functions get this for free by returning/throwing from a
20
+ wrapped handler — never construct raw `{statusCode, body}` responses in app
21
+ code.
22
+
23
+ ## Success envelope
24
+
25
+ ```json
26
+ {
27
+ "data": <handler return value>,
28
+ "error": null,
29
+ "meta": { "requestId": "<uuid>", "duration": <ms> }
30
+ }
31
+ ```
32
+
33
+ HTTP status is always `200` for a handler that returns normally — partial
34
+ failures the handler swallows are invisible at the status layer, so handlers
35
+ should throw for real failures.
36
+
37
+ ## Error envelope
38
+
39
+ ```json
40
+ { "data": null, "error": "<message>" }
41
+ ```
42
+
43
+ Two emitters produce it:
44
+
45
+ - `error(message, status)` — used for auth/blocklist/parse failures before
46
+ the handler runs.
47
+ - `structuredError(err, status)` — used for anything the handler throws.
48
+ The thrown error's `statusCode` number becomes the HTTP status; **absent
49
+ `statusCode` the status is `500`**, so domain errors that should surface
50
+ as 4xx must set `err.statusCode` (the codebase idiom is
51
+ `const e: any = new Error('…'); e.statusCode = 400; throw e`).
52
+ Whitelisted extra fields are copied onto the body when present on the
53
+ error: `action`, `limit`, `retry_after` (plus `retryAfter` → `retry_after`)
54
+ — that is how `RateLimitError` from
55
+ `.framework/functions/_shared/rate-limit.ts` renders a 429 with recovery
56
+ metadata.
57
+
58
+ ## Status codes in practice
59
+
60
+ | Status | When |
61
+ |---|---|
62
+ | 200 | success; also preflight (`cors()` returns `{message:'CORS enabled'}`) |
63
+ | 400 | `statusCode`-tagged validation failures; unparsable JSON body (`Invalid JSON in request body`) |
64
+ | 401 | anonymous principal on a `createHandler` endpoint (`Authentication required`); credential resolution failures (see [Authentication](auth.md)) |
65
+ | 403 | `security_blocklist` hits (`Access denied`); `statusCode`-tagged permission failures (`Insufficient permissions …`) |
66
+ | 429 | `RateLimitError` — body carries `action`/`limit`/`retry_after` |
67
+ | 500 | any thrown error without `statusCode` — includes most `new Error('…')` sites |
68
+
69
+ Because untagged errors map to 500, a 500 does not always mean a crash — it
70
+ can be an intentional refusal (e.g. `Invalid action or method`, `Record not
71
+ found`) that nobody tagged. Check `error` text and function logs.
72
+
73
+ ## Response headers
74
+
75
+ Set by `json()` on every response:
76
+
77
+ | Header | Value |
78
+ |---|---|
79
+ | `Content-Type` | `application/json` |
80
+ | `Access-Control-Allow-Origin` | `SITE_URL` → `URL` → `*` (in that order) |
81
+ | `Access-Control-Allow-Headers` | `Content-Type, Authorization, X-Account-Id, X-App-Id, X-Request-ID, X-Source, X-Log-Level, traceparent, tracestate` |
82
+ | `Access-Control-Allow-Methods` | `GET, POST, PATCH, DELETE, OPTIONS` |
83
+ | `X-Request-ID` | the request id (echo of a valid `X-Request-Id` input, else generated UUID) |
84
+ | `traceparent` / `tracestate` | W3C trace context for the request span |
85
+
86
+ ## Request context the handler receives
87
+
88
+ `ctx` (`RequestContext`) carries `principal`, `accountId`, `db` (traced,
89
+ principal-scoped client), `requestId`, `appId` (`X-App-Id`), `query`
90
+ (query-string params plus `method` filled from the HTTP method),
91
+ `requestPath`, lowercased `headers`, `source`/`logLevel` (from `X-Source`/
92
+ `X-Log-Level`), and `trace`. A `?id=` query param is copied into `body.id`
93
+ when the body lacks it.
94
+
95
+ ## Observability side effects
96
+
97
+ Every request emits fire-and-forget spans (`emitSpan`) for
98
+ `request.<method>` / `request.error` / `request.blocked` /
99
+ `request.unauthenticated` / `request.parse_error`, and `traceDb` wraps
100
+ `ctx.db` calls into `db.*` spans. Telemetry failures never fail the request.
101
+
102
+ ## Client handling
103
+
104
+ `apiFetch` (`.framework/src/lib/api.ts`) returns the raw `Response` — callers
105
+ read `response.json()` and inspect `error`. A `401` invokes the registered
106
+ `onUnauthorized` callback except on `/api/auth` and `/api/admin-data` paths.
107
+ Debug per-request tracing is opt-in via `localStorage.spine_debug_api = '1'`.
108
+
109
+ ## Related
110
+
111
+ - [Authentication](auth.md) — credential resolution and its failure modes
112
+ - [admin-data](admin-data.md) — the largest consumer of this contract
113
+ - [Shared Modules](../shared-modules.md) — `middleware.ts` internals