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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: system-cron
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: reference
|
|
5
|
+
verified_against: f69c37d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/system-cron.ts
|
|
9
|
+
- .framework/functions/trigger-scheduler.ts
|
|
10
|
+
- .framework/functions/_shared/pipeline-runner.ts
|
|
11
|
+
- .framework/functions/_shared/retry-deliveries.ts
|
|
12
|
+
- .framework/functions/_shared/principal.ts
|
|
13
|
+
- .framework/migrations/068_cron_schedule_timer_rpcs.sql
|
|
14
|
+
- netlify.toml
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# system-cron
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
The worker behind every `trigger-scheduler` tick. Each authenticated run
|
|
22
|
+
performs, in order: a stale-execution sweep, due-schedule execution, due-
|
|
23
|
+
timer pipelines, threshold-alert evaluation, webhook-delivery retries, and
|
|
24
|
+
— on the 00:00 UTC tick — log cleanup. It is also directly invocable over
|
|
25
|
+
HTTP for manual runs.
|
|
26
|
+
|
|
27
|
+
## Route and methods
|
|
28
|
+
|
|
29
|
+
`/.netlify/functions/system-cron`. Normally reached internally via
|
|
30
|
+
`trigger-scheduler` (Netlify Clockwork, `* * * * *` in `netlify.toml`),
|
|
31
|
+
which calls this handler either with a fabricated `SYSTEM_PRINCIPAL`
|
|
32
|
+
context (scheduled ticks) or through the normal middleware path (manual
|
|
33
|
+
calls). Any HTTP method reaches the handler — there is no method dispatch.
|
|
34
|
+
|
|
35
|
+
## Authentication
|
|
36
|
+
|
|
37
|
+
Inside the handler, after `createHandler` has resolved a non-anonymous
|
|
38
|
+
principal:
|
|
39
|
+
|
|
40
|
+
- `?api_key=` or `?scheduler_key=` equal to the `SCHEDULER_API_KEY`
|
|
41
|
+
environment variable, **or**
|
|
42
|
+
- a machine principal with `machineType === 'internal'` — which is what a
|
|
43
|
+
valid `x-internal-secret` header resolves to (`SYSTEM_PRINCIPAL`).
|
|
44
|
+
|
|
45
|
+
Anything else returns 403 and emits a `system_cron.unauthorized_access`
|
|
46
|
+
audit event. When `SCHEDULER_API_KEY` is unset the key comparison can
|
|
47
|
+
never match, so only internal machine principals pass — fail closed.
|
|
48
|
+
|
|
49
|
+
## Request contract
|
|
50
|
+
|
|
51
|
+
- Query: `api_key` or `scheduler_key` (scheduler key auth path only).
|
|
52
|
+
- Headers: `x-internal-secret` (internal machine principal path).
|
|
53
|
+
- No body is read.
|
|
54
|
+
|
|
55
|
+
## What runs per invocation
|
|
56
|
+
|
|
57
|
+
1. **Stale-execution sweep** — `sweepStaleExecutions()` marks
|
|
58
|
+
`pipeline_executions` rows in `running`/`pending`/`queued` untouched
|
|
59
|
+
for over 10 minutes as `failed` with a `stale execution` error message.
|
|
60
|
+
Runs on every invocation, before anything else.
|
|
61
|
+
2. **Due schedules** — `get_due_schedules` RPC returns due
|
|
62
|
+
`public.schedules` rows. For each: `validate_schedule_creator` RPC →
|
|
63
|
+
load the `public.actions` row → load the schedule's machine principal
|
|
64
|
+
from `api_keys` → verify scope (`action.required_scopes[0]` or
|
|
65
|
+
`<handler>:execute`, satisfied also by `*:*`) → `executeAction` →
|
|
66
|
+
insert a `schedule_executions` row → `update_schedule_after_run` RPC →
|
|
67
|
+
`schedule.execute` audit.
|
|
68
|
+
3. **Due timers** — `get_due_timers` RPC; each due timer runs
|
|
69
|
+
`runPipeline(timer.pipeline_id, …)` under a fabricated machine
|
|
70
|
+
principal `timer:<timer_id>` with scope `pipelines:execute`, then
|
|
71
|
+
`update_timer_after_run` reschedules it. Timers without `pipeline_id`
|
|
72
|
+
fail that item only.
|
|
73
|
+
4. **Threshold alerts** — active `threshold_alerts` rows are evaluated
|
|
74
|
+
against `error_rate`, `latency_p95`, or `pipeline_failure_rate` metric
|
|
75
|
+
RPCs over `window_minutes`. `operator: 'gt'` compares `>`; anything
|
|
76
|
+
else compares `<`. A breached threshold with a `pipeline_id` fires
|
|
77
|
+
`runPipeline` with the metric details as trigger data, and a
|
|
78
|
+
`threshold.breached` audit event is emitted either way on breach.
|
|
79
|
+
5. **Log cleanup** — only when the tick lands on UTC 00:00:
|
|
80
|
+
`cleanup_old_logs` RPC with `p_retention_days: 30`.
|
|
81
|
+
6. **Delivery retries** — `retryPendingDeliveries(ctx)` sweeps due
|
|
82
|
+
outbound webhook deliveries.
|
|
83
|
+
|
|
84
|
+
`executeAction` routes `action.handler_module`:
|
|
85
|
+
|
|
86
|
+
- `functions` — `send_email`, `generate_report`, `notify_watchers` are
|
|
87
|
+
console-log stubs returning success-shaped objects; `run_pipeline`
|
|
88
|
+
invokes `runPipeline` for real.
|
|
89
|
+
- `integrations` — stub returning `{ executed: true, handler }`.
|
|
90
|
+
- `custom` — throws `Custom handlers not yet implemented`.
|
|
91
|
+
- anything else — throws `Unknown handler module`.
|
|
92
|
+
|
|
93
|
+
## Response contract
|
|
94
|
+
|
|
95
|
+
- 200 — `{ message, executed, success, failed, skipped,
|
|
96
|
+
thresholds_evaluated, thresholds_breached, logs_cleaned,
|
|
97
|
+
deliveries_retried, results }` where `results[]` carries
|
|
98
|
+
`{ scheduleId, actionId, status: success|failed|skipped, error?,
|
|
99
|
+
durationMs }` per schedule/timer item.
|
|
100
|
+
- 200 with `{ message: 'No schedules due for execution', executed: 0,
|
|
101
|
+
results: [] }` when `get_due_schedules` returns nothing — see the early
|
|
102
|
+
return note below.
|
|
103
|
+
- 403 — invalid scheduler authentication.
|
|
104
|
+
- 500 — top-level failure, with a `system_cron.error` audit event.
|
|
105
|
+
|
|
106
|
+
## Side effects
|
|
107
|
+
|
|
108
|
+
Writes `pipeline_executions` (via `runPipeline` and the sweep),
|
|
109
|
+
`schedule_executions`, schedule/timer bookkeeping via RPC, `logs` audit
|
|
110
|
+
events (`system_cron.unauthorized_access`, `system_cron.error`,
|
|
111
|
+
`schedule.execute`, `threshold.breached`), deletes old `logs` rows at
|
|
112
|
+
00:00 UTC, and mutates `webhook_deliveries` through the retry sweep.
|
|
113
|
+
|
|
114
|
+
## Errors
|
|
115
|
+
|
|
116
|
+
Per-item failures are contained: a failing schedule/timer records a
|
|
117
|
+
`failed` result and a `schedule_executions`/`update_*_after_run` failure
|
|
118
|
+
record rather than aborting the tick. A failure in the outer block —
|
|
119
|
+
e.g. `get_due_schedules` itself erroring — short-circuits to 500 with a
|
|
120
|
+
`system_cron.error` audit. Threshold, timer-loop, cleanup, and sweep
|
|
121
|
+
failures are caught and logged without failing the response.
|
|
122
|
+
|
|
123
|
+
## Agent notes
|
|
124
|
+
|
|
125
|
+
- **Early return:** when `get_due_schedules` yields no due schedules the
|
|
126
|
+
handler returns immediately — due timers, threshold evaluation,
|
|
127
|
+
delivery retries, and log cleanup all sit *after* that return and only
|
|
128
|
+
run on ticks where at least one schedule is due. Installations relying
|
|
129
|
+
on timers need at least one due schedule per tick for those steps to
|
|
130
|
+
execute; verify this is intended before depending on timer cadence.
|
|
131
|
+
- `send_email`, `generate_report`, `notify_watchers`, and the
|
|
132
|
+
`integrations` module are stubs — a schedule pointing at them "succeeds"
|
|
133
|
+
while doing nothing but a console log. Only `run_pipeline` performs real
|
|
134
|
+
work today.
|
|
135
|
+
- `SCHEDULER_API_KEY` unset does not open the endpoint: the key path
|
|
136
|
+
simply never matches and only internal machine principals pass.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: system
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: reference
|
|
5
|
+
verified_against: f69c37d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/system.ts
|
|
9
|
+
- .framework/functions/_shared/middleware.ts
|
|
10
|
+
- .framework/functions/_shared/db.ts
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# system
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
Deployment introspection: a `manifest` action describing version,
|
|
18
|
+
migrations, and the function surface; a `health` action reporting
|
|
19
|
+
database/migration/integrity status; and an `openapi` action generating an
|
|
20
|
+
OpenAPI 3.1.0 document from discovered functions. Read-only — nothing here
|
|
21
|
+
mutates the database.
|
|
22
|
+
|
|
23
|
+
## Route and methods
|
|
24
|
+
|
|
25
|
+
`/.netlify/functions/system?action=<manifest|health|openapi>`.
|
|
26
|
+
|
|
27
|
+
`action` defaults to `manifest` when omitted. There is no method check —
|
|
28
|
+
any HTTP method dispatches the same way.
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
Wrapped in `createHandler`: any non-anonymous principal passes — human
|
|
33
|
+
Bearer JWT, machine `x-api-key`, or `x-internal-secret`. Anonymous → 401.
|
|
34
|
+
There is no role gate; every authenticated caller can read the manifest,
|
|
35
|
+
health, and OpenAPI output. All queries run on `adminDb`.
|
|
36
|
+
|
|
37
|
+
## Request contract
|
|
38
|
+
|
|
39
|
+
Query parameters only: `action` (optional, default `manifest`). No body is
|
|
40
|
+
read.
|
|
41
|
+
|
|
42
|
+
## Response contract
|
|
43
|
+
|
|
44
|
+
All responses arrive inside the middleware envelope
|
|
45
|
+
`{ data: { data: <payload> }, error: null, meta }` — the action functions
|
|
46
|
+
return `{ data: … }` objects which are wrapped again by `createHandler`.
|
|
47
|
+
|
|
48
|
+
- `manifest` → `{ version, schema: 'public', migrations: { applied,
|
|
49
|
+
latest, pending }, functions: [{ name, methods, path, description }],
|
|
50
|
+
integrity: { core_hash, verified } }`.
|
|
51
|
+
- `version` comes from `.spine-manifest.json` in the process working
|
|
52
|
+
directory (default `'2.0.0'` when missing or unparseable).
|
|
53
|
+
- `integrity.core_hash` is `manifest.integrity.src`; `verified` is true
|
|
54
|
+
only when the file exists and parses.
|
|
55
|
+
- `functions` is built by scanning a `v2-core/functions` directory for
|
|
56
|
+
`.ts` files and assigning methods from blocklists (see Agent notes).
|
|
57
|
+
- `health` → `{ status: 'healthy' | 'degraded' | 'unhealthy', checks:
|
|
58
|
+
{ database: { connected, latency_ms }, migrations: { current, pending,
|
|
59
|
+
applied }, integrity: { verified }, test_runs? } }`.
|
|
60
|
+
- `unhealthy` when the `accounts` probe query fails; `degraded` when
|
|
61
|
+
migrations are pending or integrity is unverified; else `healthy`.
|
|
62
|
+
- `test_runs` is the latest `test_runs` row (`suite`, `status`,
|
|
63
|
+
`started_at`) when present.
|
|
64
|
+
- `openapi` → an OpenAPI `3.1.0` document (`info.title` = "Spine API",
|
|
65
|
+
`info.version` = manifest version, `servers: ['/ .netlify/functions']`
|
|
66
|
+
— see Agent notes) whose `paths` are generated from the discovered
|
|
67
|
+
function list, each operation exposing an optional `action` query
|
|
68
|
+
parameter and a placeholder object response schema.
|
|
69
|
+
- Unknown action → the handler returns `{ error: 'Unknown action: ….
|
|
70
|
+
Valid actions: manifest, health, openapi', status: 400 }` **as data** —
|
|
71
|
+
the HTTP response is still 200 because the value is returned, not
|
|
72
|
+
thrown.
|
|
73
|
+
|
|
74
|
+
## Side effects
|
|
75
|
+
|
|
76
|
+
None on the database. The filesystem reads are: `.spine-manifest.json`,
|
|
77
|
+
and the `v2-core/functions` + `v2-core/migrations_dayzero` directories
|
|
78
|
+
under `process.cwd()`. Database reads: `accounts` (health probe),
|
|
79
|
+
`public.schema_migrations`, `test_runs` — all via `adminDb`.
|
|
80
|
+
|
|
81
|
+
## Errors
|
|
82
|
+
|
|
83
|
+
The handler never throws for its own logic — database failures inside
|
|
84
|
+
`getManifest`/`getHealth` are swallowed into `applied: 0` / `pending`
|
|
85
|
+
counts and status flags. `createHandler` still surfaces middleware-level
|
|
86
|
+
errors (401 unauthenticated, 400 invalid JSON body).
|
|
87
|
+
|
|
88
|
+
## Agent notes
|
|
89
|
+
|
|
90
|
+
- **Stale paths:** `discoverFunctions` scans `v2-core/functions` and the
|
|
91
|
+
migration checks scan `v2-core/migrations_dayzero` — `v2-core` is the
|
|
92
|
+
pre-monorepo layout name; the live code lives under
|
|
93
|
+
`.framework/functions` and `.framework/migrations`. In the current
|
|
94
|
+
layout those directories do not exist, so `functions` returns `[]` and
|
|
95
|
+
`migrations.pending` is always `0` (making `migrations.current` always
|
|
96
|
+
true and `health` never degraded on migrations). Treat the manifest's
|
|
97
|
+
`functions` list and pending count as unpopulated until this is fixed.
|
|
98
|
+
- The migration table queried is `schema_migrations`; the CLI migrator
|
|
99
|
+
records to `_spine_migrations`, so `applied`/`latest` may also read
|
|
100
|
+
empty — the query failure is swallowed silently.
|
|
101
|
+
- Because `verified` requires a readable `.spine-manifest.json` at the
|
|
102
|
+
function's working directory, `health` reports `degraded` on deploys
|
|
103
|
+
where the file is absent from the bundle.
|
|
104
|
+
- This endpoint answers "what is deployed" questions for agents: prefer
|
|
105
|
+
`health` for a liveness signal and `manifest` for version/integrity; do
|
|
106
|
+
not rely on `functions` or `openapi` path lists being complete.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: tests
|
|
3
|
+
audience: both
|
|
4
|
+
kind: reference
|
|
5
|
+
verified_against: f69c37d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/tests.ts
|
|
9
|
+
- .framework/functions/_shared/middleware.ts
|
|
10
|
+
- .framework/public/_redirects
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# tests
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
Read-only reporting over the `test_runs` and `test_results` tables —
|
|
18
|
+
the suites a Spine deployment records about itself. Lists recent runs,
|
|
19
|
+
returns one run with its results, or summarizes the last 30 days per
|
|
20
|
+
suite.
|
|
21
|
+
|
|
22
|
+
## Route and methods
|
|
23
|
+
|
|
24
|
+
`/.netlify/functions/tests`, reachable as `/api/tests` (`/api/*` →
|
|
25
|
+
`/.netlify/functions/:splat`). No method check — the action comes from
|
|
26
|
+
query parameters:
|
|
27
|
+
|
|
28
|
+
| Call | Result |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `GET /api/tests` or `?action=list` | recent `test_runs` (default limit 20, max 100; `?suite=` filters) |
|
|
31
|
+
| `GET /api/tests?action=get&id=<uuid>` or any `?id=`/`?run_id=` | one run + its `test_results` |
|
|
32
|
+
| `GET /api/tests?action=stats` | per-suite rollup for `unit`, `integration`, `api`, `ui` over the last 30 days |
|
|
33
|
+
|
|
34
|
+
Note that `?id=` (or `?run_id=`) forces the `get` path regardless of
|
|
35
|
+
`action`.
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
Standard `createHandler` plus `requireSystemContextWithAudit` — the
|
|
40
|
+
caller must resolve to a `system_admin` principal (human with the
|
|
41
|
+
`system_admin` role, a `*:*` machine key, or the internal secret).
|
|
42
|
+
Anonymous is rejected with 401 by the middleware.
|
|
43
|
+
|
|
44
|
+
All queries use `adminDb` (service role).
|
|
45
|
+
|
|
46
|
+
## Request contract
|
|
47
|
+
|
|
48
|
+
Query params only: `action` (`list` default), `id`/`run_id`, `suite`,
|
|
49
|
+
`limit` (capped at 100).
|
|
50
|
+
|
|
51
|
+
## Response contract
|
|
52
|
+
|
|
53
|
+
This handler returns raw `{ data, error }` objects as its result, so the
|
|
54
|
+
middleware wraps them again: the body is
|
|
55
|
+
`{ "data": { "data": <payload>, "error": null }, "error": null, "meta": {...} }`.
|
|
56
|
+
|
|
57
|
+
- `list` → `data.data` = array of run summaries (`id, suite, status,
|
|
58
|
+
started_at, finished_at, duration_ms, total, passed, failed, skipped,
|
|
59
|
+
triggered_by`).
|
|
60
|
+
- `get` → `data.data` = the run row plus `results` (`test_results` ordered
|
|
61
|
+
by `status`, `file`, `name`).
|
|
62
|
+
- `stats` → `data.data` = one object per suite: `{ suite, total_runs,
|
|
63
|
+
last_status, last_run_at, last_passed, last_failed, last_total }`.
|
|
64
|
+
|
|
65
|
+
**Errors are also embedded in the payload**: failures return
|
|
66
|
+
`{ error: "<msg>", status: <code> }` inside `data` — the outer HTTP status
|
|
67
|
+
is still 200.
|
|
68
|
+
|
|
69
|
+
## Side effects
|
|
70
|
+
|
|
71
|
+
None — read-only.
|
|
72
|
+
|
|
73
|
+
## Errors
|
|
74
|
+
|
|
75
|
+
Embedded under `data` (HTTP 200):
|
|
76
|
+
|
|
77
|
+
| Payload | Meaning |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `{ error: "id is required for action=get", status: 400 }` | `action=get` without `id`/`run_id` |
|
|
80
|
+
| `{ error: "Test run not found: …", status: 404 }` | unknown run id |
|
|
81
|
+
| `{ error: "<postgrest msg>", status: 500 }` | query failure |
|
|
82
|
+
|
|
83
|
+
Non-system-admin callers get the `requireSystemContextWithAudit` denial,
|
|
84
|
+
which — like the other responses — is returned as the handler result and
|
|
85
|
+
lands inside `data` (a `{ statusCode, headers, body }` response object)
|
|
86
|
+
with HTTP 200, not as a real 403.
|
|
87
|
+
|
|
88
|
+
## Agent notes
|
|
89
|
+
|
|
90
|
+
- Always unwrap twice: `body.data.data` is the payload, `body.data.error`
|
|
91
|
+
is where this endpoint reports failures. Do not rely on HTTP status
|
|
92
|
+
codes here.
|
|
93
|
+
- `stats` only covers the four hard-coded suites (`unit`, `integration`,
|
|
94
|
+
`api`, `ui`); other suite names appear in `list` but never in `stats`.
|
|
95
|
+
- To record runs, write `test_runs`/`test_results` through
|
|
96
|
+
`/api/admin-data` (raw entity path) or service-role code — this endpoint
|
|
97
|
+
does not create them.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: timers
|
|
3
|
+
audience: both
|
|
4
|
+
kind: reference
|
|
5
|
+
verified_against: f69c37d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/timers.ts
|
|
9
|
+
- .framework/functions/_shared/middleware.ts
|
|
10
|
+
- .framework/functions/system-cron.ts
|
|
11
|
+
- .framework/migrations/020_schema_reconciliation.sql
|
|
12
|
+
- .framework/migrations/068_cron_schedule_timer_rpcs.sql
|
|
13
|
+
- .framework/public/_redirects
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# timers
|
|
17
|
+
|
|
18
|
+
## What it does
|
|
19
|
+
|
|
20
|
+
CRUD plus `toggle` over `public.timers` — the config rows that bind a
|
|
21
|
+
`pipeline_id` to a schedule (`timer_type` + `config`, with
|
|
22
|
+
`next_execution`/`execution_count` bookkeeping columns). **This function
|
|
23
|
+
never executes anything**: firing is done by `system-cron`, which polls
|
|
24
|
+
the `get_due_timers` RPC each tick and calls `runPipeline` as a
|
|
25
|
+
`timer:<id>` machine principal, then reschedules via
|
|
26
|
+
`update_timer_after_run` (`.framework/migrations/068_cron_schedule_timer_rpcs.sql`).
|
|
27
|
+
|
|
28
|
+
## Route and methods
|
|
29
|
+
|
|
30
|
+
`/.netlify/functions/timers`, reachable as `/api/timers` (`/api/*` →
|
|
31
|
+
`/.netlify/functions/:splat`, `.framework/public/_redirects`). Dispatch
|
|
32
|
+
is by **`?action=` only** — the HTTP method is ignored:
|
|
33
|
+
|
|
34
|
+
| Call | Handler |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `/api/timers?action=list` | `list` |
|
|
37
|
+
| `/api/timers?action=get&id=<uuid>` | `get` |
|
|
38
|
+
| `/api/timers?action=create` | `create` |
|
|
39
|
+
| `/api/timers?action=update` (`id` in body or query) | `update` |
|
|
40
|
+
| `/api/timers?action=remove` (`id` in body or query) | `remove` |
|
|
41
|
+
| `/api/timers?action=toggle` (`id` in body or query) | `toggle` |
|
|
42
|
+
|
|
43
|
+
A bare `GET /api/timers` with no action throws
|
|
44
|
+
`Unsupported action: undefined`. Conversely `GET ?action=create` reaches
|
|
45
|
+
`create` (with a null body → required-field error).
|
|
46
|
+
|
|
47
|
+
## Authentication
|
|
48
|
+
|
|
49
|
+
Standard `createHandler` auth: `x-api-key` → machine principal,
|
|
50
|
+
`x-internal-secret` → internal principal, `Bearer <jwt>` → human;
|
|
51
|
+
anonymous → 401.
|
|
52
|
+
|
|
53
|
+
- `list`/`create` require `ctx.accountId`; `create`/`update`/`remove`/
|
|
54
|
+
`toggle` additionally require a person+account context.
|
|
55
|
+
- No role gate — system admins use `adminDb`, other humans use
|
|
56
|
+
JWT-scoped `ctx.db` (RLS); machines run service-role.
|
|
57
|
+
- Reads are sanitized: non-admins get `{ id }` per row (timer rows carry
|
|
58
|
+
no `design_schema`).
|
|
59
|
+
|
|
60
|
+
## Request contract
|
|
61
|
+
|
|
62
|
+
**list** (query): `app_id`, `timer_type`, `is_active`
|
|
63
|
+
(`'true'`/`'false'`). Ordered by `name`; no pagination parameters.
|
|
64
|
+
|
|
65
|
+
**get** (query): `id`.
|
|
66
|
+
|
|
67
|
+
**create** (body): `name`, `timer_type` required. Optional: `app_id`,
|
|
68
|
+
`description`, `config` `{}`, `pipeline_id`, `metadata` `{}`.
|
|
69
|
+
`account_id` and `created_by` stamped from context. (Columns
|
|
70
|
+
`timer_type`, `pipeline_id`, `next_execution`, `execution_count` were
|
|
71
|
+
added by `.framework/migrations/020_schema_reconciliation.sql`.)
|
|
72
|
+
|
|
73
|
+
**update** (`id` in body or query + body): allowlist — `name`,
|
|
74
|
+
`description`, `config`, `pipeline_id`, `metadata`, `is_active`,
|
|
75
|
+
`timer_type`, `app_id`.
|
|
76
|
+
|
|
77
|
+
**remove** (`id` in body or query): **hard delete**.
|
|
78
|
+
|
|
79
|
+
**toggle** (`id` in body or query): flips `is_active` to its negation —
|
|
80
|
+
no target-state argument.
|
|
81
|
+
|
|
82
|
+
## Response contract
|
|
83
|
+
|
|
84
|
+
Standard `{ data, error, meta }` envelope. `list`/`get`/`create`/
|
|
85
|
+
`update`/`toggle` → sanitized row(s); `remove` → `{ success: true }`.
|
|
86
|
+
|
|
87
|
+
## Side effects
|
|
88
|
+
|
|
89
|
+
- `toggle` and `update` stamp `updated_at`; `is_active` is what
|
|
90
|
+
`get_due_timers` gates on (plus `next_execution <= now`).
|
|
91
|
+
- `emitLog` writes `timer.created` / `.updated` / `.deleted` /
|
|
92
|
+
`.toggled` audit rows to `public.logs`.
|
|
93
|
+
- No schedule recomputation happens here — `next_execution` is only
|
|
94
|
+
advanced by `update_timer_after_run` during `system-cron` ticks.
|
|
95
|
+
|
|
96
|
+
## Errors
|
|
97
|
+
|
|
98
|
+
| Condition | HTTP | Message |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| No account context (list, create) | 500 | `Account context required` |
|
|
101
|
+
| Missing create fields | 500 | `name and timer_type are required` |
|
|
102
|
+
| No person+account context | 500 | `User context (person and account) required` |
|
|
103
|
+
| Missing `id` | 500 | `Timer ID is required` |
|
|
104
|
+
| Missing row (remove/toggle) | 500 | `Timer not found` |
|
|
105
|
+
| Missing/unknown action | 500 | `Unsupported action: <action>` |
|
|
106
|
+
|
|
107
|
+
## Agent notes
|
|
108
|
+
|
|
109
|
+
- `next_execution` is a real column (migration 020) but is not in the
|
|
110
|
+
create/update allowlists — it is only read by `get_due_timers` and
|
|
111
|
+
advanced by `update_timer_after_run` on `system-cron` ticks.
|
|
112
|
+
- `toggle` flips blindly — read the row first if you need a target
|
|
113
|
+
state; concurrent toggles can cancel each other.
|
|
114
|
+
- `remove` is a hard delete (unlike `triggers`, which soft-deletes).
|
|
115
|
+
- Distinct roles: `timers` = config CRUD; `system-cron` = firing;
|
|
116
|
+
`pipelines` = what runs; `pipeline-executions` = run records. See
|
|
117
|
+
[Automation](../../concepts/automation.md).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: trigger-scheduler
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: reference
|
|
5
|
+
verified_against: f69c37d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/trigger-scheduler.ts
|
|
9
|
+
- .framework/functions/system-cron.ts
|
|
10
|
+
- .framework/functions/_shared/principal.ts
|
|
11
|
+
- netlify.toml
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# trigger-scheduler
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
The clock for Spine automation. `netlify.toml` declares it a Netlify
|
|
19
|
+
scheduled function at `* * * * *` — Netlify Clockwork invokes it every
|
|
20
|
+
minute. The file itself is a thin shim: it detects whether the invocation
|
|
21
|
+
is a genuine scheduled tick (which arrives with no credentials) and runs
|
|
22
|
+
the shared `system-cron` handler under a fabricated system context, or
|
|
23
|
+
falls through to `system-cron`'s normal middleware auth for everything
|
|
24
|
+
else. All actual work — due schedules, due timers, threshold alerts,
|
|
25
|
+
delivery retries, log cleanup — lives in `system-cron`.
|
|
26
|
+
|
|
27
|
+
## Route and methods
|
|
28
|
+
|
|
29
|
+
Two invocation shapes:
|
|
30
|
+
|
|
31
|
+
1. **Netlify scheduled invocation** — the platform calls
|
|
32
|
+
`/.netlify/functions/trigger-scheduler` once per minute with a schedule
|
|
33
|
+
indicator header and a `{"next_run": "<ISO timestamp>"}` body.
|
|
34
|
+
2. **Manual HTTP call** — any authenticated request to the same path
|
|
35
|
+
(useful for forcing a tick in dev). This falls through to
|
|
36
|
+
`system-cron`'s `createHandler` auth path.
|
|
37
|
+
|
|
38
|
+
## Authentication
|
|
39
|
+
|
|
40
|
+
Clockwork detection (`isNetlifyScheduledInvocation`) requires **both**:
|
|
41
|
+
|
|
42
|
+
- a schedule indicator — `x-netlify-event: schedule` (production canonical
|
|
43
|
+
header), `x-nf-event: schedule` (Netlify CLI emulation), or a
|
|
44
|
+
`Netlify Clockwork` user-agent when neither header is present — **and**
|
|
45
|
+
- a JSON body whose `next_run` field is a string.
|
|
46
|
+
|
|
47
|
+
Requiring the body shape means a bare spoofed header or UA alone cannot
|
|
48
|
+
authenticate. A detected tick fabricates a `RequestContext` carrying
|
|
49
|
+
`SYSTEM_PRINCIPAL` (`machine`, `machineType: 'internal'`, `*:*` scope) and
|
|
50
|
+
`adminDb`, then calls the cron handler directly — `createHandler`
|
|
51
|
+
short-circuits when `requestId` + `principal` are already present, so no
|
|
52
|
+
second auth resolution happens.
|
|
53
|
+
|
|
54
|
+
Anything that fails detection goes through `system-cron`'s own checks:
|
|
55
|
+
|
|
56
|
+
- `x-internal-secret` header → resolves to `SYSTEM_PRINCIPAL` via
|
|
57
|
+
`resolvePrincipal`, which passes the internal-machine gate.
|
|
58
|
+
- `?api_key=` or `?scheduler_key=` query param equal to
|
|
59
|
+
`SCHEDULER_API_KEY`.
|
|
60
|
+
- any other internal machine principal.
|
|
61
|
+
- anonymous requests → 401 from `createHandler`; authenticated but
|
|
62
|
+
unauthorized → 403 plus a `system_cron.unauthorized_access` audit event.
|
|
63
|
+
|
|
64
|
+
## Request contract
|
|
65
|
+
|
|
66
|
+
- Scheduled tick: schedule indicator header + body
|
|
67
|
+
`{"next_run": "<ISO timestamp>"}`.
|
|
68
|
+
- Manual call: `?api_key=$SCHEDULER_API_KEY` (or `?scheduler_key=`), or an
|
|
69
|
+
`x-internal-secret` header. No request body is read.
|
|
70
|
+
|
|
71
|
+
## Response contract
|
|
72
|
+
|
|
73
|
+
Whatever `system-cron`'s handler returns — a JSON summary of the tick
|
|
74
|
+
(`executed`, `success`, `failed`, `skipped`, `thresholds_evaluated`,
|
|
75
|
+
`logs_cleaned`, `deliveries_retried`, `results[]`), or an auth/500 error.
|
|
76
|
+
See the `system-cron` reference page for the full shape.
|
|
77
|
+
|
|
78
|
+
## Side effects
|
|
79
|
+
|
|
80
|
+
None of its own — every effect belongs to `system-cron`. The only
|
|
81
|
+
local decision is which auth path the invocation takes.
|
|
82
|
+
|
|
83
|
+
## Errors
|
|
84
|
+
|
|
85
|
+
- 401 — unauthenticated non-scheduled request (from `createHandler`).
|
|
86
|
+
- 403 — authenticated but not an internal machine principal and no valid
|
|
87
|
+
scheduler key (from `system-cron`, with audit event).
|
|
88
|
+
- 500 — bubbled from `system-cron` failures (`system_cron.error` audit).
|
|
89
|
+
|
|
90
|
+
## Agent notes
|
|
91
|
+
|
|
92
|
+
- The function is deliberately a shim: scheduled-invocation plumbing has
|
|
93
|
+
changed across Netlify environments before (CLI vs production header
|
|
94
|
+
names), so detection is centralized here rather than inside the worker.
|
|
95
|
+
If a future Netlify rename breaks ticks, this file is the fix point —
|
|
96
|
+
the Clockwork user-agent check is already the third fallback indicator.
|
|
97
|
+
- A "401 every minute" symptom in production usually means the scheduler
|
|
98
|
+
hit the non-Clockwork path — check that the invocation carries a
|
|
99
|
+
recognized schedule header **and** the `next_run` body.
|
|
100
|
+
- There is no per-tick idempotency key: two overlapping ticks would both
|
|
101
|
+
run `system-cron`. Due-item claiming relies on the RPCs and row state,
|
|
102
|
+
not on tick uniqueness.
|