spine-framework 0.4.4 → 0.5.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -3
- package/.framework/cli/commands/bootstrap.ts +392 -0
- package/.framework/cli/commands/dev.ts +91 -218
- package/.framework/cli/commands/doctor.ts +132 -26
- package/.framework/cli/commands/generate.ts +38 -298
- package/.framework/cli/commands/init.ts +20 -1
- package/.framework/cli/commands/install-app.ts +183 -13
- package/.framework/cli/commands/migrate.ts +36 -30
- package/.framework/cli/commands/migrations.ts +16 -56
- package/.framework/cli/commands/upgrade.ts +35 -0
- package/.framework/cli/commands/validate-app.ts +127 -1
- package/.framework/cli/commands/validate-schema.ts +147 -0
- package/.framework/cli/context.ts +223 -110
- package/.framework/cli/index.ts +6 -0
- package/.framework/cli/lib/project-files.ts +241 -1
- package/.framework/functions/_shared/app-manifest.ts +2 -0
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/permissions.ts +4 -0
- package/.framework/functions/admin-data.ts +51 -28
- package/.framework/functions/ai-agents.ts +10 -6
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +63 -29
- package/.framework/functions/cil-ingest.ts +48 -15
- package/.framework/functions/debug-auth.ts +14 -8
- package/.framework/functions/deliveries.ts +39 -12
- package/.framework/functions/embeddings.ts +149 -78
- package/.framework/functions/integration-outbound.ts +67 -27
- package/.framework/functions/integrations.ts +254 -127
- package/.framework/functions/logs.ts +273 -133
- package/.framework/functions/observability.ts +278 -148
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +240 -127
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
- package/.framework/migrations/075_security_definer_grants.sql +157 -0
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/layout/Sidebar.tsx +300 -177
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/contexts/AppContext.tsx +5 -12
- package/.framework/src/contexts/AuthContext.tsx +17 -9
- package/.framework/src/hooks/useApps.ts +18 -22
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +74 -0
- package/.framework/src/instructions/accounts.md +97 -0
- package/.framework/src/instructions/ai-agents.md +69 -0
- package/.framework/src/instructions/alerts.md +80 -0
- package/.framework/src/instructions/api-keys.md +87 -0
- package/.framework/src/instructions/apps.md +77 -0
- package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
- package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
- package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
- package/.framework/src/instructions/concepts/automation.md +186 -0
- package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
- package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
- package/.framework/src/instructions/concepts/design-schema.md +152 -0
- package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
- package/.framework/src/instructions/concepts/observability.md +116 -0
- package/.framework/src/instructions/concepts/permissions.md +150 -0
- package/.framework/src/instructions/concepts/runtime-records.md +111 -0
- package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
- package/.framework/src/instructions/concepts/spine-way.md +172 -0
- package/.framework/src/instructions/deliveries.md +59 -0
- package/.framework/src/instructions/guide/00-orientation.md +106 -0
- package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
- package/.framework/src/instructions/guide/02-project-map.md +147 -0
- package/.framework/src/instructions/guide/03-domain-model.md +223 -0
- package/.framework/src/instructions/guide/04-first-app.md +227 -0
- package/.framework/src/instructions/guide/05-glossary.md +183 -0
- package/.framework/src/instructions/integrations.md +96 -0
- package/.framework/src/instructions/internal/cil-markers.md +83 -0
- package/.framework/src/instructions/internal/docs-contract.md +158 -0
- package/.framework/src/instructions/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.md +77 -0
- package/.framework/src/instructions/notifications.md +77 -0
- package/.framework/src/instructions/observability.md +61 -0
- package/.framework/src/instructions/operations/deployment.md +115 -0
- package/.framework/src/instructions/operations/migrations.md +125 -0
- package/.framework/src/instructions/operations/security-checklist.md +170 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/people.md +91 -0
- package/.framework/src/instructions/pipeline-executions.md +62 -0
- package/.framework/src/instructions/pipelines.md +78 -0
- package/.framework/src/instructions/prompts.md +95 -0
- package/.framework/src/instructions/reference/api/admin-data.md +162 -0
- package/.framework/src/instructions/reference/api/auth.md +113 -0
- package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
- package/.framework/src/instructions/reference/app-manifest.md +184 -0
- package/.framework/src/instructions/reference/cli.md +124 -0
- package/.framework/src/instructions/reference/database.md +132 -0
- package/.framework/src/instructions/reference/design-schema.md +280 -0
- package/.framework/src/instructions/reference/env-vars.md +133 -0
- package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
- package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
- package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
- package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
- package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
- package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
- package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
- package/.framework/src/instructions/reference/functions/apps.md +140 -0
- package/.framework/src/instructions/reference/functions/auth.md +99 -0
- package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
- package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
- package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
- package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
- package/.framework/src/instructions/reference/functions/index.md +103 -0
- package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
- package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
- package/.framework/src/instructions/reference/functions/integrations.md +130 -0
- package/.framework/src/instructions/reference/functions/invites.md +182 -0
- package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
- package/.framework/src/instructions/reference/functions/kb.md +166 -0
- package/.framework/src/instructions/reference/functions/logs.md +141 -0
- package/.framework/src/instructions/reference/functions/observability.md +105 -0
- package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
- package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
- package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
- package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
- package/.framework/src/instructions/reference/functions/roles.md +121 -0
- package/.framework/src/instructions/reference/functions/security.md +140 -0
- package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
- package/.framework/src/instructions/reference/functions/system.md +106 -0
- package/.framework/src/instructions/reference/functions/tests.md +97 -0
- package/.framework/src/instructions/reference/functions/timers.md +117 -0
- package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
- package/.framework/src/instructions/reference/functions/triggers.md +140 -0
- package/.framework/src/instructions/reference/functions/types.md +146 -0
- package/.framework/src/instructions/reference/seeds.md +126 -0
- package/.framework/src/instructions/reference/shared-modules.md +127 -0
- package/.framework/src/instructions/reference/spine-config.md +114 -0
- package/.framework/src/instructions/roles.md +80 -0
- package/.framework/src/instructions/runtime-collaboration.md +106 -0
- package/.framework/src/instructions/runtime-detail.md +70 -0
- package/.framework/src/instructions/runtime-list.md +77 -0
- package/.framework/src/instructions/security.md +67 -0
- package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
- package/.framework/src/instructions/tasks/add-a-role.md +96 -0
- package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
- package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
- package/.framework/src/instructions/tasks/add-a-type.md +151 -0
- package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
- package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
- package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
- package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
- package/.framework/src/instructions/tasks/document-your-app.md +117 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
- package/.framework/src/instructions/tasks/register-an-app.md +137 -0
- package/.framework/src/instructions/tasks/test-your-code.md +108 -0
- package/.framework/src/instructions/tasks/write-a-function.md +132 -0
- package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
- package/.framework/src/instructions/testing.md +59 -0
- package/.framework/src/instructions/timers.md +84 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/app-mount.ts +88 -0
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/LoginPage.tsx +3 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
- package/.framework/templates/agent/AGENTS.md +167 -0
- package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
- package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
- package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
- package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
- package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +4 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +479 -44
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/app-mount.test.ts +80 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API Keys
|
|
3
|
+
audience: both
|
|
4
|
+
kind: entity
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/api-keys.ts
|
|
9
|
+
- .framework/functions/_shared/hash-api-key.ts
|
|
10
|
+
- .framework/functions/_shared/principal.ts
|
|
11
|
+
- .framework/functions/_shared/logging.ts
|
|
12
|
+
- .framework/src/pages/admin/APIKeysPage.tsx
|
|
13
|
+
- .framework/src/pages/admin/APIKeyDetailPage.tsx
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What this is for
|
|
17
|
+
|
|
18
|
+
An **API key** authenticates callers to Spine — integrations, MCP servers, CLI tooling, and service-to-service calls. A key controls which callers are accepted, how fast they can call, and which permissions they carry.
|
|
19
|
+
|
|
20
|
+
## Fields and settings
|
|
21
|
+
|
|
22
|
+
- **Name** *(required)* — human-readable label; missing name blocks creation.
|
|
23
|
+
- **Integration ID** — optional UUID of the integration this key serves.
|
|
24
|
+
- **App ID** — optional UUID/slug of the owning app (server resolves slugs via `resolveAppUuid`).
|
|
25
|
+
- **Key Type** — `inbound` (external callers into Spine), `outbound` (Spine calling out), `internal` (service-to-service), `cli` (scripts/tooling). Determines how the key is validated.
|
|
26
|
+
- **Key Prefix** — derived server-side: the raw key is `sk_<uuid>` and `key_prefix` stores its first 8 characters for lookup. Not a form field.
|
|
27
|
+
- **Rate Limit (requests/min)** — `rate_limit`, UI range 1–10000. Enforced per key inside `rate_limit_window_seconds` (default 60s, overridable via key `config`). An unset/`0` value falls back to the default 100 — it does not block requests.
|
|
28
|
+
- **Expires At** — optional date; after it the key is rejected.
|
|
29
|
+
- **Machine Type** — machine principal type, e.g. `integration`.
|
|
30
|
+
- **Internal** — marks the key for internal service-to-service use.
|
|
31
|
+
- **Active** — inactive keys are rejected on every call.
|
|
32
|
+
- **Permissions / Scopes / Metadata (JSON)** — `permissions` and `scopes` are string arrays (validated against required permissions on `action=validate`); `metadata` is free-form. Invalid JSON fails creation.
|
|
33
|
+
|
|
34
|
+
**Downstream effects**
|
|
35
|
+
|
|
36
|
+
- The gateway validates `key_prefix` + `key_hash` on every call — inactive, expired, or over-limit keys are rejected.
|
|
37
|
+
- The raw key is returned **once** at create (`raw_key` in the response envelope). It is never stored or returned again — `key_value`/`key_hash` are stripped from all API responses (S-001/F-040).
|
|
38
|
+
- The API has no update action (list/get/create plus `validate`, `revoke`, `usage-logs`) — rotate = create new + revoke old.
|
|
39
|
+
- Keys still referenced in local MCP configs or scripts fail immediately when revoked/deactivated.
|
|
40
|
+
|
|
41
|
+
## Minimal configuration
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"name": "CI deploy key",
|
|
46
|
+
"key_type": "cli",
|
|
47
|
+
"rate_limit": 60,
|
|
48
|
+
"is_active": true
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Full configuration
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"name": "Workbench MCP",
|
|
57
|
+
"key_type": "inbound",
|
|
58
|
+
"app_id": "workbench",
|
|
59
|
+
"rate_limit": 600,
|
|
60
|
+
"expires_at": "2027-01-01",
|
|
61
|
+
"machine_type": "integration",
|
|
62
|
+
"is_active": true,
|
|
63
|
+
"permissions": ["admin-data:read"],
|
|
64
|
+
"scopes": ["admin-data:read", "logs:read"],
|
|
65
|
+
"metadata": { "env": "dev" }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Example
|
|
70
|
+
|
|
71
|
+
A local MCP server authenticates with an `inbound` key scoped to read-only admin-data calls; a separate `cli` key (low rate limit) backs the `spine-framework` CLI. A leaked key gets rotated by creating a replacement and revoking the old one — the audit found a plaintext-stored legacy key and deactivated it (S-001/S-003).
|
|
72
|
+
|
|
73
|
+
## Core files
|
|
74
|
+
|
|
75
|
+
- `.framework/functions/api-keys.ts` — API (create/list/get + `validate`/`revoke`/`usage-logs` actions; mints `sk_<uuid>` raw key)
|
|
76
|
+
- `.framework/functions/_shared/hash-api-key.ts` — `hashApiKey`, `verifyApiKey` (pbkdf2), `omitSecretFields` (strips `key_value`/`key_hash`)
|
|
77
|
+
- `.framework/functions/_shared/logging.ts` — `resolveAppUuid` (app slug → UUID on create)
|
|
78
|
+
- `.framework/functions/_shared/principal.ts` — per-request key resolution (`x-api-key` → prefix lookup → pbkdf2 verify → per-key + per-IP rate limit)
|
|
79
|
+
- `.framework/cli/context.ts` — CLI auth path (F-042)
|
|
80
|
+
- `.framework/src/pages/admin/APIKeysPage.tsx` / `APIKeyDetailPage.tsx` — admin UI
|
|
81
|
+
- Table: `public.api_keys`
|
|
82
|
+
|
|
83
|
+
## Agent notes
|
|
84
|
+
|
|
85
|
+
- Copy `raw_key` at creation — there is no "show key" later. Treat any key whose value was ever visible as burned.
|
|
86
|
+
- Prefer short `expires_at` + documented `metadata.env` over long-lived keys.
|
|
87
|
+
- `key_type: cli` + a `permissions` array is the documented path for scripted/agent access (see S-002 — a first-class scriptable login is still pending).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Apps
|
|
3
|
+
audience: both
|
|
4
|
+
kind: entity
|
|
5
|
+
verified_against: b3b3ae0
|
|
6
|
+
verified_on: 2026-11-01
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/apps.ts
|
|
9
|
+
- .framework/src/pages/admin/AppsPage.tsx
|
|
10
|
+
- .framework/src/pages/admin/AppDetailPage.tsx
|
|
11
|
+
- .framework/migrations/003_core_system_data.sql
|
|
12
|
+
- .framework/migrations/013_get_account_apps_role_filter.sql
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What this is for
|
|
16
|
+
|
|
17
|
+
An **app** is a registered application in Spine. The registry controls navigation visibility, who can open the app (min role), how it renders (generic vs custom), and the JSON configuration the app or its types consume at runtime.
|
|
18
|
+
|
|
19
|
+
## Fields and settings
|
|
20
|
+
|
|
21
|
+
- **Name** — string. Display name in the app switcher and registry.
|
|
22
|
+
- **Slug** — string. Unique identifier; set at creation, rarely changed. Routes and API scoping depend on it.
|
|
23
|
+
- **Description** — string, optional. Short text shown in the app list.
|
|
24
|
+
- **App Type** — select: `system`, `custom`, `marketplace`. Drives how the platform treats the app.
|
|
25
|
+
- **Source** — select: `system`, `custom`, `marketplace`. Tracks where the package came from.
|
|
26
|
+
- **Min Role** — select: `member`, `admin`, `owner`. Lowest role that can see/open the app.
|
|
27
|
+
- **Version** — string. Current version shown in the registry.
|
|
28
|
+
- **Active** — boolean. Inactive apps are hidden and cannot be opened.
|
|
29
|
+
- **System** — boolean. Protects the app from tenant-level deletion.
|
|
30
|
+
- **Icon / Color** — UI presentation for the app tile.
|
|
31
|
+
- **Route Prefix** — string, optional. URL prefix where the app mounts (defaults to `/slug`). `"/"` mounts the app at the site root (exactly one app may own it; the app must render its own not-found); nested prefixes like `/crm/sales` work longest-first. `/spine-framework/*` is reserved. See [Apps and Renderers → Mounting](concepts/apps-and-renderers.md#mounting-directory-or-root).
|
|
32
|
+
- **Renderer** — select: `generic` (renders list/detail from type `design_schema.views`), `custom` (app renders its own pages), `none`.
|
|
33
|
+
- **Config (JSON)** — `config` column; runtime config for the app or its generic renderer.
|
|
34
|
+
- **Nav Items (JSON)** — array of navigation items for the app shell.
|
|
35
|
+
- **Integration Deps (JSON)** — array of integration slugs the app depends on.
|
|
36
|
+
- **Metadata (JSON)** — free-form metadata.
|
|
37
|
+
|
|
38
|
+
**Downstream effects**
|
|
39
|
+
|
|
40
|
+
- The app shell reads `slug`, `route_prefix`, `min_role`, `is_active`, `icon`, `color`, `nav_items` to build navigation and gate access.
|
|
41
|
+
- `renderer = generic` requires every served type to have complete `views` in its `design_schema`.
|
|
42
|
+
- `min_role` raises the floor for the whole app — setting `admin` hides it from members even if record permissions would allow reads.
|
|
43
|
+
|
|
44
|
+
## Minimal configuration
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"slug": "my-app",
|
|
49
|
+
"name": "My App",
|
|
50
|
+
"app_type": "custom",
|
|
51
|
+
"source": "custom",
|
|
52
|
+
"renderer": "custom",
|
|
53
|
+
"is_active": true,
|
|
54
|
+
"min_role": "member"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Full configuration
|
|
59
|
+
|
|
60
|
+
Adds `version`, `icon`, `color`, `route_prefix`, `config` (feature flags, default views), `nav_items` (grouped sidebar entries), `integration_deps` (e.g. `["openai", "sendgrid"]`), and `metadata` (author, homepage, tags).
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
A support app registers with `renderer: "custom"`, `route_prefix: "/answer-once"`, `min_role: "member"`, and `integration_deps: ["openai"]` — the admin renders its own pages but still uses `/api/admin-data` for record CRUD and `types` for its ticket/thread types.
|
|
65
|
+
|
|
66
|
+
## Core files
|
|
67
|
+
|
|
68
|
+
- `.framework/functions/apps.ts` — API (list/get/create/update/delete)
|
|
69
|
+
- `.framework/src/pages/admin/AppsPage.tsx` / `AppDetailPage.tsx` — admin UI
|
|
70
|
+
- `.framework/src/components/app-shell/` — shell, generic renderer, app registry
|
|
71
|
+
- Table: `public.apps` (+ `get_account_apps` RPC for counts)
|
|
72
|
+
|
|
73
|
+
## Agent notes
|
|
74
|
+
|
|
75
|
+
- `renderer = "custom"` still needs `fields` + `record_permissions` on its types so `admin-data` can stamp and validate records.
|
|
76
|
+
- Slug appears in API envelopes and `x-app-id` headers — renaming an in-use slug breaks logging attribution and callers.
|
|
77
|
+
- `marketplace` as a type/source value is under review (F-031) — prefer `custom` for new apps.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "AI agents, prompt configs, and embeddings"
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: concept
|
|
5
|
+
verified_against: f69c37d
|
|
6
|
+
verified_on: 2026-10-29
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/ai-agents.ts
|
|
9
|
+
- .framework/functions/prompt-configs.ts
|
|
10
|
+
- .framework/functions/embeddings.ts
|
|
11
|
+
- .framework/functions/ai-interactions.ts
|
|
12
|
+
- .framework/functions/_shared/agent-runner.ts
|
|
13
|
+
- .framework/functions/_shared/llm.ts
|
|
14
|
+
- .framework/functions/_shared/pipeline-runner.ts
|
|
15
|
+
- .framework/migrations/034_match_embeddings_knn.sql
|
|
16
|
+
- .framework/migrations/044_match_embeddings_filtered_knn.sql
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# AI agents, prompt configs, and embeddings
|
|
20
|
+
|
|
21
|
+
Three config entities shape Spine's AI behavior:
|
|
22
|
+
|
|
23
|
+
- An **AI agent** (`public.ai_agents`) is the *who*: a named model + system
|
|
24
|
+
prompt + tool set + guardrails.
|
|
25
|
+
- A **prompt config** (`public.prompt_configs`) is the *how*: the prompt
|
|
26
|
+
template, model parameters, retrieval sources, and escalation policy applied
|
|
27
|
+
to a conversation.
|
|
28
|
+
- An **embedding** (`public.embeddings`) is the *memory*: per-document vector
|
|
29
|
+
rows that semantic retrieval searches over.
|
|
30
|
+
|
|
31
|
+
All three are ordinary admin-data-managed rows: `ai-agents` and
|
|
32
|
+
`prompt-configs` expose CRUD via their own functions, and the conversational
|
|
33
|
+
runtime lives in `.framework/functions/_shared/agent-runner.ts`.
|
|
34
|
+
|
|
35
|
+
## Agent configuration
|
|
36
|
+
|
|
37
|
+
`ai-agents` (`.framework/functions/ai-agents.ts`) stores, per row:
|
|
38
|
+
|
|
39
|
+
- `name` + `agent_type` — required (`chat`, `analysis`, `automation`,
|
|
40
|
+
`custom` in the admin UI).
|
|
41
|
+
- `model_config` — `{ model, temperature, max_tokens, tools }`. At inference
|
|
42
|
+
time the model resolves as `model_config.model || LLM_DEFAULT_MODEL ||
|
|
43
|
+
'gpt-4o'`; `model_config.tools` is passed through as OpenAI tool definitions
|
|
44
|
+
with `tool_choice: 'auto'`.
|
|
45
|
+
- `system_prompt` — concatenated with the prompt config's `system_prompt`
|
|
46
|
+
(agent first) to form the system message.
|
|
47
|
+
- `tools`, `capabilities`, `constraints`, `metadata` — `metadata
|
|
48
|
+
.default_prompt_config_id` is the fallback used when the thread doesn't
|
|
49
|
+
stamp its own `prompt_config_id`.
|
|
50
|
+
- `is_active` — inactive agents fail `resolveAgentConfig` lookup.
|
|
51
|
+
|
|
52
|
+
## Prompt configs and templating
|
|
53
|
+
|
|
54
|
+
`prompt-configs` (`.framework/functions/prompt-configs.ts`) carries the fields
|
|
55
|
+
the runner consumes per turn: `system_prompt`, `context_template`, `model`
|
|
56
|
+
(default `gpt-4o`), `temperature` (0.7), `max_tokens` (4000),
|
|
57
|
+
`is_multi_turn` (true), `max_history_messages` (20), `confidence_threshold`,
|
|
58
|
+
`escalation_action` / `escalation_target`, `output_mode` / `output_field`,
|
|
59
|
+
`requires_review`, `knowledge_sources`, `available_tools`, and
|
|
60
|
+
`tool_constraints`.
|
|
61
|
+
|
|
62
|
+
`context_template` supports exactly two placeholders, substituted in
|
|
63
|
+
`buildContext`: `{{user_message}}` (the inbound text, truncated to 4000 chars)
|
|
64
|
+
and `{{conversation_history}}` (prior messages rendered as
|
|
65
|
+
`Customer:`/`Agent:` lines, truncated to 4000 chars).
|
|
66
|
+
|
|
67
|
+
## How a thread resolves its agent and prompt
|
|
68
|
+
|
|
69
|
+
`resolveAgentConfig` in `.framework/functions/_shared/agent-runner.ts`:
|
|
70
|
+
|
|
71
|
+
1. Reads the thread (`threads`, joined with its `types` row).
|
|
72
|
+
2. Agent id comes from `thread.data.agent_id`, falling back to the thread
|
|
73
|
+
type's `design_schema.default_agent_id`. No agent → the run aborts.
|
|
74
|
+
3. Prompt config id comes from `thread.data.prompt_config_id`, falling back to
|
|
75
|
+
`agent.metadata.default_prompt_config_id`.
|
|
76
|
+
4. Both rows must be `is_active`.
|
|
77
|
+
|
|
78
|
+
`buildContext` then assembles the model input: combined system prompt, the
|
|
79
|
+
rendered `context_template`, retrieved knowledge excerpts (numbered `[n]` and
|
|
80
|
+
explicitly marked *untrusted* — the prompt instructs the model to treat them as
|
|
81
|
+
evidence, never instructions), `## Conversation History`, `## Tool Results`,
|
|
82
|
+
the filtered `available_tools` list, and finally `## Current Message` with a
|
|
83
|
+
prompt-injection warning.
|
|
84
|
+
|
|
85
|
+
## Invoking an agent
|
|
86
|
+
|
|
87
|
+
The interactive path is `POST /api/ai-agents?action=run` with `{ thread_id,
|
|
88
|
+
user_message, mode }` — `mode` is `step`, `complete`, or `episode` (default
|
|
89
|
+
`complete`). Before any tokens are spent the handler runs a prompt-injection
|
|
90
|
+
preflight (`detectInjection`); the app's `security.prompt_injection` setting
|
|
91
|
+
chooses `off` | `flag` (default) | `block`. Each claimed run debits the
|
|
92
|
+
`agent_step` rate limit.
|
|
93
|
+
|
|
94
|
+
Inside a run, `runAgent`/`runAgentStep` build context, call inference, dispatch
|
|
95
|
+
any `tool_calls` the model returns through `executeToolAction`, persist the
|
|
96
|
+
outbound `messages` row stamped with `agent_id` and `prompt_config_id`, and
|
|
97
|
+
manage the episode lease (see [concurrency](./concurrency-and-idempotency.md)).
|
|
98
|
+
|
|
99
|
+
Pipelines reach AI through stage types, not the `run` action:
|
|
100
|
+
|
|
101
|
+
- `agent_inference` — POSTs `{ context, model, temperature, max_tokens }` to
|
|
102
|
+
`config.webhook_url` via `integration-outbound` (`generic-http`); without a
|
|
103
|
+
`webhook_url` it returns a mock response.
|
|
104
|
+
- `search_knowledge` — retrieval over knowledge sources for the stage config's
|
|
105
|
+
`query`.
|
|
106
|
+
- `generate_embeddings` / `revoke_embeddings` / `kb_ingest` / `kb_revoke` /
|
|
107
|
+
`create_interaction` — delegate to the `embeddings`, `kb`, and
|
|
108
|
+
`ai-interactions` functions described below.
|
|
109
|
+
|
|
110
|
+
## The LLM call itself
|
|
111
|
+
|
|
112
|
+
`_shared/llm.ts` does not hold API keys. `callOpenAI` routes every request
|
|
113
|
+
through `integration-outbound` with `integration_slug: 'openai'` and
|
|
114
|
+
`credential_field: 'api_token'` — credentials come from the `integrations`
|
|
115
|
+
table like any other outbound call. `callOpenAIChat` defaults to
|
|
116
|
+
`gpt-4o-mini`/0.7/4000 tokens, overridable per call and via
|
|
117
|
+
`integration.config.models.chat` / `.embedding`. When no OpenAI integration or
|
|
118
|
+
credential exists, inference returns a clearly-marked mock response rather than
|
|
119
|
+
failing.
|
|
120
|
+
|
|
121
|
+
## Embeddings and retrieval
|
|
122
|
+
|
|
123
|
+
`embeddings` (`.framework/functions/embeddings.ts`) manages the
|
|
124
|
+
`public.embeddings` vector store:
|
|
125
|
+
|
|
126
|
+
- `action=generate` builds embeddable text per **vector type**. The type's
|
|
127
|
+
`design_schema.vector_types` decides which builders run — default
|
|
128
|
+
`['semantic', 'structure']`; registered builders also cover `intent`,
|
|
129
|
+
`cause`, `procedure`, `outcome`, `context`, and `error_signal`. Each vector
|
|
130
|
+
type occupies a fixed `chunk_index` slot so concurrent types never collide
|
|
131
|
+
on the unique constraint, and unchanged content is skipped by hash.
|
|
132
|
+
- `action=revoke` sets `revoked_at` on a document's vectors; `action=approve`
|
|
133
|
+
sets `approved_at` and clears revocation.
|
|
134
|
+
- `action=search-similar` calls the `match_embeddings` RPC (pgvector KNN,
|
|
135
|
+
defined in migrations `034`/`044`) with `query_embedding`, `match_count`,
|
|
136
|
+
`similarity_threshold`, and optional account/vector-type/item-type/document
|
|
137
|
+
filters.
|
|
138
|
+
- `action=search-semantic` is a Postgres `websearch` full-text query over the
|
|
139
|
+
`content` column — the lexical complement to vector search.
|
|
140
|
+
|
|
141
|
+
## Interaction records
|
|
142
|
+
|
|
143
|
+
`ai-interactions` (`.framework/functions/ai-interactions.ts`) writes the
|
|
144
|
+
feedback/observability side of the loop as `items` rows (default type
|
|
145
|
+
`ai_feedback_record`, overridable via `type_slug`; the call returns
|
|
146
|
+
`{ skipped: true }` when the type isn't installed). Actions:
|
|
147
|
+
`submit_feedback`, `create_interaction` (what the pipeline stage calls — it
|
|
148
|
+
stamps `thread_id`, `ai_message_id`, `retrieved_sources`, `ai_confidence`,
|
|
149
|
+
`kb_candidate`, and review fields), `update_interaction`,
|
|
150
|
+
`create_cil_proposal`, and `list`.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Apps and Renderers
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: concept
|
|
5
|
+
verified_against: b3b3ae0
|
|
6
|
+
verified_on: 2026-11-01
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/migrations/001_core_schema.sql
|
|
9
|
+
- .framework/migrations/013_get_account_apps_role_filter.sql
|
|
10
|
+
- .framework/migrations/014_apps_required_roles.sql
|
|
11
|
+
- .framework/src/App.tsx
|
|
12
|
+
- .framework/src/components/AppWrapper.tsx
|
|
13
|
+
- .framework/src/components/CustomAppLoader.tsx
|
|
14
|
+
- .framework/src/components/app-shell/GenericAppShell.tsx
|
|
15
|
+
- .framework/src/components/app-shell/GenericListPage.tsx
|
|
16
|
+
- .framework/src/components/app-shell/GenericDetailPage.tsx
|
|
17
|
+
- .framework/functions/_shared/app-manifest.ts
|
|
18
|
+
- .framework/functions/apps.ts
|
|
19
|
+
- .framework/cli/commands/create-app.ts
|
|
20
|
+
- .framework/cli/commands/install-app.ts
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Apps and Renderers
|
|
24
|
+
|
|
25
|
+
An app in Spine is a **row in `public.apps`** plus — for apps that render their
|
|
26
|
+
own UI — a directory of code. The registry row decides where the app mounts,
|
|
27
|
+
who can open it, and *how* it renders. The `renderer` column is the fork in
|
|
28
|
+
the road.
|
|
29
|
+
|
|
30
|
+
## The registry row
|
|
31
|
+
|
|
32
|
+
`.framework/migrations/001_core_schema.sql` defines `public.apps`: unique
|
|
33
|
+
`slug`, `name`, `route_prefix`, `renderer` (CHECK constraint:
|
|
34
|
+
`'generic' | 'custom' | 'none'`), `min_role`, `required_roles` (added in
|
|
35
|
+
migration 014), `nav_items`, `config`, `integration_deps`, `metadata`,
|
|
36
|
+
`is_active`, `is_system`, `owner_account_id`, domains.
|
|
37
|
+
|
|
38
|
+
- `route_prefix` (defaults to `/<slug>`) is where the SPA mounts the app —
|
|
39
|
+
`.framework/src/App.tsx` builds a `<Route>` per active app.
|
|
40
|
+
- `is_active: false` removes the app from navigation and routing.
|
|
41
|
+
- `is_system: true` protects the row from tenant-level deletion.
|
|
42
|
+
|
|
43
|
+
## Mounting: directory or root
|
|
44
|
+
|
|
45
|
+
`route_prefix` is the whole mount contract. Every routable app
|
|
46
|
+
(`route_prefix != null`, `renderer !== 'none'`) becomes a
|
|
47
|
+
`<Route path="<prefix>/*">` in `.framework/src/App.tsx`:
|
|
48
|
+
|
|
49
|
+
- **Directory mount** — `"/portal"` → the app owns `/portal/*`.
|
|
50
|
+
- **Root mount** — `"/"` → the app owns `/*` and renders directly at `/` for
|
|
51
|
+
non-admin users.
|
|
52
|
+
- **Nested mount** — `"/crm/sales"` works mechanically (`/crm/sales/*`);
|
|
53
|
+
prefixes sort longest-first, so `/crm/sales` wins over `/crm`. Supported
|
|
54
|
+
but unusual.
|
|
55
|
+
|
|
56
|
+
### Host-keyed mount overrides
|
|
57
|
+
|
|
58
|
+
`prod_domain` + `prod_route_prefix` (and the `preview_` pair for
|
|
59
|
+
`VITE_ENVIRONMENT=preview`) let one row mount differently per hostname
|
|
60
|
+
(`.framework/src/lib/app-mount.ts`, resolved in `useApps`/`AppContext` before
|
|
61
|
+
records reach the router):
|
|
62
|
+
|
|
63
|
+
- Host matches `prod_domain` → app mounts at `prod_route_prefix` (or
|
|
64
|
+
`route_prefix` when no override is set).
|
|
65
|
+
- Host doesn't match, override set → still routable at `route_prefix`.
|
|
66
|
+
- Host doesn't match, no override → filtered out (legacy domain-lock).
|
|
67
|
+
|
|
68
|
+
So `route_prefix: "/demo"` + `prod_domain: "demo.example.com"` +
|
|
69
|
+
`prod_route_prefix: "/"` serves the app at `/` on its own site while it
|
|
70
|
+
stays at `/demo` everywhere else. `manifest.json` carries all four fields;
|
|
71
|
+
`validate-app` requires the matching domain whenever an override prefix is
|
|
72
|
+
declared. The unique index `idx_apps_route_prefix_domain` allows `/` per
|
|
73
|
+
prod domain.
|
|
74
|
+
|
|
75
|
+
Rules that are easy to get wrong:
|
|
76
|
+
|
|
77
|
+
- **Exactly one app may own `/`.** Two root-mounted apps both compile to
|
|
78
|
+
`/*` and resolution order silently decides which renders — the registry
|
|
79
|
+
does not prevent this; don't create it.
|
|
80
|
+
- **A root-mounted app shadows the global not-found page.** Its `/*` route
|
|
81
|
+
matches every path the more-specific routes don't, so the app must render
|
|
82
|
+
its own not-found for unknown internal paths (a catch-all
|
|
83
|
+
`<Route path="*">` inside its `index.tsx` routes).
|
|
84
|
+
- **`system_admin` always lands on `/spine-framework/admin`.**
|
|
85
|
+
Root-mounting changes what *members* see at `/`, not admins. Non-admin
|
|
86
|
+
landing at `/`: root-mounted app if one exists → else redirect to the
|
|
87
|
+
first app's prefix → else `NoAppPage`.
|
|
88
|
+
- **App-internal navigation must be mount-agnostic.** Use
|
|
89
|
+
`useAppPath()` from `@core/hooks/useAppPath` (or relative paths inside the
|
|
90
|
+
app's `<Routes>`) for every link and `navigate()` call — it returns the
|
|
91
|
+
prefix (`''` when mounted at `/`). A hardcoded `navigate('/dashboard')`
|
|
92
|
+
escapes a directory-mounted app. See [Frontend SDK](../reference/frontend-sdk.md).
|
|
93
|
+
- **`nav_items[].path` accepts two forms** (`GenericAppShell` navigates
|
|
94
|
+
absolute paths verbatim and prefixes relative ones): absolute
|
|
95
|
+
*including* the prefix (`/taskboard/tasks`) or relative (`tasks` →
|
|
96
|
+
resolved to `/taskboard/tasks`). The broken form is absolute *without*
|
|
97
|
+
the prefix (`/tasks` escapes the app). For a root-mounted app the
|
|
98
|
+
absolute form is simply `/…`.
|
|
99
|
+
- **`/spine-framework/*` is reserved** (admin, API docs, CLI pages mount
|
|
100
|
+
there ahead of app routes). Never pick a colliding prefix.
|
|
101
|
+
- **Apps are always behind login.** `is_public`/`auth_required` exist on
|
|
102
|
+
the manifest, but the unauthenticated router only serves auth pages —
|
|
103
|
+
a root-mounted app is not a public site mechanism.
|
|
104
|
+
|
|
105
|
+
Change the mount point via `manifest.route_prefix` + `update-db-app <slug>`
|
|
106
|
+
(or the admin app detail page). On package reinstall the local
|
|
107
|
+
`manifest.json` is preserved, so the file is the durable source.
|
|
108
|
+
|
|
109
|
+
## Role gating
|
|
110
|
+
|
|
111
|
+
Two layers decide who sees an app:
|
|
112
|
+
|
|
113
|
+
- Server side, `get_account_apps` filters the app list by the caller's role
|
|
114
|
+
slugs (`.framework/functions/apps.ts`;
|
|
115
|
+
`.framework/migrations/013_get_account_apps_role_filter.sql`).
|
|
116
|
+
- Client side, `AppWrapper` computes `required_roles` (falling back to
|
|
117
|
+
`[min_role]`) and renders an access-denied screen unless the user holds
|
|
118
|
+
`system_admin` or one of those roles (`.framework/src/components/AppWrapper.tsx`).
|
|
119
|
+
|
|
120
|
+
This gates *opening the app*; it does not replace record-level
|
|
121
|
+
`record_permissions` on the data underneath.
|
|
122
|
+
|
|
123
|
+
## `renderer = 'generic'`
|
|
124
|
+
|
|
125
|
+
The platform renders the app's list and detail pages for you, straight from
|
|
126
|
+
each type's `design_schema.views`:
|
|
127
|
+
|
|
128
|
+
- `App.tsx` mounts `GenericAppShell` for `renderer === 'generic'` apps.
|
|
129
|
+
- `GenericAppShell` builds the sidebar from the app's `nav_items` and, for
|
|
130
|
+
every nav item carrying `type_slug` (and optional `view`), generates
|
|
131
|
+
list/detail/new routes backed by `GenericListPage` and
|
|
132
|
+
`GenericDetailPage`.
|
|
133
|
+
- `GenericListPage` turns `views.default_list` (or the nav item's named view)
|
|
134
|
+
into sortable columns; `GenericDetailPage` renders `views.default_detail`
|
|
135
|
+
sections with per-field read/write awareness.
|
|
136
|
+
|
|
137
|
+
**What you get for free:** a working list page, detail page, create/edit form,
|
|
138
|
+
sorting, filters, field-type rendering, and permission-aware editing — with
|
|
139
|
+
zero app code. **What it costs:** every type the app serves must have complete
|
|
140
|
+
`fields`, `views.default_list`, and `views.default_detail`, or the pages fail
|
|
141
|
+
with "No schema available" / schema-load errors. See
|
|
142
|
+
[Design Schema](design-schema.md).
|
|
143
|
+
|
|
144
|
+
## `renderer = 'custom'`
|
|
145
|
+
|
|
146
|
+
The app ships its own React code under `custom/apps/<slug>/` (a
|
|
147
|
+
project-owned directory, not part of the framework package). `App.tsx` mounts
|
|
148
|
+
`CustomAppLoader`, which resolves the app through an
|
|
149
|
+
`import.meta.glob('custom/apps/*/index.tsx')` — the app's `index.tsx` is its
|
|
150
|
+
entry point and default-exports the routed component (the `create-app`
|
|
151
|
+
scaffold generates exactly this shape).
|
|
152
|
+
|
|
153
|
+
A custom app renders its own pages but still uses the platform for data:
|
|
154
|
+
`/api/admin-data` for runtime CRUD, `/api/types` for schemas. Custom apps
|
|
155
|
+
must never write their own CRUD wrappers — that bypasses permissions,
|
|
156
|
+
validation, audit, and triggers (see [Runtime Records](runtime-records.md)).
|
|
157
|
+
|
|
158
|
+
`renderer = 'none'` registers the app (data, types, integrations) with no UI
|
|
159
|
+
surface.
|
|
160
|
+
|
|
161
|
+
## The manifest
|
|
162
|
+
|
|
163
|
+
`custom/apps/<slug>/manifest.json` is the app's declarative half, validated by
|
|
164
|
+
`loadManifest` in `.framework/functions/_shared/app-manifest.ts` (the
|
|
165
|
+
`AppManifest` interface is the contract):
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"name": "My App",
|
|
170
|
+
"slug": "my-app",
|
|
171
|
+
"description": "…",
|
|
172
|
+
"version": "1.0.0",
|
|
173
|
+
"required_roles": ["member"],
|
|
174
|
+
"route_prefix": "/my-app",
|
|
175
|
+
"renderer": "custom",
|
|
176
|
+
"routes": ["/my-app", "/my-app/dashboard"],
|
|
177
|
+
"nav_items": [{ "title": "Dashboard", "path": "/my-app/dashboard", "icon": "LayoutDashboard", "order": 1 }],
|
|
178
|
+
"features": [],
|
|
179
|
+
"dependencies": [],
|
|
180
|
+
"entry_point": "./index.tsx",
|
|
181
|
+
"is_public": false,
|
|
182
|
+
"auth_required": true
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Validated requirements: non-empty `name`, `slug` matching
|
|
187
|
+
`^[a-z][a-z0-9-]*$` (and equal to the directory name for discovery), and
|
|
188
|
+
`required_roles` as an array of strings. Optional keys the install flow also
|
|
189
|
+
honors: `config` (merged onto the apps row), `seed` (declared seed entries),
|
|
190
|
+
`owned_types`, `registration`, `metadata`, `min_role`,
|
|
191
|
+
`preview_domain`/`prod_domain`.
|
|
192
|
+
|
|
193
|
+
## Install and seed flow
|
|
194
|
+
|
|
195
|
+
Two paths put an app on disk and in the registry:
|
|
196
|
+
|
|
197
|
+
- **`npx spine-framework create-app <slug>`** — scaffolds
|
|
198
|
+
`custom/apps/<slug>/` (`manifest.json` + `index.tsx` + `components/`) and
|
|
199
|
+
inserts a `public.apps` row with `renderer: 'custom'` and
|
|
200
|
+
`route_prefix: /<slug>` (`.framework/cli/commands/create-app.ts`).
|
|
201
|
+
- **`npx spine-framework install-app <package>`** — copies a packaged app into
|
|
202
|
+
`custom/apps/<slug>/`, runs migrations, then `seedApp`:
|
|
203
|
+
- upserts the `apps` row from the manifest (`appRowPayload`: name,
|
|
204
|
+
description, version, nav_items, route_prefix, renderer, required_roles,
|
|
205
|
+
min_role, config merge, metadata);
|
|
206
|
+
- applies seed files in dependency order — conventional
|
|
207
|
+
`seed/roles.json` → `roles` (conflict `slug`), `seed/types.json` →
|
|
208
|
+
`types` (conflict `kind,slug`, **no** `app_id` injection,
|
|
209
|
+
`ignoreDuplicates`) — plus any `manifest.seed` entries;
|
|
210
|
+
- refuses to overwrite a `types` row whose `design_schema` differs from the
|
|
211
|
+
seed unless the slug is declared in `manifest.owned_types` and
|
|
212
|
+
`--reconcile-owned-types` was passed (types are global — see
|
|
213
|
+
[Scope and Accounts](scope-and-accounts.md));
|
|
214
|
+
- syncs the `registration` block into `spine.config.json` (the project-owned
|
|
215
|
+
config file at the project root).
|
|
216
|
+
- **`update-db-app <slug>`** re-syncs a local `manifest.json` to the DB without
|
|
217
|
+
touching files; **`seed-app <slug>`** re-runs seeds;
|
|
218
|
+
**`uninstall-app`** removes the app (`--keep-data` preserves seeded rows).
|
|
219
|
+
All in `.framework/cli/commands/install-app.ts`.
|
|
220
|
+
|
|
221
|
+
## The practical split
|
|
222
|
+
|
|
223
|
+
Use `generic` when the app is "records in, records out" — lists, detail forms,
|
|
224
|
+
admin-style editing — and keep views accurate. Use `custom` when the UX is a
|
|
225
|
+
product surface (dashboards, wizards, bespoke layouts) and treat the manifest
|
|
226
|
+
as your app's contract: registration, roles, seeds, nav. Either way, types and
|
|
227
|
+
`design_schema` do the data modeling — the renderer only decides who draws the
|
|
228
|
+
pixels.
|