spine-framework 0.4.4 → 0.5.0-rc.5
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/install-app.ts +183 -13
- 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-app.ts +127 -1
- 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/app-manifest.ts +2 -0
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/permissions.ts +4 -0
- package/.framework/functions/admin-data.ts +51 -28
- package/.framework/functions/ai-agents.ts +10 -6
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +63 -29
- 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 +149 -78
- 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 +278 -148
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +240 -127
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
- package/.framework/migrations/075_security_definer_grants.sql +157 -0
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/layout/Sidebar.tsx +300 -177
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/contexts/AppContext.tsx +5 -12
- package/.framework/src/contexts/AuthContext.tsx +17 -9
- package/.framework/src/hooks/useApps.ts +18 -22
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +74 -0
- package/.framework/src/instructions/accounts.md +97 -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 +228 -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/internal/docs-contract.md +158 -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/notifications.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 +170 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/people.md +91 -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 +184 -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/frontend-sdk.md +207 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +197 -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-collaboration.md +106 -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/document-your-app.md +117 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
- package/.framework/src/instructions/tasks/register-an-app.md +137 -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 +84 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/app-mount.ts +88 -0
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/LoginPage.tsx +3 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
- package/.framework/templates/agent/AGENTS.md +167 -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 +79 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +4 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +479 -44
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/app-mount.test.ts +80 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -0,0 +1,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,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Security Checklist
|
|
3
|
+
audience: both
|
|
4
|
+
kind: guide
|
|
5
|
+
verified_against: b3b3ae0
|
|
6
|
+
verified_on: 2026-11-01
|
|
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
|
+
## Signup & first-run surface
|
|
101
|
+
|
|
102
|
+
- **First-registrant claim window.** `handle_new_auth_user` grants
|
|
103
|
+
`system_admin` to a new registrant only when `public.people` is empty —
|
|
104
|
+
the *first* person to register claims the instance. On a public
|
|
105
|
+
deployment, run `npx spine-framework bootstrap` **before** the site is
|
|
106
|
+
reachable (or disable public signup), or the first stranger who finds
|
|
107
|
+
`/register` owns the instance. See
|
|
108
|
+
[Install and first run](../guide/01-install-and-first-run.md).
|
|
109
|
+
- **Post-bootstrap registration is safe to leave on.** With `people`
|
|
110
|
+
non-empty, subsequent registrants land unplaced/pending — no privilege.
|
|
111
|
+
Verify once by registering a second user and checking its role.
|
|
112
|
+
- **Turnstile is conditional.** `VITE_TURNSTILE_SITE_KEY` unset → auth
|
|
113
|
+
pages render with no widget; set → the widget enforces on
|
|
114
|
+
register/login. Confirm both states at least once per environment —
|
|
115
|
+
a site key set without the secret (or vice versa) silently degrades to
|
|
116
|
+
the unset behavior.
|
|
117
|
+
- **Headless access is `x-api-key`, not cookies.** `x-api-key:
|
|
118
|
+
<SPINE_API_KEY>` authenticates `/api/*` for machine callers — no
|
|
119
|
+
Turnstile, no session. Scope it (never `*:*`); the full discipline is in
|
|
120
|
+
"API key hygiene" above.
|
|
121
|
+
|
|
122
|
+
## Endpoint exposure notes
|
|
123
|
+
|
|
124
|
+
- `debug-auth` (`/api/debug-auth`) is gated by *any authenticated
|
|
125
|
+
principal*, not system-admin — it reports principal type and
|
|
126
|
+
`SET`/`NOT_SET` env flags. Diagnostic, not product surface; consider
|
|
127
|
+
whether it belongs in a hardened production deploy
|
|
128
|
+
(see [debug-auth](../reference/functions/debug-auth.md)).
|
|
129
|
+
- `/api/system` (`manifest`, `health`, `openapi`) likewise answers any
|
|
130
|
+
authenticated principal — the OpenAPI spec and manifest are not
|
|
131
|
+
admin-only information.
|
|
132
|
+
- Public endpoints (`invites`, `integration-routes`, registration flows)
|
|
133
|
+
run through `createPublicHandler` — anonymous by design; their abuse
|
|
134
|
+
controls are IP blocklist, rate limits, and Turnstile, not auth.
|
|
135
|
+
|
|
136
|
+
## Known ungated surfaces
|
|
137
|
+
|
|
138
|
+
Honest list — the audit found these; mitigate by key discipline until
|
|
139
|
+
they are tightened:
|
|
140
|
+
|
|
141
|
+
- **`logs?action=cleanup`** uses `ctx.db` with no `isSystemAdmin` check and
|
|
142
|
+
no account scoping (`.framework/functions/logs.ts`). Machine principals
|
|
143
|
+
get `adminDb` from `getPrincipalDb` — so **any valid API key can delete
|
|
144
|
+
log rows across all accounts**. Mitigation: issue machine keys narrowly,
|
|
145
|
+
watch `api_key_usage_logs`, rotate on suspicion.
|
|
146
|
+
- **`api-keys` `list`/`get`** select `*` via `adminDb` for system admins —
|
|
147
|
+
and `ctx.db` is *also* `adminDb` for machine principals, so a scoped
|
|
148
|
+
machine key reads `api_keys` metadata cross-account
|
|
149
|
+
(`api_keys` rows are stripped of `key_value`/`key_hash`, but slugs,
|
|
150
|
+
integration links, rate limits, and scopes are visible).
|
|
151
|
+
- **Function-level RLS is uneven** — mutating handlers branch
|
|
152
|
+
`isSystemAdmin ? adminDb : ctx.db`; a scoped machine key still gets
|
|
153
|
+
`adminDb`, so "machine keys are always RLS-bounded" is not a safe
|
|
154
|
+
assumption anywhere `ctx.db` is used without an explicit account filter.
|
|
155
|
+
|
|
156
|
+
## Checklist
|
|
157
|
+
|
|
158
|
+
- [ ] No `VITE_`-prefixed secret anywhere; service-role key server-only.
|
|
159
|
+
- [ ] `INTERNAL_INVOKE_SECRET`, `SCHEDULER_API_KEY`, `APP_BASE_URL` set; `SITE_URL` pinned to the real origin for CORS.
|
|
160
|
+
- [ ] Machine keys carry least-privilege scopes (never `*:*` for third parties), rate limits, and expiry where possible.
|
|
161
|
+
- [ ] `api_keys` rows audited for legacy `key_value` plaintext.
|
|
162
|
+
- [ ] Blocklist tested end-to-end (a banned IP gets 403); Netlify edge sync env vars set if edge bans are wanted.
|
|
163
|
+
- [ ] `debug-auth` and `/api/system` exposure reviewed for the environment.
|
|
164
|
+
- [ ] Machine-principal surfaces above understood — `ctx.db` = service role for machines.
|
|
165
|
+
|
|
166
|
+
## Related
|
|
167
|
+
|
|
168
|
+
- [Auth & API Keys](../concepts/auth-and-api-keys.md) · [Permissions](../concepts/permissions.md)
|
|
169
|
+
- [Errors and Envelopes](../reference/api/errors-and-envelopes.md) · [api-keys](../reference/functions/api-keys.md) · [security](../reference/functions/security.md)
|
|
170
|
+
- [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,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: People
|
|
3
|
+
audience: both
|
|
4
|
+
kind: entity
|
|
5
|
+
verified_against: bc17374
|
|
6
|
+
verified_on: 2026-11-02
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/migrations/001_core_schema.sql
|
|
9
|
+
- .framework/functions/auth.ts
|
|
10
|
+
- .framework/functions/invites.ts
|
|
11
|
+
- .framework/functions/security.ts
|
|
12
|
+
- .framework/src/contexts/AuthContext.tsx
|
|
13
|
+
- .framework/src/pages/auth/RegisterPage.tsx
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# People
|
|
17
|
+
|
|
18
|
+
## What this is for
|
|
19
|
+
|
|
20
|
+
A **person** is a human user — the `public.people` row bound to a Supabase
|
|
21
|
+
auth user. Every signed-in session resolves to exactly one person; that row
|
|
22
|
+
carries the account placement, role, and status that gate everything the
|
|
23
|
+
user can see and do. Watchers, messages, notification recipients, and
|
|
24
|
+
pipeline "who did this" all reference `people.id`.
|
|
25
|
+
|
|
26
|
+
## Fields and settings
|
|
27
|
+
|
|
28
|
+
| Column | Notes |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `auth_uid` | UNIQUE — the Supabase `auth.users.id`. `NULL` on invite stubs (person created before they register). |
|
|
31
|
+
| `email` | UNIQUE, required. Login + invite identity. |
|
|
32
|
+
| `full_name` | required display name. |
|
|
33
|
+
| `account_id` | required — the account the person is placed in. **Falsy `account_id` = "unplaced"**: the router forces `/register?resume=1` until placement completes. |
|
|
34
|
+
| `role_id` | FK → `roles`. The person's role slug drives app gating and record permissions. |
|
|
35
|
+
| `status` | default `'active'`. `'pending'` users are gated to the pending-approval page by the router. |
|
|
36
|
+
| `type_id` | required — people are typed records like everything else. |
|
|
37
|
+
| `data` | jsonb — non-system fields per the person type's `design_schema`. |
|
|
38
|
+
| `avatar_url`, `phone`, `is_active` | Profile/lifecycle flags. |
|
|
39
|
+
|
|
40
|
+
**Downstream effects**
|
|
41
|
+
|
|
42
|
+
- `useAuth().user` (`@core/contexts/AuthContext`) is derived from this row +
|
|
43
|
+
role join: `roles: string[]`, `account_id`, `is_system_admin`, `status`.
|
|
44
|
+
- The registration lifecycle: register → (email confirm) → person row with
|
|
45
|
+
`account_id` unset = unplaced → `/register?resume=1` finishes placement →
|
|
46
|
+
`status: 'pending'` requires approval → `active`.
|
|
47
|
+
- `handle_new_auth_user` grants `system_admin` only when `people` is empty —
|
|
48
|
+
the first registrant claims the instance (see
|
|
49
|
+
[Security checklist](operations/security-checklist.md)).
|
|
50
|
+
- `people.role` comes back from Supabase as object *or* array depending on
|
|
51
|
+
the join — normalize before reading `.slug`.
|
|
52
|
+
|
|
53
|
+
## Minimal configuration
|
|
54
|
+
|
|
55
|
+
People rows are created by registration/invites, not by hand — but a valid
|
|
56
|
+
person requires: `email`, `full_name`, `account_id`, `role_id`, `type_id`
|
|
57
|
+
(the person type), `status: 'active'`.
|
|
58
|
+
|
|
59
|
+
## Full configuration
|
|
60
|
+
|
|
61
|
+
Adds `avatar_url`, `phone`, `data` (type-defined fields), `app_id`
|
|
62
|
+
(which app created them), `is_active`.
|
|
63
|
+
|
|
64
|
+
## Example
|
|
65
|
+
|
|
66
|
+
An invite flow creates a person stub (`auth_uid: null`, `status: 'pending'`,
|
|
67
|
+
`account_id` + `role_id` set by the invite's `config`); when the invitee
|
|
68
|
+
registers with that email, `auth_uid` binds and they land already placed —
|
|
69
|
+
skipping the unplaced dance entirely.
|
|
70
|
+
|
|
71
|
+
## Core files
|
|
72
|
+
|
|
73
|
+
- `.framework/migrations/001_core_schema.sql` — table + constraints
|
|
74
|
+
- `.framework/functions/auth.ts` — session → person + role resolution
|
|
75
|
+
- `.framework/functions/invites.ts` — invite stubs and placement config
|
|
76
|
+
- `.framework/src/contexts/AuthContext.tsx` — client `useAuth` shape
|
|
77
|
+
- `.framework/src/pages/auth/RegisterPage.tsx` — placement/resume flow
|
|
78
|
+
|
|
79
|
+
## Agent notes
|
|
80
|
+
|
|
81
|
+
- Never `INSERT` a person as a substitute for registration — `auth_uid`
|
|
82
|
+
binding and account placement happen through auth/invite paths.
|
|
83
|
+
- `status: 'pending'` + truthy `account_id` → pending-approval gate;
|
|
84
|
+
falsy `account_id` → resume-registration routing. Different fixes.
|
|
85
|
+
- `person_id` is the FK used by `watchers`, `messages`, `notifications`,
|
|
86
|
+
`item_progress` — it's `people.id`, never `auth_uid`.
|
|
87
|
+
- `onAuthStateChange` callbacks must never await Supabase calls —
|
|
88
|
+
auth-js awaits listeners inside `initializePromise`, so
|
|
89
|
+
`fetchUserContext → apiFetch → getAuthHeaders → getSession` deadlocks
|
|
90
|
+
until `SESSION_TIMEOUT_MS` (15 s of spinner on every expired-session
|
|
91
|
+
visit). Defer the work (`setTimeout`) inside the callback.
|