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,93 @@
|
|
|
1
|
+
-- Migration 067: stale-gated detached-driver steal
|
|
2
|
+
--
|
|
3
|
+
-- 066 let a request-bound driver steal any live 'detached:' lease
|
|
4
|
+
-- unconditionally. That guaranteed a mid-turn displacement on every
|
|
5
|
+
-- guided run (triage kick claims first, the client's episode POST steals
|
|
6
|
+
-- seconds later), fencing a healthy driver and leaving research_state
|
|
7
|
+
-- mid-flight — the residual duplicate-narration class. Steal is a
|
|
8
|
+
-- recovery path for STALLED background work, so it now requires the
|
|
9
|
+
-- holder's heartbeat (updated_at, renewed by heartbeat_episode_lease)
|
|
10
|
+
-- to be older than p_stale_seconds. Healthy detached drivers are
|
|
11
|
+
-- refused 'held' like any other live driver.
|
|
12
|
+
|
|
13
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/067_episode_lease_stale_steal.sql:module:migration" type="migration" identity="episode-lease-stale-steal" parent="~/.framework/migrations/067_episode_lease_stale_steal.sql"]
|
|
14
|
+
-- CIL[DOC-MACRO purpose="Gate p_steal_detached on the holder's heartbeat age so request drivers only displace stalled background work"]
|
|
15
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Redefine acquire_episode_lease with a p_stale_seconds parameter; detach-prefix holders are stealable only when updated_at exceeds the staleness window"]
|
|
16
|
+
-- CIL[DOC-CONTEXT module="episode-concurrency" layer="schema" related_files=".framework/functions/_shared/agent-runner.ts,.framework/functions/ai-agents.ts"]
|
|
17
|
+
-- CIL[DOC-CONTRACT inputs="p_thread_id uuid, p_account_id uuid, p_driver_id text, p_ttl_seconds int default 90, p_steal_detached bool default false, p_stale_seconds int default 30" outputs="jsonb verdict {outcome: claimed|held, lease_token?, holder_driver_id?}" errors="none — fail-open verdict" side_effects="inserts or updates episode_leases"]
|
|
18
|
+
-- CIL[DOC-MICRO logic_summary="Identical to 066 except the steal branch additionally requires v_lease.updated_at to be older than p_stale_seconds; heartbeat renewals keep a healthy detached driver unstealable" notable_decisions="30s default bounds takeover latency well under the 90s TTL while tolerating slow single turns between heartbeats"]
|
|
19
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.92" flags="none"]
|
|
20
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
21
|
+
-- CIL[DOC-AUDIENCE audience="system-admin"]
|
|
22
|
+
-- CIL[DOC-VECTOR type="context" content="Third lease RPC revision: 063 atomic claim, 066 detached steal, 067 stale-gated steal" status="ready"]
|
|
23
|
+
-- CIL[END stable_id="~/.framework/migrations/067_episode_lease_stale_steal.sql:module:migration"]
|
|
24
|
+
|
|
25
|
+
DROP FUNCTION IF EXISTS public.acquire_episode_lease(uuid, uuid, text, integer, boolean);
|
|
26
|
+
|
|
27
|
+
CREATE FUNCTION public.acquire_episode_lease(
|
|
28
|
+
p_thread_id uuid,
|
|
29
|
+
p_account_id uuid,
|
|
30
|
+
p_driver_id text,
|
|
31
|
+
p_ttl_seconds integer DEFAULT 90,
|
|
32
|
+
p_steal_detached boolean DEFAULT false,
|
|
33
|
+
p_stale_seconds integer DEFAULT 30
|
|
34
|
+
)
|
|
35
|
+
RETURNS jsonb
|
|
36
|
+
LANGUAGE plpgsql
|
|
37
|
+
SECURITY DEFINER
|
|
38
|
+
SET search_path = public
|
|
39
|
+
AS $$
|
|
40
|
+
DECLARE
|
|
41
|
+
v_lease public.episode_leases%ROWTYPE;
|
|
42
|
+
v_new_token uuid := gen_random_uuid();
|
|
43
|
+
BEGIN
|
|
44
|
+
INSERT INTO public.episode_leases (thread_id, account_id, driver_id, lease_token, expires_at)
|
|
45
|
+
VALUES (
|
|
46
|
+
p_thread_id,
|
|
47
|
+
p_account_id,
|
|
48
|
+
p_driver_id,
|
|
49
|
+
v_new_token,
|
|
50
|
+
now() + make_interval(secs => p_ttl_seconds)
|
|
51
|
+
)
|
|
52
|
+
ON CONFLICT (thread_id) DO NOTHING;
|
|
53
|
+
|
|
54
|
+
IF FOUND THEN
|
|
55
|
+
RETURN jsonb_build_object('outcome', 'claimed', 'lease_token', v_new_token);
|
|
56
|
+
END IF;
|
|
57
|
+
|
|
58
|
+
SELECT * INTO v_lease
|
|
59
|
+
FROM public.episode_leases
|
|
60
|
+
WHERE thread_id = p_thread_id
|
|
61
|
+
FOR UPDATE;
|
|
62
|
+
|
|
63
|
+
-- Held: a different live driver owns the episode. Steal is allowed
|
|
64
|
+
-- only for a 'detached:'-prefixed holder whose heartbeat has gone
|
|
65
|
+
-- stale — a healthy background driver is refused like anyone else so
|
|
66
|
+
-- request claims stop displacing mid-turn work.
|
|
67
|
+
IF v_lease.driver_id <> p_driver_id
|
|
68
|
+
AND v_lease.expires_at > now()
|
|
69
|
+
AND NOT (p_steal_detached
|
|
70
|
+
AND v_lease.driver_id LIKE 'detached:%'
|
|
71
|
+
AND v_lease.updated_at < now() - make_interval(secs => p_stale_seconds)) THEN
|
|
72
|
+
RETURN jsonb_build_object(
|
|
73
|
+
'outcome', 'held',
|
|
74
|
+
'holder_driver_id', v_lease.driver_id
|
|
75
|
+
);
|
|
76
|
+
END IF;
|
|
77
|
+
|
|
78
|
+
UPDATE public.episode_leases SET
|
|
79
|
+
driver_id = p_driver_id,
|
|
80
|
+
lease_token = v_new_token,
|
|
81
|
+
account_id = p_account_id,
|
|
82
|
+
expires_at = now() + make_interval(secs => p_ttl_seconds),
|
|
83
|
+
updated_at = now()
|
|
84
|
+
WHERE thread_id = p_thread_id;
|
|
85
|
+
|
|
86
|
+
RETURN jsonb_build_object('outcome', 'claimed', 'lease_token', v_new_token);
|
|
87
|
+
END;
|
|
88
|
+
$$;
|
|
89
|
+
|
|
90
|
+
REVOKE ALL ON FUNCTION public.acquire_episode_lease(uuid, uuid, text, integer, boolean, integer) FROM PUBLIC;
|
|
91
|
+
REVOKE ALL ON FUNCTION public.acquire_episode_lease(uuid, uuid, text, integer, boolean, integer) FROM anon;
|
|
92
|
+
REVOKE ALL ON FUNCTION public.acquire_episode_lease(uuid, uuid, text, integer, boolean, integer) FROM authenticated;
|
|
93
|
+
GRANT EXECUTE ON FUNCTION public.acquire_episode_lease(uuid, uuid, text, integer, boolean, integer) TO service_role;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
-- Migration 068: bootstrap the RPCs system-cron expects
|
|
2
|
+
--
|
|
3
|
+
-- system-cron calls nine Postgres functions that were documented as
|
|
4
|
+
-- "bootstrapped by framework migrations" but never created — every tick
|
|
5
|
+
-- hard-failed on get_due_schedules with
|
|
6
|
+
-- "Could not find the function public.get_due_schedules(p_now) in the
|
|
7
|
+
-- schema cache"
|
|
8
|
+
-- This migration creates the missing RPCs against the existing
|
|
9
|
+
-- schedules / timers / schedule_executions / pipeline_executions / logs
|
|
10
|
+
-- tables.
|
|
11
|
+
--
|
|
12
|
+
-- Honest limits (the tables are half-designed — schedules has no
|
|
13
|
+
-- config/delegated_scopes columns and there is no cron-expression
|
|
14
|
+
-- evaluator in the DB):
|
|
15
|
+
-- * get_due_schedules returns delegated_scopes=NULL and config='{}'
|
|
16
|
+
-- (the cron handler already tolerates both).
|
|
17
|
+
-- * update_schedule_after_run reschedules only the every-N-minutes
|
|
18
|
+
-- cron form ('*/N * * * *'); anything else gets next_run_at=NULL so
|
|
19
|
+
-- a schedule can never re-fire every tick forever.
|
|
20
|
+
-- * update_timer_after_run reschedules when
|
|
21
|
+
-- config->>'interval_minutes' is a positive number, else NULL.
|
|
22
|
+
|
|
23
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/068_cron_schedule_timer_rpcs.sql:module:migration" type="migration" identity="cron-schedule-timer-rpcs" parent="~/.framework/migrations/068_cron_schedule_timer_rpcs.sql"]
|
|
24
|
+
-- CIL[DOC-MACRO purpose="Create the nine Postgres RPCs system-cron invokes for schedules, timers, threshold metrics and log cleanup"]
|
|
25
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Provide get_due_schedules, validate_schedule_creator, update_schedule_after_run, get_due_timers, update_timer_after_run, get_error_rate, get_latency_percentiles, get_pipeline_stats, cleanup_old_logs"]
|
|
26
|
+
-- CIL[DOC-CONTEXT module="system-cron" layer="schema" related_files=".framework/functions/system-cron.ts"]
|
|
27
|
+
-- CIL[DOC-CONTRACT inputs="timestamptz/uuid/bool/int params per function" outputs="setof rows or scalars matching the shapes system-cron reads" errors="none — SECURITY DEFINER, stable where read-only" side_effects="update_schedule_after_run/update_timer_after_run mutate schedule/timer rows; cleanup_old_logs deletes old logs"]
|
|
28
|
+
-- CIL[DOC-MICRO logic_summary="Simple SELECT/UPDATE/DELETE implementations; cron reschedule limited to '*/N * * * *', timers honor config->>'interval_minutes', everything else next_*=NULL to prevent infinite re-fire" notable_decisions="No speculative cron parser — a NULL next_run_at is a safe one-shot stop until the feature gets a real scheduler model"]
|
|
29
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
30
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
31
|
+
-- CIL[DOC-AUDIENCE audience="system-admin"]
|
|
32
|
+
-- CIL[END stable_id="~/.framework/migrations/068_cron_schedule_timer_rpcs.sql:module:migration"]
|
|
33
|
+
|
|
34
|
+
-- ── schedules ─────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
CREATE OR REPLACE FUNCTION public.get_due_schedules(p_now timestamptz)
|
|
37
|
+
RETURNS TABLE (
|
|
38
|
+
schedule_id uuid,
|
|
39
|
+
action_id uuid,
|
|
40
|
+
account_id uuid,
|
|
41
|
+
machine_principal_id uuid,
|
|
42
|
+
delegated_scopes jsonb,
|
|
43
|
+
config jsonb
|
|
44
|
+
)
|
|
45
|
+
LANGUAGE sql
|
|
46
|
+
STABLE
|
|
47
|
+
SECURITY DEFINER
|
|
48
|
+
SET search_path = public
|
|
49
|
+
AS $$
|
|
50
|
+
SELECT
|
|
51
|
+
s.id,
|
|
52
|
+
s.action_id,
|
|
53
|
+
s.account_id,
|
|
54
|
+
s.machine_principal_id,
|
|
55
|
+
NULL::jsonb,
|
|
56
|
+
'{}'::jsonb
|
|
57
|
+
FROM public.schedules s
|
|
58
|
+
WHERE s.is_active
|
|
59
|
+
AND s.next_run_at IS NOT NULL
|
|
60
|
+
AND s.next_run_at <= p_now
|
|
61
|
+
$$;
|
|
62
|
+
|
|
63
|
+
CREATE OR REPLACE FUNCTION public.validate_schedule_creator(p_schedule_id uuid)
|
|
64
|
+
RETURNS TABLE (is_valid boolean, error_message text)
|
|
65
|
+
LANGUAGE sql
|
|
66
|
+
STABLE
|
|
67
|
+
SECURITY DEFINER
|
|
68
|
+
SET search_path = public
|
|
69
|
+
AS $$
|
|
70
|
+
SELECT
|
|
71
|
+
EXISTS (
|
|
72
|
+
SELECT 1 FROM public.schedules s
|
|
73
|
+
WHERE s.id = p_schedule_id AND s.is_active
|
|
74
|
+
),
|
|
75
|
+
CASE
|
|
76
|
+
WHEN NOT EXISTS (SELECT 1 FROM public.schedules s WHERE s.id = p_schedule_id)
|
|
77
|
+
THEN 'schedule not found'
|
|
78
|
+
WHEN NOT EXISTS (SELECT 1 FROM public.schedules s WHERE s.id = p_schedule_id AND s.is_active)
|
|
79
|
+
THEN 'schedule is not active'
|
|
80
|
+
ELSE NULL
|
|
81
|
+
END
|
|
82
|
+
$$;
|
|
83
|
+
|
|
84
|
+
CREATE OR REPLACE FUNCTION public.update_schedule_after_run(
|
|
85
|
+
p_schedule_id uuid,
|
|
86
|
+
p_success boolean,
|
|
87
|
+
p_error_message text
|
|
88
|
+
)
|
|
89
|
+
RETURNS void
|
|
90
|
+
LANGUAGE plpgsql
|
|
91
|
+
SECURITY DEFINER
|
|
92
|
+
SET search_path = public
|
|
93
|
+
AS $$
|
|
94
|
+
DECLARE
|
|
95
|
+
v_cron text;
|
|
96
|
+
v_minutes integer;
|
|
97
|
+
BEGIN
|
|
98
|
+
SELECT cron_expression INTO v_cron FROM public.schedules WHERE id = p_schedule_id;
|
|
99
|
+
|
|
100
|
+
-- Only the every-N-minutes form is reschedulable here; a full cron
|
|
101
|
+
-- evaluator does not exist in the DB. Anything else becomes a
|
|
102
|
+
-- one-shot (NULL next_run_at) rather than re-firing every tick.
|
|
103
|
+
v_minutes := NULL;
|
|
104
|
+
IF v_cron ~ '^\*/(\d+) \* \* \* \*$' THEN
|
|
105
|
+
v_minutes := substring(v_cron FROM '^\*/(\d+)')::integer;
|
|
106
|
+
END IF;
|
|
107
|
+
|
|
108
|
+
UPDATE public.schedules
|
|
109
|
+
SET
|
|
110
|
+
last_run_at = now(),
|
|
111
|
+
next_run_at = CASE
|
|
112
|
+
WHEN v_minutes IS NOT NULL THEN now() + make_interval(mins => v_minutes)
|
|
113
|
+
ELSE NULL
|
|
114
|
+
END,
|
|
115
|
+
updated_at = now()
|
|
116
|
+
WHERE id = p_schedule_id;
|
|
117
|
+
END;
|
|
118
|
+
$$;
|
|
119
|
+
|
|
120
|
+
-- ── timers ────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
CREATE OR REPLACE FUNCTION public.get_due_timers(p_now timestamptz)
|
|
123
|
+
RETURNS TABLE (
|
|
124
|
+
timer_id uuid,
|
|
125
|
+
pipeline_id uuid,
|
|
126
|
+
account_id uuid,
|
|
127
|
+
app_id uuid,
|
|
128
|
+
name text,
|
|
129
|
+
execution_count integer,
|
|
130
|
+
created_by uuid
|
|
131
|
+
)
|
|
132
|
+
LANGUAGE sql
|
|
133
|
+
STABLE
|
|
134
|
+
SECURITY DEFINER
|
|
135
|
+
SET search_path = public
|
|
136
|
+
AS $$
|
|
137
|
+
SELECT
|
|
138
|
+
t.id,
|
|
139
|
+
t.pipeline_id,
|
|
140
|
+
t.account_id,
|
|
141
|
+
t.app_id,
|
|
142
|
+
t.name,
|
|
143
|
+
COALESCE(t.execution_count, 0),
|
|
144
|
+
t.created_by
|
|
145
|
+
FROM public.timers t
|
|
146
|
+
WHERE t.is_active
|
|
147
|
+
AND t.pipeline_id IS NOT NULL
|
|
148
|
+
AND t.next_execution IS NOT NULL
|
|
149
|
+
AND t.next_execution <= p_now
|
|
150
|
+
$$;
|
|
151
|
+
|
|
152
|
+
CREATE OR REPLACE FUNCTION public.update_timer_after_run(
|
|
153
|
+
p_timer_id uuid,
|
|
154
|
+
p_success boolean,
|
|
155
|
+
p_error_message text
|
|
156
|
+
)
|
|
157
|
+
RETURNS void
|
|
158
|
+
LANGUAGE plpgsql
|
|
159
|
+
SECURITY DEFINER
|
|
160
|
+
SET search_path = public
|
|
161
|
+
AS $$
|
|
162
|
+
DECLARE
|
|
163
|
+
v_interval numeric;
|
|
164
|
+
BEGIN
|
|
165
|
+
SELECT CASE
|
|
166
|
+
WHEN config->>'interval_minutes' ~ '^\d+(\.\d+)?$'
|
|
167
|
+
THEN (config->>'interval_minutes')::numeric
|
|
168
|
+
ELSE NULL
|
|
169
|
+
END INTO v_interval
|
|
170
|
+
FROM public.timers WHERE id = p_timer_id;
|
|
171
|
+
|
|
172
|
+
UPDATE public.timers
|
|
173
|
+
SET
|
|
174
|
+
last_execution = now(),
|
|
175
|
+
execution_count = COALESCE(execution_count, 0) + 1,
|
|
176
|
+
success_count = COALESCE(success_count, 0) + CASE WHEN p_success THEN 1 ELSE 0 END,
|
|
177
|
+
failure_count = COALESCE(failure_count, 0) + CASE WHEN p_success THEN 0 ELSE 1 END,
|
|
178
|
+
next_execution = CASE
|
|
179
|
+
WHEN v_interval IS NOT NULL AND v_interval > 0
|
|
180
|
+
THEN now() + make_interval(mins => v_interval::double precision)
|
|
181
|
+
ELSE NULL
|
|
182
|
+
END,
|
|
183
|
+
updated_at = now()
|
|
184
|
+
WHERE id = p_timer_id;
|
|
185
|
+
END;
|
|
186
|
+
$$;
|
|
187
|
+
|
|
188
|
+
-- ── threshold metrics ─────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
CREATE OR REPLACE FUNCTION public.get_error_rate(p_window_minutes integer)
|
|
191
|
+
RETURNS TABLE (error_rate numeric)
|
|
192
|
+
LANGUAGE sql
|
|
193
|
+
STABLE
|
|
194
|
+
SECURITY DEFINER
|
|
195
|
+
SET search_path = public
|
|
196
|
+
AS $$
|
|
197
|
+
SELECT CASE WHEN count(*) = 0 THEN 0
|
|
198
|
+
ELSE round(count(*) FILTER (WHERE outcome = 'failure' OR level = 'error')::numeric * 100 / count(*), 2)
|
|
199
|
+
END
|
|
200
|
+
FROM public.logs
|
|
201
|
+
WHERE created_at >= now() - make_interval(mins => p_window_minutes)
|
|
202
|
+
$$;
|
|
203
|
+
|
|
204
|
+
CREATE OR REPLACE FUNCTION public.get_latency_percentiles(p_window_minutes integer)
|
|
205
|
+
RETURNS TABLE (p50 numeric, p95 numeric, p99 numeric)
|
|
206
|
+
LANGUAGE sql
|
|
207
|
+
STABLE
|
|
208
|
+
SECURITY DEFINER
|
|
209
|
+
SET search_path = public
|
|
210
|
+
AS $$
|
|
211
|
+
SELECT
|
|
212
|
+
percentile_cont(0.50) WITHIN GROUP (ORDER BY duration_ms)::numeric,
|
|
213
|
+
percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms)::numeric,
|
|
214
|
+
percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)::numeric
|
|
215
|
+
FROM public.logs
|
|
216
|
+
WHERE created_at >= now() - make_interval(mins => p_window_minutes)
|
|
217
|
+
AND duration_ms IS NOT NULL
|
|
218
|
+
$$;
|
|
219
|
+
|
|
220
|
+
CREATE OR REPLACE FUNCTION public.get_pipeline_stats(p_window_minutes integer)
|
|
221
|
+
RETURNS TABLE (total_success bigint, total_failure bigint)
|
|
222
|
+
LANGUAGE sql
|
|
223
|
+
STABLE
|
|
224
|
+
SECURITY DEFINER
|
|
225
|
+
SET search_path = public
|
|
226
|
+
AS $$
|
|
227
|
+
SELECT
|
|
228
|
+
count(*) FILTER (WHERE status = 'completed'),
|
|
229
|
+
count(*) FILTER (WHERE status = 'failed')
|
|
230
|
+
FROM public.pipeline_executions
|
|
231
|
+
WHERE created_at >= now() - make_interval(mins => p_window_minutes)
|
|
232
|
+
$$;
|
|
233
|
+
|
|
234
|
+
-- ── log retention ─────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
CREATE OR REPLACE FUNCTION public.cleanup_old_logs(p_retention_days integer DEFAULT 90)
|
|
237
|
+
RETURNS TABLE (deleted_count bigint)
|
|
238
|
+
LANGUAGE plpgsql
|
|
239
|
+
SECURITY DEFINER
|
|
240
|
+
SET search_path = public
|
|
241
|
+
AS $$
|
|
242
|
+
BEGIN
|
|
243
|
+
RETURN QUERY
|
|
244
|
+
WITH deleted AS (
|
|
245
|
+
DELETE FROM public.logs
|
|
246
|
+
WHERE created_at < now() - make_interval(days => p_retention_days)
|
|
247
|
+
RETURNING 1
|
|
248
|
+
)
|
|
249
|
+
SELECT count(*) FROM deleted;
|
|
250
|
+
END;
|
|
251
|
+
$$;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.get_log_stats(
|
|
2
|
+
p_account_id uuid,
|
|
3
|
+
p_level text DEFAULT NULL::text,
|
|
4
|
+
p_source text DEFAULT NULL::text,
|
|
5
|
+
p_source_type text DEFAULT NULL::text,
|
|
6
|
+
p_source_id text DEFAULT NULL::text,
|
|
7
|
+
p_person_id text DEFAULT NULL::text,
|
|
8
|
+
p_query text DEFAULT NULL::text,
|
|
9
|
+
p_date_from timestamp with time zone DEFAULT NULL::timestamp with time zone,
|
|
10
|
+
p_date_to timestamp with time zone DEFAULT NULL::timestamp with time zone
|
|
11
|
+
)
|
|
12
|
+
RETURNS TABLE(total bigint, error_count bigint, user_action_count bigint, event_type_count bigint, levels text[], sources text[], source_types text[])
|
|
13
|
+
LANGUAGE plpgsql
|
|
14
|
+
AS $function$
|
|
15
|
+
DECLARE
|
|
16
|
+
v_from timestamptz := COALESCE(p_date_from, now() - interval '7 days');
|
|
17
|
+
v_total bigint;
|
|
18
|
+
v_error_count bigint;
|
|
19
|
+
v_user_action_count bigint;
|
|
20
|
+
v_event_type_count bigint;
|
|
21
|
+
v_levels text[];
|
|
22
|
+
v_sources text[];
|
|
23
|
+
v_source_types text[];
|
|
24
|
+
BEGIN
|
|
25
|
+
SELECT
|
|
26
|
+
count(*),
|
|
27
|
+
count(*) FILTER (WHERE level ILIKE '%error%'),
|
|
28
|
+
count(*) FILTER (WHERE person_id IS NOT NULL),
|
|
29
|
+
count(DISTINCT level)
|
|
30
|
+
INTO v_total, v_error_count, v_user_action_count, v_event_type_count
|
|
31
|
+
FROM public.logs
|
|
32
|
+
WHERE account_id = p_account_id
|
|
33
|
+
AND created_at >= v_from
|
|
34
|
+
AND (p_level IS NULL OR level = p_level)
|
|
35
|
+
AND (p_source IS NULL OR source = p_source)
|
|
36
|
+
AND (p_source_type IS NULL OR source_type = p_source_type)
|
|
37
|
+
AND (p_source_id IS NULL OR source_id = p_source_id::uuid)
|
|
38
|
+
AND (p_person_id IS NULL OR person_id = p_person_id::uuid)
|
|
39
|
+
AND (p_query IS NULL OR message ILIKE '%' || p_query || '%')
|
|
40
|
+
AND (p_date_to IS NULL OR created_at <= p_date_to);
|
|
41
|
+
|
|
42
|
+
SELECT
|
|
43
|
+
coalesce(array_agg(DISTINCT level ORDER BY level) FILTER (WHERE level IS NOT NULL), ARRAY[]::text[]),
|
|
44
|
+
coalesce(array_agg(DISTINCT source ORDER BY source) FILTER (WHERE source IS NOT NULL), ARRAY[]::text[]),
|
|
45
|
+
coalesce(array_agg(DISTINCT source_type ORDER BY source_type) FILTER (WHERE source_type IS NOT NULL), ARRAY[]::text[])
|
|
46
|
+
INTO v_levels, v_sources, v_source_types
|
|
47
|
+
FROM public.logs
|
|
48
|
+
WHERE account_id = p_account_id
|
|
49
|
+
AND created_at >= v_from
|
|
50
|
+
AND (p_date_to IS NULL OR created_at <= p_date_to);
|
|
51
|
+
|
|
52
|
+
RETURN QUERY SELECT v_total, v_error_count, v_user_action_count, v_event_type_count, v_levels, v_sources, v_source_types;
|
|
53
|
+
END;
|
|
54
|
+
$function$;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
-- Migration 070: bootstrap the RPCs the types/apps handlers expect
|
|
2
|
+
--
|
|
3
|
+
-- types.ts getSchema (?action=schema) and apps.ts getSchema /
|
|
4
|
+
-- checkAvailability / updateVersion (?action=schema|available|version)
|
|
5
|
+
-- call four Postgres functions that existed in the pre-consolidation v2
|
|
6
|
+
-- schema (v2-core/migrations/010_apps.sql, 004_types.sql +
|
|
7
|
+
-- 049_schema_driven_permissions.sql) but were never ported into
|
|
8
|
+
-- .framework/migrations — each action fails at runtime on a stock
|
|
9
|
+
-- install with "Could not find the function public.<name>(...) in the
|
|
10
|
+
-- schema cache". This migration recreates them against the public
|
|
11
|
+
-- schema.
|
|
12
|
+
--
|
|
13
|
+
-- Semantics are ported from the recovered v2 definitions:
|
|
14
|
+
-- * get_type_schema returns the matching active type's design_schema
|
|
15
|
+
-- ('{}' when none). An app_id argument prefers that app's row and
|
|
16
|
+
-- falls back to the global (app_id IS NULL) type — the documented
|
|
17
|
+
-- "app-specific fallback to system" behavior.
|
|
18
|
+
-- * get_app_schema returns a jsonb projection of the active app row
|
|
19
|
+
-- ('{}' when none), extended with the required_roles / route_prefix /
|
|
20
|
+
-- renderer columns added since v2.
|
|
21
|
+
-- * is_app_available returns true when an active app with the slug is
|
|
22
|
+
-- available to the account under the same ownership rule
|
|
23
|
+
-- get_account_apps uses (is_system OR owner_account_id IS NULL OR
|
|
24
|
+
-- owner_account_id = account_id).
|
|
25
|
+
-- * update_app_version bumps apps.version + updated_at and returns
|
|
26
|
+
-- void — the handler only checks for an error.
|
|
27
|
+
--
|
|
28
|
+
-- All four run as SECURITY INVOKER (the v2 default): callers go through
|
|
29
|
+
-- ctx.db, so human callers keep RLS scoping (types are world-readable via
|
|
30
|
+
-- types_read; apps via apps_access) and machine principals bypass RLS as
|
|
31
|
+
-- service_role. GRANTs match the sibling RPC convention in
|
|
32
|
+
-- 004_core_rpc_and_broker.sql.
|
|
33
|
+
|
|
34
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/070_app_type_schema_rpcs.sql:module:migration" type="migration" identity="app-type-schema-rpcs" parent="~/.framework/migrations/070_app_type_schema_rpcs.sql"]
|
|
35
|
+
-- CIL[DOC-MACRO purpose="Create the four Postgres RPCs the types and apps Netlify handlers invoke: get_type_schema, get_app_schema, is_app_available, update_app_version"]
|
|
36
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Provide schema lookup, app-schema projection, account-scoped app availability, and app version bump functions missing since the v2-to-public consolidation"]
|
|
37
|
+
-- CIL[DOC-CONTEXT module="functions" layer="schema" related_files=".framework/functions/types.ts,.framework/functions/apps.ts,.framework/migrations/013_get_account_apps_role_filter.sql"]
|
|
38
|
+
-- CIL[DOC-CONTRACT inputs="text/uuid params matching the .rpc named args in types.ts and apps.ts" outputs="jsonb scalars, boolean, void matching the shapes the handlers read" errors="none — SECURITY INVOKER relies on caller RLS" side_effects="update_app_version mutates public.apps version and updated_at"]
|
|
39
|
+
-- CIL[DOC-MICRO logic_summary="Ports the recovered v2-core function bodies to the public schema; get_type_schema reads design_schema with app-specific fallback to the global type; is_app_available reuses the get_account_apps ownership rule including NULL-owner apps" notable_decisions="SECURITY INVOKER rather than DEFINER so human callers keep RLS scoping and no caller-supplied privilege claims are trusted"]
|
|
40
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
41
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
42
|
+
-- CIL[DOC-AUDIENCE audience="framework_developers"]
|
|
43
|
+
-- CIL[END stable_id="~/.framework/migrations/070_app_type_schema_rpcs.sql:module:migration"]
|
|
44
|
+
|
|
45
|
+
-- ── types ─────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
CREATE OR REPLACE FUNCTION public.get_type_schema(
|
|
48
|
+
kind text,
|
|
49
|
+
slug text,
|
|
50
|
+
app_id uuid DEFAULT NULL
|
|
51
|
+
)
|
|
52
|
+
RETURNS jsonb
|
|
53
|
+
LANGUAGE plpgsql
|
|
54
|
+
STABLE
|
|
55
|
+
AS $$
|
|
56
|
+
DECLARE
|
|
57
|
+
type_schema jsonb;
|
|
58
|
+
BEGIN
|
|
59
|
+
SELECT t.design_schema INTO type_schema
|
|
60
|
+
FROM public.types t
|
|
61
|
+
WHERE t.kind = get_type_schema.kind
|
|
62
|
+
AND t.slug = get_type_schema.slug
|
|
63
|
+
AND t.is_active = true
|
|
64
|
+
AND (t.app_id = get_type_schema.app_id OR t.app_id IS NULL)
|
|
65
|
+
ORDER BY t.app_id NULLS LAST -- prefer app-specific types over global
|
|
66
|
+
LIMIT 1;
|
|
67
|
+
|
|
68
|
+
RETURN COALESCE(type_schema, '{}'::jsonb);
|
|
69
|
+
END;
|
|
70
|
+
$$;
|
|
71
|
+
|
|
72
|
+
GRANT EXECUTE ON FUNCTION public.get_type_schema(text, text, uuid) TO authenticated;
|
|
73
|
+
GRANT EXECUTE ON FUNCTION public.get_type_schema(text, text, uuid) TO service_role;
|
|
74
|
+
|
|
75
|
+
COMMENT ON FUNCTION public.get_type_schema(text, text, uuid) IS 'Get type design_schema by kind+slug, app-specific with fallback to global';
|
|
76
|
+
|
|
77
|
+
-- ── apps ──────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
CREATE OR REPLACE FUNCTION public.get_app_schema(app_slug text)
|
|
80
|
+
RETURNS jsonb
|
|
81
|
+
LANGUAGE plpgsql
|
|
82
|
+
STABLE
|
|
83
|
+
AS $$
|
|
84
|
+
DECLARE
|
|
85
|
+
app_schema jsonb;
|
|
86
|
+
BEGIN
|
|
87
|
+
SELECT jsonb_build_object(
|
|
88
|
+
'id', a.id,
|
|
89
|
+
'slug', a.slug,
|
|
90
|
+
'name', a.name,
|
|
91
|
+
'description', a.description,
|
|
92
|
+
'icon', a.icon,
|
|
93
|
+
'color', a.color,
|
|
94
|
+
'version', a.version,
|
|
95
|
+
'app_type', a.app_type,
|
|
96
|
+
'source', a.source,
|
|
97
|
+
'config', a.config,
|
|
98
|
+
'nav_items', a.nav_items,
|
|
99
|
+
'min_role', a.min_role,
|
|
100
|
+
'required_roles', a.required_roles,
|
|
101
|
+
'integration_deps', a.integration_deps,
|
|
102
|
+
'metadata', a.metadata,
|
|
103
|
+
'route_prefix', a.route_prefix,
|
|
104
|
+
'renderer', a.renderer
|
|
105
|
+
) INTO app_schema
|
|
106
|
+
FROM public.apps a
|
|
107
|
+
WHERE a.slug = get_app_schema.app_slug
|
|
108
|
+
AND a.is_active = true;
|
|
109
|
+
|
|
110
|
+
RETURN COALESCE(app_schema, '{}'::jsonb);
|
|
111
|
+
END;
|
|
112
|
+
$$;
|
|
113
|
+
|
|
114
|
+
GRANT EXECUTE ON FUNCTION public.get_app_schema(text) TO authenticated;
|
|
115
|
+
GRANT EXECUTE ON FUNCTION public.get_app_schema(text) TO service_role;
|
|
116
|
+
|
|
117
|
+
COMMENT ON FUNCTION public.get_app_schema(text) IS 'Get full app schema by slug';
|
|
118
|
+
|
|
119
|
+
CREATE OR REPLACE FUNCTION public.is_app_available(
|
|
120
|
+
app_slug text,
|
|
121
|
+
account_id uuid
|
|
122
|
+
)
|
|
123
|
+
RETURNS boolean
|
|
124
|
+
LANGUAGE plpgsql
|
|
125
|
+
STABLE
|
|
126
|
+
AS $$
|
|
127
|
+
BEGIN
|
|
128
|
+
RETURN EXISTS (
|
|
129
|
+
SELECT 1
|
|
130
|
+
FROM public.apps a
|
|
131
|
+
WHERE a.slug = is_app_available.app_slug
|
|
132
|
+
AND a.is_active = true
|
|
133
|
+
AND (
|
|
134
|
+
a.is_system = true
|
|
135
|
+
OR a.owner_account_id IS NULL
|
|
136
|
+
OR a.owner_account_id = is_app_available.account_id
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
END;
|
|
140
|
+
$$;
|
|
141
|
+
|
|
142
|
+
GRANT EXECUTE ON FUNCTION public.is_app_available(text, uuid) TO authenticated;
|
|
143
|
+
GRANT EXECUTE ON FUNCTION public.is_app_available(text, uuid) TO service_role;
|
|
144
|
+
|
|
145
|
+
COMMENT ON FUNCTION public.is_app_available(text, uuid) IS 'Check if app is available to account';
|
|
146
|
+
|
|
147
|
+
CREATE OR REPLACE FUNCTION public.update_app_version(
|
|
148
|
+
app_id uuid,
|
|
149
|
+
new_version text
|
|
150
|
+
)
|
|
151
|
+
RETURNS void
|
|
152
|
+
LANGUAGE plpgsql
|
|
153
|
+
AS $$
|
|
154
|
+
BEGIN
|
|
155
|
+
UPDATE public.apps
|
|
156
|
+
SET
|
|
157
|
+
version = update_app_version.new_version,
|
|
158
|
+
updated_at = now()
|
|
159
|
+
WHERE id = update_app_version.app_id;
|
|
160
|
+
END;
|
|
161
|
+
$$;
|
|
162
|
+
|
|
163
|
+
GRANT EXECUTE ON FUNCTION public.update_app_version(uuid, text) TO authenticated;
|
|
164
|
+
GRANT EXECUTE ON FUNCTION public.update_app_version(uuid, text) TO service_role;
|
|
165
|
+
|
|
166
|
+
COMMENT ON FUNCTION public.update_app_version(uuid, text) IS 'Update app version';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
-- Migration 071: F-056 — make timers created via the API/admin UI fire.
|
|
2
|
+
--
|
|
3
|
+
-- Two halves of the fix:
|
|
4
|
+
-- 1. timers.ts now writes next_execution server-side on
|
|
5
|
+
-- create/update/activate (068's get_due_timers gates on
|
|
6
|
+
-- next_execution IS NOT NULL and nothing else ever set it).
|
|
7
|
+
-- 2. This file: update_timer_after_run previously rescheduled only
|
|
8
|
+
-- from config->>'interval_minutes', but TimerDetailPage seeded
|
|
9
|
+
-- config.interval_seconds — UI-created timers would fire once (if
|
|
10
|
+
-- armed) and never reschedule. The RPC now accepts both keys with
|
|
11
|
+
-- the same precedence the TS handler uses: interval_minutes
|
|
12
|
+
-- (minutes) first, then interval_seconds (seconds / 60).
|
|
13
|
+
-- Existing rows keep working under either key.
|
|
14
|
+
--
|
|
15
|
+
-- A backfill at the bottom arms existing active timers that have a
|
|
16
|
+
-- computable interval but next_execution IS NULL — rows created before
|
|
17
|
+
-- the API could write the column could never fire.
|
|
18
|
+
|
|
19
|
+
-- CIL[BEGIN stable_id="~/.framework/migrations/071_timer_interval_keys.sql:module:migration" type="migration" identity="timer-interval-keys" parent="~/.framework/migrations/071_timer_interval_keys.sql"]
|
|
20
|
+
-- CIL[DOC-MACRO purpose="Teach update_timer_after_run the interval_seconds config key and backfill next_execution for dormant armed-by-interval timers"]
|
|
21
|
+
-- CIL[DOC-RESPONSIBILITY responsibility="Replace update_timer_after_run with a dual-key interval resolver; arm existing active timers whose next_execution is NULL"]
|
|
22
|
+
-- CIL[DOC-CONTEXT module="system-cron" layer="schema" related_files=".framework/functions/system-cron.ts, .framework/functions/timers.ts, .framework/migrations/068_cron_schedule_timer_rpcs.sql"]
|
|
23
|
+
-- CIL[DOC-CONTRACT inputs="p_timer_id uuid, p_success boolean, p_error_message text" outputs="void" errors="none — SECURITY DEFINER" side_effects="mutates timers bookkeeping columns; backfill sets next_execution on dormant interval timers"]
|
|
24
|
+
-- CIL[DOC-MICRO logic_summary="Interval precedence: config->>'interval_minutes' (minutes) then config->>'interval_seconds' (seconds/60); non-positive or absent => next_execution NULL one-shot stop. Backfill UPDATE arms is_active + next_execution IS NULL + positive-interval rows to now()+interval" notable_decisions="Both keys kept forever so rows seeded with the old UI default (interval_seconds) and the canonical key (interval_minutes) both reschedule; 'once'/cron timers still dead-end to NULL since no cron evaluator exists in the DB"]
|
|
25
|
+
-- CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
26
|
+
-- CIL[DOC-STABILITY stability="stable"]
|
|
27
|
+
-- CIL[DOC-AUDIENCE audience="system-admin"]
|
|
28
|
+
-- CIL[END stable_id="~/.framework/migrations/071_timer_interval_keys.sql:module:migration"]
|
|
29
|
+
|
|
30
|
+
CREATE OR REPLACE FUNCTION public.update_timer_after_run(
|
|
31
|
+
p_timer_id uuid,
|
|
32
|
+
p_success boolean,
|
|
33
|
+
p_error_message text
|
|
34
|
+
)
|
|
35
|
+
RETURNS void
|
|
36
|
+
LANGUAGE plpgsql
|
|
37
|
+
SECURITY DEFINER
|
|
38
|
+
SET search_path = public
|
|
39
|
+
AS $$
|
|
40
|
+
DECLARE
|
|
41
|
+
v_config jsonb;
|
|
42
|
+
v_interval numeric; -- resolved interval in MINUTES
|
|
43
|
+
BEGIN
|
|
44
|
+
SELECT config INTO v_config FROM public.timers WHERE id = p_timer_id;
|
|
45
|
+
|
|
46
|
+
-- interval_minutes (canonical) wins; interval_seconds is the legacy UI
|
|
47
|
+
-- default and is converted seconds -> minutes.
|
|
48
|
+
v_interval := CASE
|
|
49
|
+
WHEN v_config->>'interval_minutes' ~ '^\d+(\.\d+)?$'
|
|
50
|
+
THEN (v_config->>'interval_minutes')::numeric
|
|
51
|
+
WHEN v_config->>'interval_seconds' ~ '^\d+(\.\d+)?$'
|
|
52
|
+
THEN (v_config->>'interval_seconds')::numeric / 60
|
|
53
|
+
ELSE NULL
|
|
54
|
+
END;
|
|
55
|
+
|
|
56
|
+
UPDATE public.timers
|
|
57
|
+
SET
|
|
58
|
+
last_execution = now(),
|
|
59
|
+
execution_count = COALESCE(execution_count, 0) + 1,
|
|
60
|
+
success_count = COALESCE(success_count, 0) + CASE WHEN p_success THEN 1 ELSE 0 END,
|
|
61
|
+
failure_count = COALESCE(failure_count, 0) + CASE WHEN p_success THEN 0 ELSE 1 END,
|
|
62
|
+
next_execution = CASE
|
|
63
|
+
WHEN v_interval IS NOT NULL AND v_interval > 0
|
|
64
|
+
THEN now() + (v_interval * interval '1 minute')
|
|
65
|
+
ELSE NULL
|
|
66
|
+
END,
|
|
67
|
+
updated_at = now()
|
|
68
|
+
WHERE id = p_timer_id;
|
|
69
|
+
END;
|
|
70
|
+
$$;
|
|
71
|
+
|
|
72
|
+
-- Arm timers that are active and have a resolvable interval but were
|
|
73
|
+
-- created while next_execution was unwritable (pre-F-056 API). One-shot
|
|
74
|
+
-- and unparseable configs stay NULL — a NULL next_execution is a safe
|
|
75
|
+
-- never-fire state, matching update_timer_after_run's stop semantics.
|
|
76
|
+
UPDATE public.timers t
|
|
77
|
+
SET
|
|
78
|
+
next_execution = now() + (v.mins * interval '1 minute'),
|
|
79
|
+
updated_at = now()
|
|
80
|
+
FROM (
|
|
81
|
+
SELECT
|
|
82
|
+
id,
|
|
83
|
+
CASE
|
|
84
|
+
WHEN config->>'interval_minutes' ~ '^\d+(\.\d+)?$'
|
|
85
|
+
THEN (config->>'interval_minutes')::numeric
|
|
86
|
+
WHEN config->>'interval_seconds' ~ '^\d+(\.\d+)?$'
|
|
87
|
+
THEN (config->>'interval_seconds')::numeric / 60
|
|
88
|
+
ELSE NULL
|
|
89
|
+
END AS mins
|
|
90
|
+
FROM public.timers
|
|
91
|
+
) v
|
|
92
|
+
WHERE t.id = v.id
|
|
93
|
+
AND t.is_active
|
|
94
|
+
AND t.next_execution IS NULL
|
|
95
|
+
AND v.mins IS NOT NULL
|
|
96
|
+
AND v.mins > 0;
|