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,125 @@
1
+ ---
2
+ title: Database Migrations
3
+ audience: both
4
+ kind: reference
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/migrate.ts
9
+ - .framework/cli/commands/migrations.ts
10
+ - .framework/cli/commands/install-app.ts
11
+ - .framework/cli/commands/upgrade.ts
12
+ - .framework/migrations/001_core_schema.sql
13
+ - .framework/migrations/003_core_system_data.sql
14
+ - .framework/migrations/069_log_stats_window.sql
15
+ ---
16
+
17
+ # Database Migrations
18
+
19
+ Spine's migration system is deliberately simple: numbered `.sql` files applied
20
+ once, in filename order, each inside its own transaction, recorded in a
21
+ `_spine_migrations` ledger table. Everything runs through
22
+ `npx spine-framework migrate` (`.framework/cli/commands/migrate.ts`).
23
+
24
+ ## File layout and numbering
25
+
26
+ - **Framework migrations** live in `.framework/migrations/` — sealed,
27
+ upgrade-synced. Naming is `NNN_snake_case_description.sql` with a
28
+ three-digit prefix; plain lexicographic sort is the apply order.
29
+ - **Custom migrations** live in `custom/migrations/` — project-owned, applied
30
+ **after** all framework files in the same run (skip them with
31
+ `--framework-only`).
32
+ - Files are filtered to `*.sql` not starting with `.`
33
+ (`collectMigrations` in `.framework/cli/commands/migrate.ts`).
34
+ - **Numbering gaps are expected.** `22–31`, `39–41`, and `63` are reserved or
35
+ consolidated numbers — a gap is not a missing migration (see
36
+ [Database Reference](../reference/database.md)). The same number can also
37
+ appear twice across the framework and custom dirs: the ledger key is the
38
+ **filename**, not the numeric prefix, so `062_send_email_action.sql` and a
39
+ custom `062_*.sql` do not collide.
40
+
41
+ ## The `_spine_migrations` ledger
42
+
43
+ `migrate` creates and reads `public._spine_migrations`
44
+ (`id SERIAL`, `filename TEXT UNIQUE NOT NULL`, `applied_at TIMESTAMPTZ`).
45
+
46
+ Per file, inside one transaction: split the SQL into statements, run each,
47
+ insert the ledger row, `COMMIT`. On the **first** statement error the file
48
+ rolls back, the error is printed, and the run stops — later files are never
49
+ attempted. Files already in the ledger are skipped, so reruns are safe.
50
+
51
+ Note there is a **second, unrelated ledger**: `migrations list` /
52
+ `migrations status` read `supabase_migrations.schema_migrations` — the table
53
+ Supabase's own tooling maintains, which `spine-framework migrate` never
54
+ writes. `migrations status` therefore reports local files as `pending` even
55
+ when `_spine_migrations` shows them applied. Use the `_spine_migrations`
56
+ table itself as the authoritative "what ran" record.
57
+
58
+ ## Running migrations
59
+
60
+ ```bash
61
+ npx spine-framework migrate # apply pending framework + custom
62
+ npx spine-framework migrate --framework-only
63
+ npx spine-framework migrate --dry-run
64
+ ```
65
+
66
+ - Connects **directly to Postgres** — `db.<project-ref>.supabase.co:5432`,
67
+ user `postgres`, database `postgres`. `SUPABASE_URL` must be a hosted
68
+ `https://<ref>.supabase.co` URL and `SUPABASE_DB_PASSWORD` must be set
69
+ (`--db-password` overrides; `.env` is preloaded).
70
+ - `--dry-run` prints **every collected file**, not just pending ones — the
71
+ pending filter lives inside `runMigrations`, which dry-run never reaches.
72
+ To see real pending state, query `_spine_migrations` or run it for real
73
+ (applied files skip cleanly).
74
+ - `npx spine-framework install-app` also calls `runMigrations` before
75
+ seeding; a migration failure aborts the install (`--no-seed` installs
76
+ files only and tolerates missing DB credentials).
77
+
78
+ ## Custom migration rules
79
+
80
+ 1. One file per change, `NNN_name.sql`, in `custom/migrations/`.
81
+ 2. **Idempotent by convention.** The ledger makes files apply-once, but
82
+ drift happens (rows applied by hand, shared dev DBs, ledger restores).
83
+ Write `IF NOT EXISTS`, `ON CONFLICT DO NOTHING` / `DO UPDATE`, and
84
+ `CREATE OR REPLACE` so a re-run is survivable — framework files follow
85
+ the same convention.
86
+ 3. Never edit a file after it has been applied anywhere — the ledger keys on
87
+ filename and will not re-run it. Add a new file instead.
88
+ 4. Never put runtime-record seed data (types, roles, pipelines) in a
89
+ migration when an app manifest `seed` block can express it — seeds get
90
+ conflict handling, validation, and reconciliation that raw SQL does not.
91
+
92
+ ## Rolling out schema changes with app updates
93
+
94
+ Two different channels, don't confuse them:
95
+
96
+ - **Framework schema changes** ship inside `.framework/migrations/` in the
97
+ npm package. `npx spine-framework upgrade` syncs the files and *reports*
98
+ new migrations — it never runs them. `npx spine-framework migrate` applies
99
+ them (see [Upgrade](upgrade.md)).
100
+ - **App-level schema** is data, not DDL: `public.types` `design_schema`
101
+ rows, roles, pipelines ship in the app package's `manifest.json` `seed`
102
+ block, applied by `install-app`/`seed-app`. `install-app` copies only
103
+ `index.tsx`, `manifest.json`, `package.json`, `pages`, `components`,
104
+ `hooks`, `utils`, `functions`, `seed`, `public` into `custom/apps/<slug>/`
105
+ — there is no per-app migrations directory. If an app truly needs DDL,
106
+ ship it as a `custom/migrations/` file in the project repo.
107
+
108
+ Production rollout order: deploy code first or migrate first — both work
109
+ because migrations are additive-by-convention, but the safe default is
110
+ **migrate, then deploy**, so new code never meets a schema older than it
111
+ expects.
112
+
113
+ ## Checklist
114
+
115
+ - [ ] `SUPABASE_URL` + `SUPABASE_DB_PASSWORD` set (`.env` or `--db-password`).
116
+ - [ ] `SELECT filename FROM _spine_migrations ORDER BY filename` reviewed for drift before major upgrades.
117
+ - [ ] New custom migration is idempotent and numbered after the highest local prefix.
118
+ - [ ] After `spine-framework upgrade`: `npx spine-framework migrate` run — upgrade only syncs files.
119
+ - [ ] `migrations status` not trusted as the source of truth (it reads `supabase_migrations`).
120
+
121
+ ## Related
122
+
123
+ - [Database Reference](../reference/database.md) — table catalog and numbering gaps
124
+ - [Upgrade](upgrade.md) · [Deployment](deployment.md) · [Troubleshooting](troubleshooting.md)
125
+ - [CLI Reference](../reference/cli.md) — `migrate`, `migrations`, `install-app` flags
@@ -0,0 +1,148 @@
1
+ ---
2
+ title: Security Checklist
3
+ audience: both
4
+ kind: guide
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/api-keys.ts
9
+ - .framework/functions/_shared/hash-api-key.ts
10
+ - .framework/functions/_shared/principal.ts
11
+ - .framework/functions/_shared/middleware.ts
12
+ - .framework/functions/_shared/permissions.ts
13
+ - .framework/functions/_shared/resolve-credentials.ts
14
+ - .framework/functions/_shared/security.ts
15
+ - .framework/functions/security.ts
16
+ - .framework/functions/logs.ts
17
+ - .framework/functions/debug-auth.ts
18
+ - .framework/migrations/049_security_blocklist.sql
19
+ - netlify.toml
20
+ - .env.example
21
+ ---
22
+
23
+ # Security Checklist
24
+
25
+ Operational security posture for a Spine deployment — what the platform
26
+ already enforces, and the surfaces you must keep an eye on yourself.
27
+ Related reading: [Auth & API Keys](../concepts/auth-and-api-keys.md),
28
+ [Permissions](../concepts/permissions.md),
29
+ [Environment Variables](../reference/env-vars.md).
30
+
31
+ ## Secrets that must stay server-side
32
+
33
+ - **`SUPABASE_SERVICE_ROLE_KEY` bypasses RLS entirely.** It is read only by
34
+ functions and the CLI (`adminDb` in `.framework/functions/_shared/db.ts`)
35
+ and must never appear under a `VITE_*` name — `VITE_*` vars are compiled
36
+ into the public browser bundle. Older docs recommended
37
+ `VITE_SUPABASE_SERVICE_ROLE_KEY`; that is an anti-pattern the audit
38
+ flagged (S-004). If it ever shipped, rotate the key.
39
+ - `netlify.toml` `SECRETS_SCAN_OMIT_KEYS` whitelists only vars that are
40
+ **meant** to be public (`VITE_SUPABASE_URL`, `VITE_SUPABASE_ANON_KEY`,
41
+ `APP_BASE_URL`, `SITE_URL`, …). Do not add secrets to it.
42
+ - `INTERNAL_INVOKE_SECRET`, `SCHEDULER_API_KEY`, `RESEND_API_KEY`,
43
+ `OPENAI_API_KEY`, `NETLIFY_ACCESS_TOKEN` — server-side Netlify env only.
44
+ - `.env` is gitignored and excluded from the shipped file set; no framework
45
+ command writes it (see [Project Map](../guide/02-project-map.md)).
46
+
47
+ ## API key hygiene
48
+
49
+ - Keys mint as `sk_<uuid>`; only `key_prefix` (first 8 chars) and a
50
+ `pbkdf2$<iters>$<salt>$<hash>` value (100k iterations, SHA-256,
51
+ `timingSafeEqual` verify) are stored
52
+ (`.framework/functions/api-keys.ts`,
53
+ `.framework/functions/_shared/hash-api-key.ts`). The raw key is returned
54
+ once as `raw_key`; `omitSecretFields` strips `key_value`/`key_hash` from
55
+ `list`, `get`, and `revoke` responses.
56
+ - Grant **scopes**, not `*:*`. A machine key with `*:*` resolves to the
57
+ `system_admin` role and effectively becomes an RLS-immune superuser
58
+ (`.framework/functions/_shared/principal.ts`). `is_internal` keys are for platform machinery.
59
+ - Every key has a rate limit (default `100`/60s) plus a per-IP ceiling
60
+ (`SPINE_IP_RATE_LIMIT`, default `300`/60s); usage lands in
61
+ `api_key_usage_logs` — read via `/api/api-keys?action=usage-logs` before
62
+ revoking so you see what the key touched.
63
+ - Revoke, don't delete: `POST /api/api-keys?action=revoke` sets
64
+ `is_active=false`. Audit `api_keys` rows for a non-null `key_value`
65
+ column — a legacy column that can hold plaintext (the S-001 incident
66
+ class); nothing in `_shared` reads it, but `select('*')` surfaces used to
67
+ return it.
68
+
69
+ ## Credential sanitization
70
+
71
+ - Integration credentials resolve server-side only:
72
+ `credentials.<field>.source = 'env_var'` reads `process.env` inside the
73
+ function (`.framework/functions/_shared/resolve-credentials.ts`); literal `value` entries are
74
+ stored in `integrations.credentials` — treat that column as secret.
75
+ - `integrations` is a second-surface table: non-system-admin and machine
76
+ reads are sanitized to minimal fields by `.framework/functions/_shared/permissions.ts`.
77
+ - Outbound stage configs (`http_request`) reference the `generic-http`
78
+ integration — credentials live on the integration row, never in the
79
+ pipeline stage JSON.
80
+
81
+ ## Ban / blocklist surface
82
+
83
+ `/api/security` (`.framework/functions/security.ts`) is gated by
84
+ `requireSystemContextWithAudit` — system-admin only. Actions:
85
+ `flagged_accounts`, `ban_user`, `unban_user`, `blocklist`,
86
+ `blocklist_add`, `blocklist_revoke`. A ban is three layers:
87
+
88
+ 1. `security_blocklist` rows (person/email/account/IP) — the authoritative
89
+ layer; enforced per-request in `createHandler` (`isBlockedRequest`) and
90
+ on public endpoints (`isBlockedIp`).
91
+ 2. Supabase Auth ban (`auth.admin.updateUserById`, ~100y `ban_duration`).
92
+ 3. Netlify edge deny rule `spine-security:blocklist` — only when
93
+ `NETLIFY_ACCESS_TOKEN` + `NETLIFY_SITE_ID` are set; skipped silently
94
+ otherwise (auth/edge failures are audited, not thrown).
95
+
96
+ Caveat: blocklist enforcement **fails open** when
97
+ `security_blocklist` is absent (pre-049 installs) — a missing table means
98
+ no blocking, not an error.
99
+
100
+ ## Endpoint exposure notes
101
+
102
+ - `debug-auth` (`/api/debug-auth`) is gated by *any authenticated
103
+ principal*, not system-admin — it reports principal type and
104
+ `SET`/`NOT_SET` env flags. Diagnostic, not product surface; consider
105
+ whether it belongs in a hardened production deploy
106
+ (see [debug-auth](../reference/functions/debug-auth.md)).
107
+ - `/api/system` (`manifest`, `health`, `openapi`) likewise answers any
108
+ authenticated principal — the OpenAPI spec and manifest are not
109
+ admin-only information.
110
+ - Public endpoints (`invites`, `integration-routes`, registration flows)
111
+ run through `createPublicHandler` — anonymous by design; their abuse
112
+ controls are IP blocklist, rate limits, and Turnstile, not auth.
113
+
114
+ ## Known ungated surfaces
115
+
116
+ Honest list — the audit found these; mitigate by key discipline until
117
+ they are tightened:
118
+
119
+ - **`logs?action=cleanup`** uses `ctx.db` with no `isSystemAdmin` check and
120
+ no account scoping (`.framework/functions/logs.ts`). Machine principals
121
+ get `adminDb` from `getPrincipalDb` — so **any valid API key can delete
122
+ log rows across all accounts**. Mitigation: issue machine keys narrowly,
123
+ watch `api_key_usage_logs`, rotate on suspicion.
124
+ - **`api-keys` `list`/`get`** select `*` via `adminDb` for system admins —
125
+ and `ctx.db` is *also* `adminDb` for machine principals, so a scoped
126
+ machine key reads `api_keys` metadata cross-account
127
+ (`api_keys` rows are stripped of `key_value`/`key_hash`, but slugs,
128
+ integration links, rate limits, and scopes are visible).
129
+ - **Function-level RLS is uneven** — mutating handlers branch
130
+ `isSystemAdmin ? adminDb : ctx.db`; a scoped machine key still gets
131
+ `adminDb`, so "machine keys are always RLS-bounded" is not a safe
132
+ assumption anywhere `ctx.db` is used without an explicit account filter.
133
+
134
+ ## Checklist
135
+
136
+ - [ ] No `VITE_`-prefixed secret anywhere; service-role key server-only.
137
+ - [ ] `INTERNAL_INVOKE_SECRET`, `SCHEDULER_API_KEY`, `APP_BASE_URL` set; `SITE_URL` pinned to the real origin for CORS.
138
+ - [ ] Machine keys carry least-privilege scopes (never `*:*` for third parties), rate limits, and expiry where possible.
139
+ - [ ] `api_keys` rows audited for legacy `key_value` plaintext.
140
+ - [ ] Blocklist tested end-to-end (a banned IP gets 403); Netlify edge sync env vars set if edge bans are wanted.
141
+ - [ ] `debug-auth` and `/api/system` exposure reviewed for the environment.
142
+ - [ ] Machine-principal surfaces above understood — `ctx.db` = service role for machines.
143
+
144
+ ## Related
145
+
146
+ - [Auth & API Keys](../concepts/auth-and-api-keys.md) · [Permissions](../concepts/permissions.md)
147
+ - [Errors and Envelopes](../reference/api/errors-and-envelopes.md) · [api-keys](../reference/functions/api-keys.md) · [security](../reference/functions/security.md)
148
+ - [Troubleshooting](troubleshooting.md) · [Deployment](deployment.md)
@@ -0,0 +1,170 @@
1
+ ---
2
+ title: Troubleshooting Playbook
3
+ audience: both
4
+ kind: guide
5
+ verified_against: d0c49d4
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/_shared/trigger-engine.ts
9
+ - .framework/functions/_shared/pipeline-runner.ts
10
+ - .framework/functions/_shared/resolve-credentials.ts
11
+ - .framework/functions/_shared/permissions.ts
12
+ - .framework/functions/_shared/middleware.ts
13
+ - .framework/functions/_shared/principal.ts
14
+ - .framework/functions/logs.ts
15
+ - .framework/functions/pipeline-executions.ts
16
+ - .framework/functions/deliveries.ts
17
+ - .framework/functions/api-keys.ts
18
+ - .framework/cli/commands/migrate.ts
19
+ - .framework/cli/commands/migrations.ts
20
+ - .framework/migrations/003_core_system_data.sql
21
+ - scripts/assemble-functions.sh
22
+ ---
23
+
24
+ # Troubleshooting Playbook
25
+
26
+ ## Where to look first
27
+
28
+ Everything operational lands in three places (see
29
+ [Observability](../concepts/observability.md) for the model):
30
+
31
+ 1. **`pipeline_executions`** — did the run exist, and where did it stop?
32
+ Admin: **Observability → Executions**; API: `/api/pipeline-executions`.
33
+ 2. **`logs`** — search by `trace_id`/`span_name`, or `action=search` on the
34
+ error text (**Observability → Logs** / `/api/logs`). `trigger.fired` /
35
+ `pipeline.failed` audit rows bracket the run. Netlify function logs
36
+ carry what never reached the DB (missing env vars, dispatch failures).
37
+ 3. **`webhook_deliveries`** — outbound HTTP stages and integrations
38
+ (**Configs → Deliveries** / `/api/deliveries`): `last_error`,
39
+ `response_status`, `next_attempt_at`; `POST /api/deliveries?action=retry`
40
+ re-fires an outbound delivery.
41
+
42
+ ## Common failure signatures
43
+
44
+ ### Trigger-dispatched pipeline never runs
45
+
46
+ Symptom: `pipeline_executions` row sits at `queued`, no spans — until the
47
+ sweeper marks it `failed` (~10 min) with `stale execution: runner died
48
+ before terminal write (swept)`
49
+ (`.framework/functions/_shared/pipeline-runner.ts`). Causes, in order of
50
+ likelihood:
51
+
52
+ 1. **`INTERNAL_INVOKE_SECRET` unset.** `dispatchTriggerPipeline` logs
53
+ `INTERNAL_INVOKE_SECRET not set — cannot dispatch trigger pipeline` and
54
+ returns — the queued row is the only artifact
55
+ (`.framework/functions/_shared/trigger-engine.ts`). Fix: set it
56
+ (`openssl rand -hex 32`), redeploy.
57
+ 2. Wrong `SITE_URL`/`URL` — dispatch POSTs to
58
+ `{SITE_URL}/.netlify/functions/pipeline-runner-background`; a mis-pointed
59
+ base URL sends the run elsewhere (or times out after 5s).
60
+
61
+ ### `integration.credential_*` on `generic-http`
62
+
63
+ These stages run through `executeOutbound` with
64
+ `integration_slug: 'generic-http'`
65
+ (`.framework/functions/_shared/pipeline-runner.ts`). Read the audit event
66
+ first — only one of them is fatal:
67
+
68
+ - `integration.credential_env_missing` **fails the call** — an `env_var`
69
+ entry names a variable that isn't set in the function env
70
+ (`.framework/functions/_shared/resolve-credentials.ts`). Fix the
71
+ integration row (Configs → Integrations) or deploy the env var.
72
+ - `integration.credential_missing` / `credential_empty` with
73
+ `skipped: true` are **informational** — no credential is configured, so
74
+ the request went out with no `Authorization` header
75
+ (`.framework/functions/integration-outbound.ts`). If the *target* then
76
+ 401s, the endpoint actually needs auth: set a real `api_token`
77
+ (`source: "env_var"` preferred) or pass `action_config.headers`.
78
+
79
+ ### `403` for a system admin: the `system-admin` dead role key
80
+
81
+ `record_permissions` keys match `roles.slug` **verbatim** — nothing
82
+ normalizes hyphens. A design schema granting `"system-admin"` silently
83
+ denies the real `system_admin` role
84
+ (`.framework/functions/_shared/permissions.ts`, the `DOC-LEARNING` note on
85
+ `resolveFirstSurfacePermissions`). Valid keys are real slugs
86
+ (`system_admin`, `member`, `support`, …) or the `all` wildcard. Same class
87
+ of bug: a `people.role_id` that resolves to no role gets the default-deny
88
+ result — check the `people` → `roles` join, not the UI.
89
+
90
+ ### `member` grants do nothing
91
+
92
+ `member` and `member_admin` are seeded by
93
+ `.framework/migrations/073_seed_member_roles.sql`. On a database that
94
+ predates that migration the slugs exist only when an app seeds them
95
+ (`install-app` applies `seed/roles.json`) or someone creates them via
96
+ `/api/roles`. With no `member` row in `public.roles`,
97
+ `record_permissions.member` grants nobody anything — a guaranteed-deny
98
+ that looks like a permissions bug.
99
+
100
+ ### Trigger never fires at all
101
+
102
+ - Event names are **singular-kind + `_created|_updated|_deleted`** —
103
+ `item_created`, `message_updated`, `person_created`. `ENTITY_EVENT_NAME`
104
+ maps `items`→`item`, `people`→`person`, etc.
105
+ (`.framework/functions/_shared/trigger-engine.ts`), so a trigger
106
+ configured for `items_created` or `record_created` matches nothing.
107
+ - `triggers.is_active` must be true; `config.entity_type`, `config.type_slug`,
108
+ and `config.filters` further narrow matches — check `trigger_executions`
109
+ and `trigger.fired`/`trigger.failed` audit rows.
110
+ - The admin-data delete path does not fire `item_deleted` triggers.
111
+
112
+ ### HTTP 200 that is actually a denial
113
+
114
+ The envelope is always `{data, error, meta}`; a handler that *returns* (not
115
+ throws) a refusal produces HTTP 200 with the denial inside `data`
116
+ ([Errors and Envelopes](../reference/api/errors-and-envelopes.md)) —
117
+ e.g. `POST /api/api-keys?action=validate` returns
118
+ `{is_valid: false, error: 'Insufficient permissions'}` at 200
119
+ (`.framework/functions/api-keys.ts`). Inspect `data`/`error`, not just the
120
+ status. Conversely, a thrown `Error` without `statusCode` maps to **500**,
121
+ so many 500s are intentional refusals (`Invalid action or method`,
122
+ `Record not found or access denied`), not crashes — read the `error` text.
123
+
124
+ ### Edits to functions "do nothing"
125
+
126
+ `netlify dev` serves the assembled snapshot in
127
+ `.assembled/netlify/functions`, not `.framework/functions`. Re-run
128
+ `npm run assemble` (or restart `dev:netlify`) after editing function code —
129
+ see [Deployment](deployment.md).
130
+
131
+ ### `dev:netlify` fails with "Could not acquire required port"
132
+
133
+ Ports 8888 (`netlify dev`) and 3001 (Vite) are fixed in manifest-protected
134
+ files — there is no supported alternate-port path, and moving only one
135
+ side cross-connects the `/api` proxy to whatever still listens on 8888
136
+ (often a second Spine project's dev stack). `scripts/dev-netlify.sh` only
137
+ kills processes it owns; find the holder (`lsof -i :8888 -i :3001`) and
138
+ stop it yourself. `Failed to fetch AI Gateway token … Unauthorized` in the
139
+ same output is unrelated Netlify CLI noise, not a Spine fault.
140
+
141
+ ### Migration state looks wrong
142
+
143
+ `migrations list`/`status` read the `_spine_migrations` ledger (`filename`,
144
+ `applied_at`) that `migrate` writes and compare it against the collected
145
+ files (`.framework/cli/commands/migrations.ts`). `migrate --dry-run` lists
146
+ every collected file, not only pending ones, and file numbering is
147
+ non-contiguous — retired migrations are removed rather than renumbered, so
148
+ gaps are expected (see [Migrations](migrations.md)).
149
+
150
+ ## Reading `pipeline_executions`
151
+
152
+ | Status | Meaning |
153
+ |---|---|
154
+ | `pending` | created via `/api/pipeline-executions` POST, not yet claimed |
155
+ | `queued` | trigger reserved the row; dispatch may or may not have landed |
156
+ | `running` | `pipeline-runner-background` claimed it |
157
+ | `completed` / `failed` | terminal; `error_message` + per-stage results name the failure |
158
+ | `cancelled` | manually cancelled via PATCH |
159
+
160
+ Any of `pending`/`queued`/`running` untouched for >10 minutes is swept to
161
+ `failed` by `sweepStaleExecutions` — a wedged row is evidence, not the bug;
162
+ find why it never reached its terminal write. Useful queries:
163
+ `/api/pipeline-executions?status=failed&pipeline_id=…`,
164
+ `?action=running`, `?action=stats`.
165
+
166
+ ## Related
167
+
168
+ - [Observability](../concepts/observability.md) — logs/executions/deliveries model
169
+ - [Errors and Envelopes](../reference/api/errors-and-envelopes.md) — the 200-wrapped-denial contract
170
+ - [Security Checklist](security-checklist.md) · [Migrations](migrations.md) · [Deployment](deployment.md)
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: Framework Upgrades
3
+ audience: both
4
+ kind: reference
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/upgrade.ts
9
+ - .framework/cli/lib/project-files.ts
10
+ - scripts/checksum-manifest.mts
11
+ - scripts/verify-integrity.sh
12
+ - .framework/templates/project.json
13
+ - netlify.toml
14
+ ---
15
+
16
+ # Framework Upgrades
17
+
18
+ `npx spine-framework upgrade` syncs a project's framework-owned files to the
19
+ `spine-framework` version installed in `node_modules`
20
+ (`.framework/cli/commands/upgrade.ts`). It is a file sync with an integrity
21
+ gate, not a git operation — and the executing copy is always the **new**
22
+ package's code, since `npx` resolves `node_modules`.
23
+
24
+ ## What is sealed
25
+
26
+ The frozen manifest `.spine-manifest.json` (written by `init`, regenerated
27
+ by `upgrade`) records a sha256 for every file under the protected trees —
28
+ `.framework/`, `config/`, `scripts/`, plus `bin/`
29
+ (`scripts/checksum-manifest.mts`, `PROTECTED_DIRS` + `--include-dir bin`).
30
+ The file set itself is declared by `.framework/templates/project.json`
31
+ (`framework_dirs`, `framework_files`, `config_files`, `root_scripts`,
32
+ `bin`).
33
+
34
+ Also recorded in the manifest: `framework_version`,
35
+ `framework_dependencies` (so the package.json merge knows which deps the
36
+ framework owns), a `tree_digest`, and a separate `agent_files` hash map
37
+ (more below). `npm run verify` re-hashes the tree and fails on drift;
38
+ `SPINE_INTEGRITY=warn` downgrades that to warnings.
39
+
40
+ ## The integrity gate
41
+
42
+ Before writing anything, upgrade re-hashes every manifest entry:
43
+
44
+ - Any **modified or missing** protected file → abort with a per-file list.
45
+ The only way through is `--force`, which overwrites modified files and
46
+ restores missing ones (each is logged).
47
+ - This is the safety rail: overwriting `.framework/` loses nothing the user
48
+ owned, because the gate proves they never changed it. **Never edit sealed
49
+ trees** — the change is either blocked or silently reverted later.
50
+ - `--dry-run` computes the full report (added / changed / removed / agent
51
+ assets / new migrations) and writes nothing.
52
+
53
+ ## What an upgrade does, in order
54
+
55
+ 1. Preconditions: refuses to run from the vendored copy (`package.json`
56
+ `name` must be `spine-framework`), requires `.spine-manifest.json`,
57
+ and refuses to downgrade (`cmpSemver` on `framework_version`).
58
+ 2. Integrity gate (above).
59
+ 3. Collects the new file set, then applies copies; **deletions are confined
60
+ to `.framework/`** — files outside it that upstream dropped are warned
61
+ about and left in place.
62
+ 4. Syncs agent assets (below).
63
+ 5. Merges `package.json`: framework scripts and the dependency set are
64
+ rewritten, `spine-framework` pinned exactly; a dependency is removed
65
+ **only** if the previous framework version owned it and the new one
66
+ doesn't. Customer scripts/deps survive.
67
+ 6. Re-renders `netlify.toml`: framework blocks come from the package;
68
+ `[functions."custom_*"]` sections in the existing file (with their
69
+ attached comments) are re-appended verbatim — that's where app-owned
70
+ scheduled functions live. A changed render leaves `netlify.toml.bak`.
71
+ 7. Regenerates `.spine-manifest.json` at the new version.
72
+ 8. Lists new `.framework/migrations/*.sql` files — reported, **never run**.
73
+ `npx spine-framework migrate` applies them.
74
+
75
+ ## Agent files merge behavior
76
+
77
+ `profile.agent_files` (`.framework/templates/project.json`) declares two
78
+ merge modes, applied by `syncAgentFiles` in
79
+ `.framework/cli/lib/project-files.ts`:
80
+
81
+ - **`framework-block` → `AGENTS.md`.** The template renders
82
+ `{{SPINE_VERSION}}`, then the marker-delimited block —
83
+ `<!-- spine-framework:begin version=X.Y.Z -->` …
84
+ `<!-- spine-framework:end -->` — is replaced in place. Everything outside
85
+ the markers is customer content and is preserved. A file with no markers
86
+ gets the block **prepended** (never overwritten). Hashes go into the
87
+ manifest's `agent_files` map — deliberately not `files`, so edits outside
88
+ the markers don't count as drift.
89
+ - **`dir-replace` → `.devin/skills`.** Only `spine-*` subdirectories are
90
+ removed and re-copied from the package. Sibling dirs with any other name
91
+ are customer-owned and untouched.
92
+
93
+ ## What upgrade never touches
94
+
95
+ `custom/`, `spine.config.json`, `.env`, `.spine/`, `netlify.toml`
96
+ `[functions."custom_*"]` sections, and the customer-owned parts of
97
+ `AGENTS.md` / `.devin/skills`. If a run would destroy your work, the design
98
+ has failed — that is the promise the sealed layer exists to keep (see
99
+ [Core vs Custom Boundary](../concepts/core-vs-custom.md)).
100
+
101
+ ## Upgrade checklist
102
+
103
+ - [ ] `npm run verify` clean *before* starting — a dirty tree only aborts later anyway.
104
+ - [ ] `npm install spine-framework@<version>` first; upgrade syncs to whatever `node_modules` has.
105
+ - [ ] `npx spine-framework upgrade --dry-run`, read the report, then run for real.
106
+ - [ ] Never pass `--force` to make local `.framework/` edits stick — it does the opposite. Move the change to `custom/` first.
107
+ - [ ] `npm install` after (dependency set changed), then `npm run verify`.
108
+ - [ ] `npx spine-framework migrate` for the reported new migrations ([Migrations](migrations.md)).
109
+ - [ ] Re-run `npm run assemble` / restart `netlify dev` so `.assembled/` picks up the new files ([Deployment](deployment.md)).
110
+ - [ ] Commit the result — manifest, `netlify.toml`, `package.json`, `.devin/skills`, `AGENTS.md` are all part of the upgrade diff.
111
+
112
+ ## Related
113
+
114
+ - [Anatomy of an installed project](../guide/02-project-map.md) — sealed vs customer trees
115
+ - [Migrations](migrations.md) · [Deployment](deployment.md) · [Troubleshooting](troubleshooting.md)
116
+ - [CLI Reference](../reference/cli.md) — `upgrade`, `verify`, `doctor`
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: Pipeline Executions
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/pages/admin/PipelineExecutionsPage.tsx
9
+ - .framework/functions/pipeline-executions.ts
10
+ - .framework/functions/_shared/pipeline-runner.ts
11
+ - .framework/functions/_shared/trigger-engine.ts
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ **Pipeline Executions** (`/spine-framework/admin/observability/executions`) is the run history for every pipeline — manual runs, trigger fires, timer runs, and webhook calls. Each row is a `pipeline_executions` record: who ran it, when, how long, and whether it finished. Use it to confirm a pipeline fired, find the run that failed, and measure cadence and duration.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Stats cards** — Completed, Failed, Running counts (`/api/pipeline-executions?action=stats`; the Pipelines list reuses `action=stats&date_from=` for "runs today").
21
+ - **Search** — matches pipeline id/name fragments and status text client-side.
22
+ - **Status filter** — `pending`, `running`, `completed`, `failed`, `cancelled`.
23
+ - **Sortable columns** — Pipeline (joined name + execution id prefix), Status, Started (`started_at`), Duration (`duration_ms`), Triggered By (`triggered_by_person` full name/email when known).
24
+ - **Row click** — navigates to `observability/executions/:id`. Note: the same list component renders at that URL — there is currently no separate detail view.
25
+ - The record itself carries `trigger_data` (the payload that fired it), `result.stages` (per-stage output/duration), and `error_message`.
26
+
27
+ **Downstream effects**
28
+
29
+ - The trigger engine inserts a `queued` row before dispatching to `pipeline-runner-background`; `runPipeline` claims it `running`, then writes `completed`/`failed` with per-stage results. A row stuck `running`/`queued` >10 min is swept to `failed` by the cron sweeper.
30
+ - `action=cleanup` (server-side) deletes rows older than `days_to_keep` (default 30) — this page is a window, not an archive.
31
+ - `trigger_count`/`last_triggered` on the Triggers page and "Last Run"/"runs today" on the Pipelines page all read from this table.
32
+
33
+ ## Minimal configuration
34
+
35
+ Nothing to configure — executions are produced by running pipelines. To produce one: run a pipeline manually, fire its trigger, or let a timer come due, then filter Status = `failed` to check the result.
36
+
37
+ ## Full configuration
38
+
39
+ The API supports more than the UI exposes:
40
+
41
+ - `GET /api/pipeline-executions?action=list|running|stats|get&id=` — reads
42
+ - `POST action=cleanup` — `days_to_keep` (default 30), optional `status_filter`
43
+ - `PATCH action=start|complete|cancel` — lifecycle writes used by the runner; `complete` marks the row `failed` when `error_message` is present
44
+
45
+ ## Example
46
+
47
+ A ticket triage trigger fired at 09:12 but no assignment happened. Filter Status = `failed`, find the 09:12 row for the triage pipeline — `error_message` names the stage ("Action not found for stage type"), and `result.stages` shows stages 0–1 succeeded before stage 2 threw.
48
+
49
+ ## Core files
50
+
51
+ - `.framework/src/pages/admin/PipelineExecutionsPage.tsx` — this page
52
+ - `.framework/functions/pipeline-executions.ts` — API (list/get/running/stats/cleanup + start/complete/cancel)
53
+ - `.framework/functions/_shared/pipeline-runner.ts` — writes execution rows, stage results, and the >10min stale sweeper
54
+ - `.framework/functions/_shared/trigger-engine.ts` — pre-creates the `queued` row before dispatch
55
+ - Table: `public.pipeline_executions`
56
+
57
+ ## Agent notes
58
+
59
+ - "Stuck at running/queued" is the sweeper's job — a row still `running` an hour later means the background function died mid-run; it will be marked `failed` on the next sweep.
60
+ - `duration_ms` is wall time of the whole run; per-stage timings live in `result.stages[*].durationMs`.
61
+ - The page has no cancel/retry buttons — lifecycle actions exist on the API but the UI is read-only.
62
+ - `executions/:id` renders the same list — deep links to a single execution do not open a detail view today.