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,136 @@
1
+ ---
2
+ title: system-cron
3
+ audience: developers, agents
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/system-cron.ts
9
+ - .framework/functions/trigger-scheduler.ts
10
+ - .framework/functions/_shared/pipeline-runner.ts
11
+ - .framework/functions/_shared/retry-deliveries.ts
12
+ - .framework/functions/_shared/principal.ts
13
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
14
+ - netlify.toml
15
+ ---
16
+
17
+ # system-cron
18
+
19
+ ## What it does
20
+
21
+ The worker behind every `trigger-scheduler` tick. Each authenticated run
22
+ performs, in order: a stale-execution sweep, due-schedule execution, due-
23
+ timer pipelines, threshold-alert evaluation, webhook-delivery retries, and
24
+ — on the 00:00 UTC tick — log cleanup. It is also directly invocable over
25
+ HTTP for manual runs.
26
+
27
+ ## Route and methods
28
+
29
+ `/.netlify/functions/system-cron`. Normally reached internally via
30
+ `trigger-scheduler` (Netlify Clockwork, `* * * * *` in `netlify.toml`),
31
+ which calls this handler either with a fabricated `SYSTEM_PRINCIPAL`
32
+ context (scheduled ticks) or through the normal middleware path (manual
33
+ calls). Any HTTP method reaches the handler — there is no method dispatch.
34
+
35
+ ## Authentication
36
+
37
+ Inside the handler, after `createHandler` has resolved a non-anonymous
38
+ principal:
39
+
40
+ - `?api_key=` or `?scheduler_key=` equal to the `SCHEDULER_API_KEY`
41
+ environment variable, **or**
42
+ - a machine principal with `machineType === 'internal'` — which is what a
43
+ valid `x-internal-secret` header resolves to (`SYSTEM_PRINCIPAL`).
44
+
45
+ Anything else returns 403 and emits a `system_cron.unauthorized_access`
46
+ audit event. When `SCHEDULER_API_KEY` is unset the key comparison can
47
+ never match, so only internal machine principals pass — fail closed.
48
+
49
+ ## Request contract
50
+
51
+ - Query: `api_key` or `scheduler_key` (scheduler key auth path only).
52
+ - Headers: `x-internal-secret` (internal machine principal path).
53
+ - No body is read.
54
+
55
+ ## What runs per invocation
56
+
57
+ 1. **Stale-execution sweep** — `sweepStaleExecutions()` marks
58
+ `pipeline_executions` rows in `running`/`pending`/`queued` untouched
59
+ for over 10 minutes as `failed` with a `stale execution` error message.
60
+ Runs on every invocation, before anything else.
61
+ 2. **Due schedules** — `get_due_schedules` RPC returns due
62
+ `public.schedules` rows. For each: `validate_schedule_creator` RPC →
63
+ load the `public.actions` row → load the schedule's machine principal
64
+ from `api_keys` → verify scope (`action.required_scopes[0]` or
65
+ `<handler>:execute`, satisfied also by `*:*`) → `executeAction` →
66
+ insert a `schedule_executions` row → `update_schedule_after_run` RPC →
67
+ `schedule.execute` audit.
68
+ 3. **Due timers** — `get_due_timers` RPC; each due timer runs
69
+ `runPipeline(timer.pipeline_id, …)` under a fabricated machine
70
+ principal `timer:<timer_id>` with scope `pipelines:execute`, then
71
+ `update_timer_after_run` reschedules it. Timers without `pipeline_id`
72
+ fail that item only.
73
+ 4. **Threshold alerts** — active `threshold_alerts` rows are evaluated
74
+ against `error_rate`, `latency_p95`, or `pipeline_failure_rate` metric
75
+ RPCs over `window_minutes`. `operator: 'gt'` compares `>`; anything
76
+ else compares `<`. A breached threshold with a `pipeline_id` fires
77
+ `runPipeline` with the metric details as trigger data, and a
78
+ `threshold.breached` audit event is emitted either way on breach.
79
+ 5. **Log cleanup** — only when the tick lands on UTC 00:00:
80
+ `cleanup_old_logs` RPC with `p_retention_days: 30`.
81
+ 6. **Delivery retries** — `retryPendingDeliveries(ctx)` sweeps due
82
+ outbound webhook deliveries.
83
+
84
+ `executeAction` routes `action.handler_module`:
85
+
86
+ - `functions` — `send_email`, `generate_report`, `notify_watchers` are
87
+ console-log stubs returning success-shaped objects; `run_pipeline`
88
+ invokes `runPipeline` for real.
89
+ - `integrations` — stub returning `{ executed: true, handler }`.
90
+ - `custom` — throws `Custom handlers not yet implemented`.
91
+ - anything else — throws `Unknown handler module`.
92
+
93
+ ## Response contract
94
+
95
+ - 200 — `{ message, executed, success, failed, skipped,
96
+ thresholds_evaluated, thresholds_breached, logs_cleaned,
97
+ deliveries_retried, results }` where `results[]` carries
98
+ `{ scheduleId, actionId, status: success|failed|skipped, error?,
99
+ durationMs }` per schedule/timer item.
100
+ - 200 with `{ message: 'No schedules due for execution', executed: 0,
101
+ results: [] }` when `get_due_schedules` returns nothing — see the early
102
+ return note below.
103
+ - 403 — invalid scheduler authentication.
104
+ - 500 — top-level failure, with a `system_cron.error` audit event.
105
+
106
+ ## Side effects
107
+
108
+ Writes `pipeline_executions` (via `runPipeline` and the sweep),
109
+ `schedule_executions`, schedule/timer bookkeeping via RPC, `logs` audit
110
+ events (`system_cron.unauthorized_access`, `system_cron.error`,
111
+ `schedule.execute`, `threshold.breached`), deletes old `logs` rows at
112
+ 00:00 UTC, and mutates `webhook_deliveries` through the retry sweep.
113
+
114
+ ## Errors
115
+
116
+ Per-item failures are contained: a failing schedule/timer records a
117
+ `failed` result and a `schedule_executions`/`update_*_after_run` failure
118
+ record rather than aborting the tick. A failure in the outer block —
119
+ e.g. `get_due_schedules` itself erroring — short-circuits to 500 with a
120
+ `system_cron.error` audit. Threshold, timer-loop, cleanup, and sweep
121
+ failures are caught and logged without failing the response.
122
+
123
+ ## Agent notes
124
+
125
+ - **Early return:** when `get_due_schedules` yields no due schedules the
126
+ handler returns immediately — due timers, threshold evaluation,
127
+ delivery retries, and log cleanup all sit *after* that return and only
128
+ run on ticks where at least one schedule is due. Installations relying
129
+ on timers need at least one due schedule per tick for those steps to
130
+ execute; verify this is intended before depending on timer cadence.
131
+ - `send_email`, `generate_report`, `notify_watchers`, and the
132
+ `integrations` module are stubs — a schedule pointing at them "succeeds"
133
+ while doing nothing but a console log. Only `run_pipeline` performs real
134
+ work today.
135
+ - `SCHEDULER_API_KEY` unset does not open the endpoint: the key path
136
+ simply never matches and only internal machine principals pass.
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: system
3
+ audience: developers, agents
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/system.ts
9
+ - .framework/functions/_shared/middleware.ts
10
+ - .framework/functions/_shared/db.ts
11
+ ---
12
+
13
+ # system
14
+
15
+ ## What it does
16
+
17
+ Deployment introspection: a `manifest` action describing version,
18
+ migrations, and the function surface; a `health` action reporting
19
+ database/migration/integrity status; and an `openapi` action generating an
20
+ OpenAPI 3.1.0 document from discovered functions. Read-only — nothing here
21
+ mutates the database.
22
+
23
+ ## Route and methods
24
+
25
+ `/.netlify/functions/system?action=<manifest|health|openapi>`.
26
+
27
+ `action` defaults to `manifest` when omitted. There is no method check —
28
+ any HTTP method dispatches the same way.
29
+
30
+ ## Authentication
31
+
32
+ Wrapped in `createHandler`: any non-anonymous principal passes — human
33
+ Bearer JWT, machine `x-api-key`, or `x-internal-secret`. Anonymous → 401.
34
+ There is no role gate; every authenticated caller can read the manifest,
35
+ health, and OpenAPI output. All queries run on `adminDb`.
36
+
37
+ ## Request contract
38
+
39
+ Query parameters only: `action` (optional, default `manifest`). No body is
40
+ read.
41
+
42
+ ## Response contract
43
+
44
+ All responses arrive inside the middleware envelope
45
+ `{ data: { data: <payload> }, error: null, meta }` — the action functions
46
+ return `{ data: … }` objects which are wrapped again by `createHandler`.
47
+
48
+ - `manifest` → `{ version, schema: 'public', migrations: { applied,
49
+ latest, pending }, functions: [{ name, methods, path, description }],
50
+ integrity: { core_hash, verified } }`.
51
+ - `version` comes from `.spine-manifest.json` in the process working
52
+ directory (default `'2.0.0'` when missing or unparseable).
53
+ - `integrity.core_hash` is `manifest.integrity.src`; `verified` is true
54
+ only when the file exists and parses.
55
+ - `functions` is built by scanning a `v2-core/functions` directory for
56
+ `.ts` files and assigning methods from blocklists (see Agent notes).
57
+ - `health` → `{ status: 'healthy' | 'degraded' | 'unhealthy', checks:
58
+ { database: { connected, latency_ms }, migrations: { current, pending,
59
+ applied }, integrity: { verified }, test_runs? } }`.
60
+ - `unhealthy` when the `accounts` probe query fails; `degraded` when
61
+ migrations are pending or integrity is unverified; else `healthy`.
62
+ - `test_runs` is the latest `test_runs` row (`suite`, `status`,
63
+ `started_at`) when present.
64
+ - `openapi` → an OpenAPI `3.1.0` document (`info.title` = "Spine API",
65
+ `info.version` = manifest version, `servers: ['/ .netlify/functions']`
66
+ — see Agent notes) whose `paths` are generated from the discovered
67
+ function list, each operation exposing an optional `action` query
68
+ parameter and a placeholder object response schema.
69
+ - Unknown action → the handler returns `{ error: 'Unknown action: ….
70
+ Valid actions: manifest, health, openapi', status: 400 }` **as data** —
71
+ the HTTP response is still 200 because the value is returned, not
72
+ thrown.
73
+
74
+ ## Side effects
75
+
76
+ None on the database. The filesystem reads are: `.spine-manifest.json`,
77
+ and the `v2-core/functions` + `v2-core/migrations_dayzero` directories
78
+ under `process.cwd()`. Database reads: `accounts` (health probe),
79
+ `public.schema_migrations`, `test_runs` — all via `adminDb`.
80
+
81
+ ## Errors
82
+
83
+ The handler never throws for its own logic — database failures inside
84
+ `getManifest`/`getHealth` are swallowed into `applied: 0` / `pending`
85
+ counts and status flags. `createHandler` still surfaces middleware-level
86
+ errors (401 unauthenticated, 400 invalid JSON body).
87
+
88
+ ## Agent notes
89
+
90
+ - **Stale paths:** `discoverFunctions` scans `v2-core/functions` and the
91
+ migration checks scan `v2-core/migrations_dayzero` — `v2-core` is the
92
+ pre-monorepo layout name; the live code lives under
93
+ `.framework/functions` and `.framework/migrations`. In the current
94
+ layout those directories do not exist, so `functions` returns `[]` and
95
+ `migrations.pending` is always `0` (making `migrations.current` always
96
+ true and `health` never degraded on migrations). Treat the manifest's
97
+ `functions` list and pending count as unpopulated until this is fixed.
98
+ - The migration table queried is `schema_migrations`; the CLI migrator
99
+ records to `_spine_migrations`, so `applied`/`latest` may also read
100
+ empty — the query failure is swallowed silently.
101
+ - Because `verified` requires a readable `.spine-manifest.json` at the
102
+ function's working directory, `health` reports `degraded` on deploys
103
+ where the file is absent from the bundle.
104
+ - This endpoint answers "what is deployed" questions for agents: prefer
105
+ `health` for a liveness signal and `manifest` for version/integrity; do
106
+ not rely on `functions` or `openapi` path lists being complete.
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: tests
3
+ audience: both
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/tests.ts
9
+ - .framework/functions/_shared/middleware.ts
10
+ - .framework/public/_redirects
11
+ ---
12
+
13
+ # tests
14
+
15
+ ## What it does
16
+
17
+ Read-only reporting over the `test_runs` and `test_results` tables —
18
+ the suites a Spine deployment records about itself. Lists recent runs,
19
+ returns one run with its results, or summarizes the last 30 days per
20
+ suite.
21
+
22
+ ## Route and methods
23
+
24
+ `/.netlify/functions/tests`, reachable as `/api/tests` (`/api/*` →
25
+ `/.netlify/functions/:splat`). No method check — the action comes from
26
+ query parameters:
27
+
28
+ | Call | Result |
29
+ |---|---|
30
+ | `GET /api/tests` or `?action=list` | recent `test_runs` (default limit 20, max 100; `?suite=` filters) |
31
+ | `GET /api/tests?action=get&id=<uuid>` or any `?id=`/`?run_id=` | one run + its `test_results` |
32
+ | `GET /api/tests?action=stats` | per-suite rollup for `unit`, `integration`, `api`, `ui` over the last 30 days |
33
+
34
+ Note that `?id=` (or `?run_id=`) forces the `get` path regardless of
35
+ `action`.
36
+
37
+ ## Authentication
38
+
39
+ Standard `createHandler` plus `requireSystemContextWithAudit` — the
40
+ caller must resolve to a `system_admin` principal (human with the
41
+ `system_admin` role, a `*:*` machine key, or the internal secret).
42
+ Anonymous is rejected with 401 by the middleware.
43
+
44
+ All queries use `adminDb` (service role).
45
+
46
+ ## Request contract
47
+
48
+ Query params only: `action` (`list` default), `id`/`run_id`, `suite`,
49
+ `limit` (capped at 100).
50
+
51
+ ## Response contract
52
+
53
+ This handler returns raw `{ data, error }` objects as its result, so the
54
+ middleware wraps them again: the body is
55
+ `{ "data": { "data": <payload>, "error": null }, "error": null, "meta": {...} }`.
56
+
57
+ - `list` → `data.data` = array of run summaries (`id, suite, status,
58
+ started_at, finished_at, duration_ms, total, passed, failed, skipped,
59
+ triggered_by`).
60
+ - `get` → `data.data` = the run row plus `results` (`test_results` ordered
61
+ by `status`, `file`, `name`).
62
+ - `stats` → `data.data` = one object per suite: `{ suite, total_runs,
63
+ last_status, last_run_at, last_passed, last_failed, last_total }`.
64
+
65
+ **Errors are also embedded in the payload**: failures return
66
+ `{ error: "<msg>", status: <code> }` inside `data` — the outer HTTP status
67
+ is still 200.
68
+
69
+ ## Side effects
70
+
71
+ None — read-only.
72
+
73
+ ## Errors
74
+
75
+ Embedded under `data` (HTTP 200):
76
+
77
+ | Payload | Meaning |
78
+ |---|---|
79
+ | `{ error: "id is required for action=get", status: 400 }` | `action=get` without `id`/`run_id` |
80
+ | `{ error: "Test run not found: …", status: 404 }` | unknown run id |
81
+ | `{ error: "<postgrest msg>", status: 500 }` | query failure |
82
+
83
+ Non-system-admin callers get the `requireSystemContextWithAudit` denial,
84
+ which — like the other responses — is returned as the handler result and
85
+ lands inside `data` (a `{ statusCode, headers, body }` response object)
86
+ with HTTP 200, not as a real 403.
87
+
88
+ ## Agent notes
89
+
90
+ - Always unwrap twice: `body.data.data` is the payload, `body.data.error`
91
+ is where this endpoint reports failures. Do not rely on HTTP status
92
+ codes here.
93
+ - `stats` only covers the four hard-coded suites (`unit`, `integration`,
94
+ `api`, `ui`); other suite names appear in `list` but never in `stats`.
95
+ - To record runs, write `test_runs`/`test_results` through
96
+ `/api/admin-data` (raw entity path) or service-role code — this endpoint
97
+ does not create them.
@@ -0,0 +1,117 @@
1
+ ---
2
+ title: timers
3
+ audience: both
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/timers.ts
9
+ - .framework/functions/_shared/middleware.ts
10
+ - .framework/functions/system-cron.ts
11
+ - .framework/migrations/020_schema_reconciliation.sql
12
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
13
+ - .framework/public/_redirects
14
+ ---
15
+
16
+ # timers
17
+
18
+ ## What it does
19
+
20
+ CRUD plus `toggle` over `public.timers` — the config rows that bind a
21
+ `pipeline_id` to a schedule (`timer_type` + `config`, with
22
+ `next_execution`/`execution_count` bookkeeping columns). **This function
23
+ never executes anything**: firing is done by `system-cron`, which polls
24
+ the `get_due_timers` RPC each tick and calls `runPipeline` as a
25
+ `timer:<id>` machine principal, then reschedules via
26
+ `update_timer_after_run` (`.framework/migrations/068_cron_schedule_timer_rpcs.sql`).
27
+
28
+ ## Route and methods
29
+
30
+ `/.netlify/functions/timers`, reachable as `/api/timers` (`/api/*` →
31
+ `/.netlify/functions/:splat`, `.framework/public/_redirects`). Dispatch
32
+ is by **`?action=` only** — the HTTP method is ignored:
33
+
34
+ | Call | Handler |
35
+ |---|---|
36
+ | `/api/timers?action=list` | `list` |
37
+ | `/api/timers?action=get&id=<uuid>` | `get` |
38
+ | `/api/timers?action=create` | `create` |
39
+ | `/api/timers?action=update` (`id` in body or query) | `update` |
40
+ | `/api/timers?action=remove` (`id` in body or query) | `remove` |
41
+ | `/api/timers?action=toggle` (`id` in body or query) | `toggle` |
42
+
43
+ A bare `GET /api/timers` with no action throws
44
+ `Unsupported action: undefined`. Conversely `GET ?action=create` reaches
45
+ `create` (with a null body → required-field error).
46
+
47
+ ## Authentication
48
+
49
+ Standard `createHandler` auth: `x-api-key` → machine principal,
50
+ `x-internal-secret` → internal principal, `Bearer <jwt>` → human;
51
+ anonymous → 401.
52
+
53
+ - `list`/`create` require `ctx.accountId`; `create`/`update`/`remove`/
54
+ `toggle` additionally require a person+account context.
55
+ - No role gate — system admins use `adminDb`, other humans use
56
+ JWT-scoped `ctx.db` (RLS); machines run service-role.
57
+ - Reads are sanitized: non-admins get `{ id }` per row (timer rows carry
58
+ no `design_schema`).
59
+
60
+ ## Request contract
61
+
62
+ **list** (query): `app_id`, `timer_type`, `is_active`
63
+ (`'true'`/`'false'`). Ordered by `name`; no pagination parameters.
64
+
65
+ **get** (query): `id`.
66
+
67
+ **create** (body): `name`, `timer_type` required. Optional: `app_id`,
68
+ `description`, `config` `{}`, `pipeline_id`, `metadata` `{}`.
69
+ `account_id` and `created_by` stamped from context. (Columns
70
+ `timer_type`, `pipeline_id`, `next_execution`, `execution_count` were
71
+ added by `.framework/migrations/020_schema_reconciliation.sql`.)
72
+
73
+ **update** (`id` in body or query + body): allowlist — `name`,
74
+ `description`, `config`, `pipeline_id`, `metadata`, `is_active`,
75
+ `timer_type`, `app_id`.
76
+
77
+ **remove** (`id` in body or query): **hard delete**.
78
+
79
+ **toggle** (`id` in body or query): flips `is_active` to its negation —
80
+ no target-state argument.
81
+
82
+ ## Response contract
83
+
84
+ Standard `{ data, error, meta }` envelope. `list`/`get`/`create`/
85
+ `update`/`toggle` → sanitized row(s); `remove` → `{ success: true }`.
86
+
87
+ ## Side effects
88
+
89
+ - `toggle` and `update` stamp `updated_at`; `is_active` is what
90
+ `get_due_timers` gates on (plus `next_execution <= now`).
91
+ - `emitLog` writes `timer.created` / `.updated` / `.deleted` /
92
+ `.toggled` audit rows to `public.logs`.
93
+ - No schedule recomputation happens here — `next_execution` is only
94
+ advanced by `update_timer_after_run` during `system-cron` ticks.
95
+
96
+ ## Errors
97
+
98
+ | Condition | HTTP | Message |
99
+ |---|---|---|
100
+ | No account context (list, create) | 500 | `Account context required` |
101
+ | Missing create fields | 500 | `name and timer_type are required` |
102
+ | No person+account context | 500 | `User context (person and account) required` |
103
+ | Missing `id` | 500 | `Timer ID is required` |
104
+ | Missing row (remove/toggle) | 500 | `Timer not found` |
105
+ | Missing/unknown action | 500 | `Unsupported action: <action>` |
106
+
107
+ ## Agent notes
108
+
109
+ - `next_execution` is a real column (migration 020) but is not in the
110
+ create/update allowlists — it is only read by `get_due_timers` and
111
+ advanced by `update_timer_after_run` on `system-cron` ticks.
112
+ - `toggle` flips blindly — read the row first if you need a target
113
+ state; concurrent toggles can cancel each other.
114
+ - `remove` is a hard delete (unlike `triggers`, which soft-deletes).
115
+ - Distinct roles: `timers` = config CRUD; `system-cron` = firing;
116
+ `pipelines` = what runs; `pipeline-executions` = run records. See
117
+ [Automation](../../concepts/automation.md).
@@ -0,0 +1,102 @@
1
+ ---
2
+ title: trigger-scheduler
3
+ audience: developers, agents
4
+ kind: reference
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/trigger-scheduler.ts
9
+ - .framework/functions/system-cron.ts
10
+ - .framework/functions/_shared/principal.ts
11
+ - netlify.toml
12
+ ---
13
+
14
+ # trigger-scheduler
15
+
16
+ ## What it does
17
+
18
+ The clock for Spine automation. `netlify.toml` declares it a Netlify
19
+ scheduled function at `* * * * *` — Netlify Clockwork invokes it every
20
+ minute. The file itself is a thin shim: it detects whether the invocation
21
+ is a genuine scheduled tick (which arrives with no credentials) and runs
22
+ the shared `system-cron` handler under a fabricated system context, or
23
+ falls through to `system-cron`'s normal middleware auth for everything
24
+ else. All actual work — due schedules, due timers, threshold alerts,
25
+ delivery retries, log cleanup — lives in `system-cron`.
26
+
27
+ ## Route and methods
28
+
29
+ Two invocation shapes:
30
+
31
+ 1. **Netlify scheduled invocation** — the platform calls
32
+ `/.netlify/functions/trigger-scheduler` once per minute with a schedule
33
+ indicator header and a `{"next_run": "<ISO timestamp>"}` body.
34
+ 2. **Manual HTTP call** — any authenticated request to the same path
35
+ (useful for forcing a tick in dev). This falls through to
36
+ `system-cron`'s `createHandler` auth path.
37
+
38
+ ## Authentication
39
+
40
+ Clockwork detection (`isNetlifyScheduledInvocation`) requires **both**:
41
+
42
+ - a schedule indicator — `x-netlify-event: schedule` (production canonical
43
+ header), `x-nf-event: schedule` (Netlify CLI emulation), or a
44
+ `Netlify Clockwork` user-agent when neither header is present — **and**
45
+ - a JSON body whose `next_run` field is a string.
46
+
47
+ Requiring the body shape means a bare spoofed header or UA alone cannot
48
+ authenticate. A detected tick fabricates a `RequestContext` carrying
49
+ `SYSTEM_PRINCIPAL` (`machine`, `machineType: 'internal'`, `*:*` scope) and
50
+ `adminDb`, then calls the cron handler directly — `createHandler`
51
+ short-circuits when `requestId` + `principal` are already present, so no
52
+ second auth resolution happens.
53
+
54
+ Anything that fails detection goes through `system-cron`'s own checks:
55
+
56
+ - `x-internal-secret` header → resolves to `SYSTEM_PRINCIPAL` via
57
+ `resolvePrincipal`, which passes the internal-machine gate.
58
+ - `?api_key=` or `?scheduler_key=` query param equal to
59
+ `SCHEDULER_API_KEY`.
60
+ - any other internal machine principal.
61
+ - anonymous requests → 401 from `createHandler`; authenticated but
62
+ unauthorized → 403 plus a `system_cron.unauthorized_access` audit event.
63
+
64
+ ## Request contract
65
+
66
+ - Scheduled tick: schedule indicator header + body
67
+ `{"next_run": "<ISO timestamp>"}`.
68
+ - Manual call: `?api_key=$SCHEDULER_API_KEY` (or `?scheduler_key=`), or an
69
+ `x-internal-secret` header. No request body is read.
70
+
71
+ ## Response contract
72
+
73
+ Whatever `system-cron`'s handler returns — a JSON summary of the tick
74
+ (`executed`, `success`, `failed`, `skipped`, `thresholds_evaluated`,
75
+ `logs_cleaned`, `deliveries_retried`, `results[]`), or an auth/500 error.
76
+ See the `system-cron` reference page for the full shape.
77
+
78
+ ## Side effects
79
+
80
+ None of its own — every effect belongs to `system-cron`. The only
81
+ local decision is which auth path the invocation takes.
82
+
83
+ ## Errors
84
+
85
+ - 401 — unauthenticated non-scheduled request (from `createHandler`).
86
+ - 403 — authenticated but not an internal machine principal and no valid
87
+ scheduler key (from `system-cron`, with audit event).
88
+ - 500 — bubbled from `system-cron` failures (`system_cron.error` audit).
89
+
90
+ ## Agent notes
91
+
92
+ - The function is deliberately a shim: scheduled-invocation plumbing has
93
+ changed across Netlify environments before (CLI vs production header
94
+ names), so detection is centralized here rather than inside the worker.
95
+ If a future Netlify rename breaks ticks, this file is the fix point —
96
+ the Clockwork user-agent check is already the third fallback indicator.
97
+ - A "401 every minute" symptom in production usually means the scheduler
98
+ hit the non-Clockwork path — check that the invocation carries a
99
+ recognized schedule header **and** the `next_run` body.
100
+ - There is no per-tick idempotency key: two overlapping ticks would both
101
+ run `system-cron`. Due-item claiming relies on the RPCs and row state,
102
+ not on tick uniqueness.