spine-framework 0.4.3 → 0.5.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -3
- package/.framework/cli/commands/bootstrap.ts +392 -0
- package/.framework/cli/commands/dev.ts +91 -218
- package/.framework/cli/commands/doctor.ts +132 -26
- package/.framework/cli/commands/generate.ts +38 -298
- package/.framework/cli/commands/init.ts +20 -1
- package/.framework/cli/commands/migrate.ts +36 -30
- package/.framework/cli/commands/migrations.ts +16 -56
- package/.framework/cli/commands/upgrade.ts +35 -0
- package/.framework/cli/commands/validate-schema.ts +147 -0
- package/.framework/cli/context.ts +223 -110
- package/.framework/cli/index.ts +6 -0
- package/.framework/cli/lib/project-files.ts +241 -1
- package/.framework/functions/_shared/agent-runner.ts +1000 -81
- package/.framework/functions/_shared/cil.ts +2 -1
- package/.framework/functions/_shared/db.ts +3 -6
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/kb.ts +61 -7
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/message-sequence.ts +53 -0
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/pipeline-runner.ts +150 -37
- package/.framework/functions/_shared/principal.ts +9 -6
- package/.framework/functions/_shared/trigger-engine.ts +149 -40
- package/.framework/functions/admin-data.ts +48 -31
- package/.framework/functions/ai-agents.ts +103 -33
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +43 -26
- package/.framework/functions/cil-ingest.ts +48 -15
- package/.framework/functions/debug-auth.ts +14 -8
- package/.framework/functions/deliveries.ts +39 -12
- package/.framework/functions/embeddings.ts +145 -76
- package/.framework/functions/integration-outbound.ts +67 -27
- package/.framework/functions/integrations.ts +254 -127
- package/.framework/functions/logs.ts +273 -133
- package/.framework/functions/observability.ts +277 -147
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipeline-runner-background.ts +120 -0
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +250 -128
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/trigger-scheduler.ts +65 -5
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/064_episode_leases.sql +196 -0
- package/.framework/migrations/065_execution_tables_updated_at.sql +20 -0
- package/.framework/migrations/066_episode_steal_thread_merge.sql +148 -0
- package/.framework/migrations/067_episode_lease_stale_steal.sql +93 -0
- package/.framework/migrations/068_cron_schedule_timer_rpcs.sql +251 -0
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/schemas/spine.config.schema.json +62 -0
- package/.framework/src/App.tsx +3 -1
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +92 -81
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericAppShell.tsx +3 -1
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/auth/TurnstileWidget.tsx +9 -6
- package/.framework/src/components/kb/KBArticleEditor.tsx +49 -6
- package/.framework/src/components/layout/Layout.tsx +4 -2
- package/.framework/src/components/layout/Sidebar.tsx +304 -179
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/hooks/useBranding.ts +47 -0
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +57 -0
- package/.framework/src/instructions/ai-agents.md +69 -0
- package/.framework/src/instructions/alerts.md +80 -0
- package/.framework/src/instructions/api-keys.md +87 -0
- package/.framework/src/instructions/apps.md +77 -0
- package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
- package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
- package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
- package/.framework/src/instructions/concepts/automation.md +186 -0
- package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
- package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
- package/.framework/src/instructions/concepts/design-schema.md +152 -0
- package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
- package/.framework/src/instructions/concepts/observability.md +116 -0
- package/.framework/src/instructions/concepts/permissions.md +150 -0
- package/.framework/src/instructions/concepts/runtime-records.md +111 -0
- package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
- package/.framework/src/instructions/concepts/spine-way.md +172 -0
- package/.framework/src/instructions/deliveries.md +59 -0
- package/.framework/src/instructions/guide/00-orientation.md +106 -0
- package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
- package/.framework/src/instructions/guide/02-project-map.md +147 -0
- package/.framework/src/instructions/guide/03-domain-model.md +223 -0
- package/.framework/src/instructions/guide/04-first-app.md +227 -0
- package/.framework/src/instructions/guide/05-glossary.md +183 -0
- package/.framework/src/instructions/integrations.md +96 -0
- package/.framework/src/instructions/internal/cil-markers.md +83 -0
- package/.framework/src/instructions/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.md +77 -0
- package/.framework/src/instructions/observability.md +61 -0
- package/.framework/src/instructions/operations/deployment.md +115 -0
- package/.framework/src/instructions/operations/migrations.md +125 -0
- package/.framework/src/instructions/operations/security-checklist.md +148 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/pipeline-executions.md +62 -0
- package/.framework/src/instructions/pipelines.md +78 -0
- package/.framework/src/instructions/prompts.md +95 -0
- package/.framework/src/instructions/reference/api/admin-data.md +162 -0
- package/.framework/src/instructions/reference/api/auth.md +113 -0
- package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
- package/.framework/src/instructions/reference/app-manifest.md +150 -0
- package/.framework/src/instructions/reference/cli.md +124 -0
- package/.framework/src/instructions/reference/database.md +132 -0
- package/.framework/src/instructions/reference/design-schema.md +280 -0
- package/.framework/src/instructions/reference/env-vars.md +133 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
- package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
- package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
- package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
- package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
- package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
- package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
- package/.framework/src/instructions/reference/functions/apps.md +140 -0
- package/.framework/src/instructions/reference/functions/auth.md +99 -0
- package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
- package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
- package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
- package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
- package/.framework/src/instructions/reference/functions/index.md +103 -0
- package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
- package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
- package/.framework/src/instructions/reference/functions/integrations.md +130 -0
- package/.framework/src/instructions/reference/functions/invites.md +182 -0
- package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
- package/.framework/src/instructions/reference/functions/kb.md +166 -0
- package/.framework/src/instructions/reference/functions/logs.md +141 -0
- package/.framework/src/instructions/reference/functions/observability.md +105 -0
- package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
- package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
- package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
- package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
- package/.framework/src/instructions/reference/functions/roles.md +121 -0
- package/.framework/src/instructions/reference/functions/security.md +140 -0
- package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
- package/.framework/src/instructions/reference/functions/system.md +106 -0
- package/.framework/src/instructions/reference/functions/tests.md +97 -0
- package/.framework/src/instructions/reference/functions/timers.md +117 -0
- package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
- package/.framework/src/instructions/reference/functions/triggers.md +140 -0
- package/.framework/src/instructions/reference/functions/types.md +146 -0
- package/.framework/src/instructions/reference/seeds.md +126 -0
- package/.framework/src/instructions/reference/shared-modules.md +127 -0
- package/.framework/src/instructions/reference/spine-config.md +114 -0
- package/.framework/src/instructions/roles.md +80 -0
- package/.framework/src/instructions/runtime-detail.md +70 -0
- package/.framework/src/instructions/runtime-list.md +77 -0
- package/.framework/src/instructions/security.md +67 -0
- package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
- package/.framework/src/instructions/tasks/add-a-role.md +96 -0
- package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
- package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
- package/.framework/src/instructions/tasks/add-a-type.md +151 -0
- package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
- package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
- package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
- package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
- package/.framework/src/instructions/tasks/test-your-code.md +108 -0
- package/.framework/src/instructions/tasks/write-a-function.md +132 -0
- package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
- package/.framework/src/instructions/testing.md +59 -0
- package/.framework/src/instructions/timers.md +82 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/branding.ts +128 -0
- package/.framework/src/pages/NoAppPage.tsx +3 -1
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/SetupPage.tsx +5 -2
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +3 -1
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +3 -1
- package/.framework/src/pages/auth/LoginPage.tsx +4 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +3 -1
- package/.framework/templates/agent/AGENTS.md +150 -0
- package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
- package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
- package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
- package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
- package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +5 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +496 -47
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/episode-lease.test.ts +695 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +19 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/pipeline-runner-background.test.ts +120 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +153 -1
- package/.framework/tests/unit/principal-cache.test.ts +15 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/.framework/tests/unit/trigger-engine.test.ts +278 -91
- package/.framework/tests/unit/trigger-scheduler.test.ts +78 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/netlify.toml +5 -0
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -0,0 +1,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.
|