spine-framework 0.4.4 → 0.5.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/admin-data.ts +39 -28
- package/.framework/functions/ai-agents.ts +10 -6
- 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/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/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/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/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/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 +4 -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 +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/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/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
|
@@ -248,7 +248,8 @@ export const getExecutions = createHandler(async (ctx, body) => {
|
|
|
248
248
|
throw new Error('Account context required')
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
const
|
|
251
|
+
const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
|
|
252
|
+
const { data, error: err } = await db
|
|
252
253
|
.from('trigger_executions')
|
|
253
254
|
.select('*')
|
|
254
255
|
.eq('trigger_id', trigger_id)
|
|
@@ -275,7 +276,7 @@ export const getExecutions = createHandler(async (ctx, body) => {
|
|
|
275
276
|
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
276
277
|
// CIL[DOC-STABILITY stability="stable"]
|
|
277
278
|
export const create = createHandler(async (ctx, body) => {
|
|
278
|
-
const { app_id, name, description, event_type, pipeline_id, config, metadata } = body
|
|
279
|
+
const { app_id, name, description, event_type, trigger_type, pipeline_id, config, metadata } = body
|
|
279
280
|
|
|
280
281
|
if (!name || !event_type || !pipeline_id) {
|
|
281
282
|
throw new Error('name, event_type, and pipeline_id are required')
|
|
@@ -305,7 +306,8 @@ export const create = createHandler(async (ctx, body) => {
|
|
|
305
306
|
}
|
|
306
307
|
}
|
|
307
308
|
|
|
308
|
-
const
|
|
309
|
+
const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
|
|
310
|
+
const { data, error: err } = await db
|
|
309
311
|
.from('triggers')
|
|
310
312
|
.insert({
|
|
311
313
|
app_id: app_id || null,
|
|
@@ -316,7 +318,8 @@ export const create = createHandler(async (ctx, body) => {
|
|
|
316
318
|
pipeline_id,
|
|
317
319
|
config: config || {},
|
|
318
320
|
metadata: metadata || {},
|
|
319
|
-
|
|
321
|
+
trigger_type: trigger_type || 'event',
|
|
322
|
+
created_by: ctx.principal.type === 'human' ? ctx.principal.id : null
|
|
320
323
|
})
|
|
321
324
|
.select()
|
|
322
325
|
.single()
|
|
@@ -353,13 +356,14 @@ export const update = createHandler(async (ctx, body) => {
|
|
|
353
356
|
throw new Error('Trigger ID is required')
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
const allowed = ['name', 'description', 'config', 'pipeline_id', 'metadata', 'is_active', 'event_type', 'app_id']
|
|
359
|
+
const allowed = ['name', 'description', 'config', 'pipeline_id', 'metadata', 'is_active', 'event_type', 'trigger_type', 'app_id']
|
|
357
360
|
const updateData: Record<string, any> = { updated_at: new Date().toISOString() }
|
|
358
361
|
for (const key of allowed) {
|
|
359
362
|
if (updates[key] !== undefined) updateData[key] = updates[key]
|
|
360
363
|
}
|
|
361
364
|
|
|
362
|
-
const
|
|
365
|
+
const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
|
|
366
|
+
const { data, error: err } = await db
|
|
363
367
|
.from('triggers')
|
|
364
368
|
.update(updateData)
|
|
365
369
|
.eq('id', id)
|
|
@@ -397,7 +401,8 @@ export const remove = createHandler(async (ctx, body) => {
|
|
|
397
401
|
throw new Error('Trigger ID is required')
|
|
398
402
|
}
|
|
399
403
|
|
|
400
|
-
const
|
|
404
|
+
const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
|
|
405
|
+
const { data: existing } = await db
|
|
401
406
|
.from('triggers')
|
|
402
407
|
.select('name, event_type')
|
|
403
408
|
.eq('id', id)
|
|
@@ -407,9 +412,9 @@ export const remove = createHandler(async (ctx, body) => {
|
|
|
407
412
|
throw new Error('Trigger not found')
|
|
408
413
|
}
|
|
409
414
|
|
|
410
|
-
const { error: err } = await
|
|
415
|
+
const { error: err } = await db
|
|
411
416
|
.from('triggers')
|
|
412
|
-
.update({
|
|
417
|
+
.update({
|
|
413
418
|
is_active: false,
|
|
414
419
|
updated_at: new Date().toISOString()
|
|
415
420
|
})
|
|
@@ -446,7 +451,8 @@ export const toggle = createHandler(async (ctx, body) => {
|
|
|
446
451
|
throw new Error('Trigger ID is required')
|
|
447
452
|
}
|
|
448
453
|
|
|
449
|
-
const
|
|
454
|
+
const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
|
|
455
|
+
const { data: existing } = await db
|
|
450
456
|
.from('triggers')
|
|
451
457
|
.select('is_active, name')
|
|
452
458
|
.eq('id', id)
|
|
@@ -458,9 +464,9 @@ export const toggle = createHandler(async (ctx, body) => {
|
|
|
458
464
|
|
|
459
465
|
const newStatus = !existing.is_active
|
|
460
466
|
|
|
461
|
-
const { data, error: err } = await
|
|
467
|
+
const { data, error: err } = await db
|
|
462
468
|
.from('triggers')
|
|
463
|
-
.update({
|
|
469
|
+
.update({
|
|
464
470
|
is_active: newStatus,
|
|
465
471
|
updated_at: new Date().toISOString()
|
|
466
472
|
})
|
|
@@ -497,7 +503,16 @@ export const toggle = createHandler(async (ctx, body) => {
|
|
|
497
503
|
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
498
504
|
// CIL[DOC-STABILITY stability="stable"]
|
|
499
505
|
export const handler = createHandler(async (ctx, body) => {
|
|
500
|
-
const { action } = ctx.query || {}
|
|
506
|
+
const { action, id } = ctx.query || {}
|
|
507
|
+
const method = ctx.query?.method || 'GET'
|
|
508
|
+
|
|
509
|
+
if (!action) {
|
|
510
|
+
if (method === 'GET') return id ? await get(ctx, body) : await list(ctx, body)
|
|
511
|
+
if (method === 'POST') return await create(ctx, body)
|
|
512
|
+
if (method === 'PATCH') return await update(ctx, body)
|
|
513
|
+
if (method === 'DELETE') return await remove(ctx, body)
|
|
514
|
+
throw new Error(`Unsupported method: ${method}`)
|
|
515
|
+
}
|
|
501
516
|
|
|
502
517
|
switch (action) {
|
|
503
518
|
case 'list':
|
|
@@ -42,22 +42,27 @@ const permissions = PermissionEngine as any
|
|
|
42
42
|
// CIL[DOC-MACRO purpose="List active type definitions with optional filters and pagination"]
|
|
43
43
|
// CIL[DOC-RESPONSIBILITY responsibility="Query the public.types table and sanitize records based on the caller principal"]
|
|
44
44
|
// CIL[DOC-CONTEXT module="types function" layer="framework/functions" related_files=".framework/functions/_shared/audit.ts,.framework/functions/_shared/db.ts,.framework/functions/_shared/middleware.ts,.framework/functions/_shared/permissions.ts,.framework/functions/_shared/principal.ts,.framework/functions/_shared/schema-utils.ts,.framework/functions/types.ts"]
|
|
45
|
-
// CIL[DOC-CONTRACT inputs="kind, app_id, ownership, limit, offset, include_schema query parameters" outputs="array of sanitized type rows" errors="Database query errors" side_effects="none"]
|
|
46
|
-
// CIL[DOC-MICRO logic_summary="Builds a filtered, paginated Supabase query against the types table and returns sanitized results" notable_decisions="Returns minimal fields for unauthenticated callers; includes schema only when requested"]
|
|
45
|
+
// CIL[DOC-CONTRACT inputs="kind, app_id, ownership, active, limit, offset, include_schema query parameters" outputs="array of sanitized type rows" errors="Database query errors" side_effects="none"]
|
|
46
|
+
// CIL[DOC-MICRO logic_summary="Builds a filtered, paginated Supabase query against the types table and returns sanitized results" notable_decisions="Returns minimal fields for unauthenticated callers; includes schema only when requested; active defaults to true, 'all' lists active and inactive, 'false' lists inactive only"]
|
|
47
47
|
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
48
48
|
// CIL[DOC-STABILITY stability="stable"]
|
|
49
49
|
export const list = createHandler(async (ctx, body) => {
|
|
50
|
-
const { kind, app_id, ownership, limit = '50', offset = '0', include_schema } = ctx.query || {}
|
|
50
|
+
const { kind, app_id, ownership, active, limit = '50', offset = '0', include_schema } = ctx.query || {}
|
|
51
51
|
|
|
52
52
|
const db = isSystemAdmin(ctx.principal) ? adminDb : ctx.db
|
|
53
53
|
|
|
54
54
|
let query = db
|
|
55
55
|
.from('types')
|
|
56
56
|
.select('*')
|
|
57
|
-
.eq('is_active', true)
|
|
58
57
|
.order('kind')
|
|
59
58
|
.order('name')
|
|
60
59
|
|
|
60
|
+
if (active === 'false') {
|
|
61
|
+
query = query.eq('is_active', false)
|
|
62
|
+
} else if (active !== 'all') {
|
|
63
|
+
query = query.eq('is_active', true)
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
if (kind) {
|
|
62
67
|
query = query.eq('kind', kind)
|
|
63
68
|
}
|
|
@@ -132,7 +137,6 @@ export const get = createHandler(async (ctx, body) => {
|
|
|
132
137
|
.from('types')
|
|
133
138
|
.select('*')
|
|
134
139
|
.eq('id', id)
|
|
135
|
-
.eq('is_active', true)
|
|
136
140
|
.single()
|
|
137
141
|
|
|
138
142
|
if (err) throw err
|
|
@@ -237,7 +241,6 @@ export const create = createHandler(async (ctx, body) => {
|
|
|
237
241
|
app_id = null,
|
|
238
242
|
design_schema = {},
|
|
239
243
|
validation_schema = null,
|
|
240
|
-
view_config = {},
|
|
241
244
|
description = null,
|
|
242
245
|
icon = null,
|
|
243
246
|
color = null
|
|
@@ -272,7 +275,6 @@ export const create = createHandler(async (ctx, body) => {
|
|
|
272
275
|
app_id,
|
|
273
276
|
design_schema,
|
|
274
277
|
validation_schema: validation,
|
|
275
|
-
view_config,
|
|
276
278
|
description,
|
|
277
279
|
icon,
|
|
278
280
|
color,
|
|
@@ -325,7 +327,6 @@ export const update = createHandler(async (ctx, body) => {
|
|
|
325
327
|
.from('types')
|
|
326
328
|
.select('*')
|
|
327
329
|
.eq('id', id)
|
|
328
|
-
.eq('is_active', true)
|
|
329
330
|
.single()
|
|
330
331
|
|
|
331
332
|
if (!existing) {
|
|
@@ -340,7 +341,6 @@ export const update = createHandler(async (ctx, body) => {
|
|
|
340
341
|
name,
|
|
341
342
|
slug,
|
|
342
343
|
design_schema,
|
|
343
|
-
view_config,
|
|
344
344
|
description,
|
|
345
345
|
icon,
|
|
346
346
|
color,
|
|
@@ -354,7 +354,6 @@ export const update = createHandler(async (ctx, body) => {
|
|
|
354
354
|
|
|
355
355
|
if (name !== undefined) updates.name = name
|
|
356
356
|
if (slug !== undefined) updates.slug = slug
|
|
357
|
-
if (view_config !== undefined) updates.view_config = view_config
|
|
358
357
|
if (description !== undefined) updates.description = description
|
|
359
358
|
if (icon !== undefined) updates.icon = icon
|
|
360
359
|
if (color !== undefined) updates.color = color
|
|
@@ -368,6 +367,10 @@ export const update = createHandler(async (ctx, body) => {
|
|
|
368
367
|
updates.validation_schema = generateValidationSchema(design_schema)
|
|
369
368
|
}
|
|
370
369
|
|
|
370
|
+
if (Object.keys(updates).length === 0) {
|
|
371
|
+
return existing
|
|
372
|
+
}
|
|
373
|
+
|
|
371
374
|
const { data, error } = await adminDb
|
|
372
375
|
.from('types')
|
|
373
376
|
.update(updates)
|
|
@@ -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;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
-- 072_slug_autofill.sql
|
|
2
|
+
-- Fill slug from name on insert when the caller omits it.
|
|
3
|
+
-- Fresh-install schema (001) declares slug NOT NULL on these tables, but the
|
|
4
|
+
-- create handlers for triggers/timers/pipelines/ai_agents/test_runs/
|
|
5
|
+
-- item_progress/invites never write slug — inserts fail on fresh chains.
|
|
6
|
+
-- The trigger is a no-op whenever the caller supplies a slug.
|
|
7
|
+
|
|
8
|
+
CREATE OR REPLACE FUNCTION public.spine_fill_slug()
|
|
9
|
+
RETURNS trigger
|
|
10
|
+
LANGUAGE plpgsql
|
|
11
|
+
AS $$
|
|
12
|
+
DECLARE
|
|
13
|
+
base text;
|
|
14
|
+
candidate text;
|
|
15
|
+
clash boolean;
|
|
16
|
+
i int := 2;
|
|
17
|
+
BEGIN
|
|
18
|
+
IF NEW.slug IS NOT NULL AND NEW.slug <> '' THEN
|
|
19
|
+
RETURN NEW;
|
|
20
|
+
END IF;
|
|
21
|
+
|
|
22
|
+
base := trim(both '_' from regexp_replace(lower(coalesce(NEW.name, '')), '[^a-z0-9]+', '_', 'g'));
|
|
23
|
+
IF base = '' THEN
|
|
24
|
+
base := 'row';
|
|
25
|
+
END IF;
|
|
26
|
+
candidate := base;
|
|
27
|
+
|
|
28
|
+
LOOP
|
|
29
|
+
EXECUTE format(
|
|
30
|
+
'SELECT EXISTS (SELECT 1 FROM %I.%I WHERE app_id IS NOT DISTINCT FROM $1 AND slug = $2)',
|
|
31
|
+
TG_TABLE_SCHEMA, TG_TABLE_NAME
|
|
32
|
+
) INTO clash USING NEW.app_id, candidate;
|
|
33
|
+
EXIT WHEN NOT clash;
|
|
34
|
+
candidate := base || '_' || i;
|
|
35
|
+
i := i + 1;
|
|
36
|
+
IF i > 100 THEN
|
|
37
|
+
candidate := base || '_' || substr(replace(gen_random_uuid()::text, '-', ''), 1, 6);
|
|
38
|
+
EXECUTE format(
|
|
39
|
+
'SELECT EXISTS (SELECT 1 FROM %I.%I WHERE app_id IS NOT DISTINCT FROM $1 AND slug = $2)',
|
|
40
|
+
TG_TABLE_SCHEMA, TG_TABLE_NAME
|
|
41
|
+
) INTO clash USING NEW.app_id, candidate;
|
|
42
|
+
EXIT WHEN NOT clash;
|
|
43
|
+
END IF;
|
|
44
|
+
END LOOP;
|
|
45
|
+
|
|
46
|
+
NEW.slug := candidate;
|
|
47
|
+
RETURN NEW;
|
|
48
|
+
END;
|
|
49
|
+
$$;
|
|
50
|
+
|
|
51
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.triggers;
|
|
52
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.triggers
|
|
53
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
54
|
+
|
|
55
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.timers;
|
|
56
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.timers
|
|
57
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
58
|
+
|
|
59
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.pipelines;
|
|
60
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.pipelines
|
|
61
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
62
|
+
|
|
63
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.ai_agents;
|
|
64
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.ai_agents
|
|
65
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
66
|
+
|
|
67
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.test_runs;
|
|
68
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.test_runs
|
|
69
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
70
|
+
|
|
71
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.item_progress;
|
|
72
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.item_progress
|
|
73
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
74
|
+
|
|
75
|
+
DROP TRIGGER IF EXISTS spine_fill_slug_trg ON public.invites;
|
|
76
|
+
CREATE TRIGGER spine_fill_slug_trg BEFORE INSERT ON public.invites
|
|
77
|
+
FOR EACH ROW EXECUTE FUNCTION public.spine_fill_slug();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- 073_seed_member_roles.sql
|
|
2
|
+
-- Seed the member and member_admin end-user roles.
|
|
3
|
+
--
|
|
4
|
+
-- F-066: 003_core_system_data.sql seeds system_admin plus the department
|
|
5
|
+
-- roles but never member/member_admin, even though the platform assumes
|
|
6
|
+
-- they exist:
|
|
7
|
+
-- * 003's own thread/message types grant record_permissions to both
|
|
8
|
+
-- * 060 grants them CRU on account/person; 051/007 grant member reads
|
|
9
|
+
-- * the registration RPCs (052/059) default join_request to 'member'
|
|
10
|
+
-- * RegisterPage falls back to 'member'; the admin UI defaults app
|
|
11
|
+
-- min_role to 'member'; create-app defaults --role member
|
|
12
|
+
-- With no roles row the slug never resolves, so every member grant was a
|
|
13
|
+
-- dead key (guaranteed-deny) and apps scaffolded with the default --role
|
|
14
|
+
-- wrote a min_role no person could satisfy.
|
|
15
|
+
--
|
|
16
|
+
-- is_system stays false: invites.ts resolveRoleId and the invite-accept
|
|
17
|
+
-- path reject is_system role slugs, and these are the self-service
|
|
18
|
+
-- end-user roles (same shape the app seed/roles.json files carry).
|
|
19
|
+
-- App installs upsert the same slugs (conflict 'slug'), so ON CONFLICT
|
|
20
|
+
-- DO NOTHING keeps this a no-op wherever an app already claimed them.
|
|
21
|
+
|
|
22
|
+
INSERT INTO public.roles (id, slug, name, description, permissions, is_system, is_active, is_protected, app_id, account_id)
|
|
23
|
+
VALUES
|
|
24
|
+
(gen_random_uuid(), 'member', 'Member', 'Standard account member — end-user access to account-scoped apps', '["*"]'::jsonb, false, true, false, NULL, NULL),
|
|
25
|
+
(gen_random_uuid(), 'member_admin', 'Member Admin', 'Member administrator — same access as member plus invite and member management', '["*"]'::jsonb, false, true, false, NULL, NULL)
|
|
26
|
+
ON CONFLICT (slug) DO NOTHING;
|