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,392 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
// S-007: machine principals resolve to the service-role adminDb via
|
|
4
|
+
// getPrincipalDb, so HTTP handlers that query on ctx.db or adminDb without an
|
|
5
|
+
// account filter were reachable cross-account by any API key. These tests pin
|
|
6
|
+
// the gates: non-system principals are scoped to ctx.accountId (or denied
|
|
7
|
+
// outright for platform-wide actions); system_admin and the internal
|
|
8
|
+
// SYSTEM_PRINCIPAL keep working.
|
|
9
|
+
|
|
10
|
+
vi.mock('../../functions/_shared/middleware.ts', () => ({
|
|
11
|
+
createHandler: (fn: any) => fn,
|
|
12
|
+
json: vi.fn(),
|
|
13
|
+
error: vi.fn(),
|
|
14
|
+
parseBody: vi.fn(),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
vi.mock('../../functions/_shared/db.ts', () => ({
|
|
18
|
+
adminDb: {
|
|
19
|
+
from: vi.fn(),
|
|
20
|
+
rpc: vi.fn(),
|
|
21
|
+
},
|
|
22
|
+
getUserDb: vi.fn(() => ({})),
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
vi.mock('../../functions/_shared/audit.ts', () => ({
|
|
26
|
+
emitAudit: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
emitLog: vi.fn().mockResolvedValue(undefined),
|
|
28
|
+
emitSpan: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
emitActivity: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
vi.mock('../../functions/_shared/permissions.ts', () => ({
|
|
33
|
+
sanitizeRecordData: vi.fn(async (_ctx: any, record: any) => record),
|
|
34
|
+
}))
|
|
35
|
+
|
|
36
|
+
vi.mock('../../functions/_shared/llm.ts', () => ({
|
|
37
|
+
callOpenAIEmbeddings: vi.fn(async (_ctx: any, inputs: string[]) => ({
|
|
38
|
+
data: inputs.map((_t, i) => ({ index: i, embedding: [0.1, 0.2, 0.3] })),
|
|
39
|
+
})),
|
|
40
|
+
callOpenAIChat: vi.fn(async () => ({ choices: [{ message: { content: 'summary' } }] })),
|
|
41
|
+
callOpenAI: vi.fn(async () => ({})),
|
|
42
|
+
callEmbeddingApi: vi.fn(async (inputs: string[]) => inputs.map(() => '[0.1,0.2]')),
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
vi.mock('../../functions/_shared/logging.ts', () => ({
|
|
46
|
+
resolveAppUuid: vi.fn(async (ref: any) => ref ?? null),
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
// admin-data pulls in permissions/trigger-engine — integration-outbound only
|
|
50
|
+
// calls get/update when record_id / update_record are set.
|
|
51
|
+
vi.mock('../../functions/admin-data.ts', () => ({
|
|
52
|
+
get: vi.fn(),
|
|
53
|
+
update: vi.fn(),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
import { adminDb } from '../../functions/_shared/db.ts'
|
|
57
|
+
import { cleanup as logsCleanup } from '../../functions/logs.ts'
|
|
58
|
+
import {
|
|
59
|
+
list as apiKeysList,
|
|
60
|
+
get as apiKeysGet,
|
|
61
|
+
validate as apiKeysValidate,
|
|
62
|
+
revoke as apiKeysRevoke,
|
|
63
|
+
listUsageLogs,
|
|
64
|
+
} from '../../functions/api-keys.ts'
|
|
65
|
+
import {
|
|
66
|
+
generate as embeddingsGenerate,
|
|
67
|
+
revoke as embeddingsRevoke,
|
|
68
|
+
approve as embeddingsApprove,
|
|
69
|
+
list as embeddingsList,
|
|
70
|
+
searchSimilar,
|
|
71
|
+
} from '../../functions/embeddings.ts'
|
|
72
|
+
import { execute as executeOutbound } from '../../functions/integration-outbound.ts'
|
|
73
|
+
|
|
74
|
+
// Chainable query-builder stub — every filter/terminal method returns the
|
|
75
|
+
// builder except single/maybeSingle/await, which resolve to the result.
|
|
76
|
+
function q(result: { data?: any; error?: any; count?: any }) {
|
|
77
|
+
const b: any = {}
|
|
78
|
+
const chainers = ['select', 'eq', 'in', 'is', 'ilike', 'filter', 'not', 'order',
|
|
79
|
+
'limit', 'update', 'insert', 'upsert', 'delete', 'neq', 'or', 'contains',
|
|
80
|
+
'gte', 'lte', 'lt', 'range', 'textSearch', 'or']
|
|
81
|
+
for (const m of chainers) b[m] = vi.fn(() => b)
|
|
82
|
+
b.single = vi.fn(async () => result)
|
|
83
|
+
b.maybeSingle = vi.fn(async () => result)
|
|
84
|
+
b.then = (onF: any, onR: any) => Promise.resolve(result).then(onF, onR)
|
|
85
|
+
return b
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let queue: any[] = []
|
|
89
|
+
let built: any[] = []
|
|
90
|
+
let dbBuilt: any[] = []
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
queue = []
|
|
94
|
+
built = []
|
|
95
|
+
dbBuilt = []
|
|
96
|
+
vi.clearAllMocks()
|
|
97
|
+
vi.mocked(adminDb.from).mockImplementation(() => {
|
|
98
|
+
const b = queue.length ? queue.shift() : q({ data: null, error: null })
|
|
99
|
+
built.push(b)
|
|
100
|
+
return b
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// ctx.db for non-system callers. In production a machine principal's ctx.db IS
|
|
105
|
+
// adminDb — the tests assert the .eq('account_id', ...) filter lands on it.
|
|
106
|
+
function stubDb() {
|
|
107
|
+
return {
|
|
108
|
+
from: vi.fn((table: string) => {
|
|
109
|
+
const b = queue.length ? queue.shift() : q({ data: null, error: null })
|
|
110
|
+
;(b as any).__table = table
|
|
111
|
+
dbBuilt.push(b)
|
|
112
|
+
return b
|
|
113
|
+
}),
|
|
114
|
+
rpc: vi.fn(async () => ({ data: [], error: null })),
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const scopedMachine = (accountId: string | null = 'acct-1', extra: any = {}) => ({
|
|
119
|
+
id: 'key-1',
|
|
120
|
+
type: 'machine',
|
|
121
|
+
accountId,
|
|
122
|
+
roles: [],
|
|
123
|
+
scopes: ['logs:read'],
|
|
124
|
+
isInternal: false,
|
|
125
|
+
machineType: 'integration',
|
|
126
|
+
provenance: { sourceType: 'api_key' },
|
|
127
|
+
...extra,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const internalScopedMachine = (accountId: string | null = 'acct-1') => ({
|
|
131
|
+
...scopedMachine(accountId),
|
|
132
|
+
isInternal: true,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const systemAdmin = () => ({
|
|
136
|
+
id: 'user-1',
|
|
137
|
+
type: 'human',
|
|
138
|
+
accountId: 'acct-1',
|
|
139
|
+
roles: ['system_admin'],
|
|
140
|
+
provenance: { sourceType: 'jwt' },
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const systemPrincipal = () => ({
|
|
144
|
+
id: 'system',
|
|
145
|
+
type: 'machine',
|
|
146
|
+
accountId: null,
|
|
147
|
+
roles: [],
|
|
148
|
+
scopes: ['*:*'],
|
|
149
|
+
isInternal: true,
|
|
150
|
+
machineType: 'internal',
|
|
151
|
+
provenance: { sourceType: 'cron' },
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
function makeCtx(principal: any, overrides: any = {}) {
|
|
155
|
+
return {
|
|
156
|
+
requestId: 'req-1',
|
|
157
|
+
principal,
|
|
158
|
+
accountId: principal?.accountId ?? null,
|
|
159
|
+
db: stubDb(),
|
|
160
|
+
query: {},
|
|
161
|
+
headers: {},
|
|
162
|
+
...overrides,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
describe('logs cleanup gate (S-007)', () => {
|
|
167
|
+
it('denies a scoped machine principal with statusCode 403', async () => {
|
|
168
|
+
const ctx = makeCtx(scopedMachine())
|
|
169
|
+
await expect(logsCleanup(ctx, { days_to_keep: 30 })).rejects.toMatchObject({ statusCode: 403 })
|
|
170
|
+
expect(ctx.db.from).not.toHaveBeenCalled()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('allows a system_admin principal', async () => {
|
|
174
|
+
queue.push(q({ data: [{ id: 'l1' }], error: null }))
|
|
175
|
+
const ctx = makeCtx(systemAdmin())
|
|
176
|
+
const res = await logsCleanup(ctx, { days_to_keep: 30 })
|
|
177
|
+
expect(res.deleted_count).toBe(1)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('allows the internal SYSTEM_PRINCIPAL (no account binding)', async () => {
|
|
181
|
+
queue.push(q({ data: [{ id: 'l1' }], error: null }))
|
|
182
|
+
const ctx = makeCtx(systemPrincipal())
|
|
183
|
+
const res = await logsCleanup(ctx, {})
|
|
184
|
+
expect(res.deleted_count).toBe(1)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('api-keys account scoping (S-007)', () => {
|
|
189
|
+
it('get scopes the lookup to ctx.accountId for a machine principal', async () => {
|
|
190
|
+
const ctx = makeCtx(scopedMachine())
|
|
191
|
+
await expect(apiKeysGet({ ...ctx, query: { id: 'key-9' } }, {})).resolves.toBeDefined()
|
|
192
|
+
const b = dbBuilt[0]
|
|
193
|
+
expect(b.eq).toHaveBeenCalledWith('id', 'key-9')
|
|
194
|
+
expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('get does not scope for system_admin', async () => {
|
|
198
|
+
queue.push(q({ data: { id: 'key-9', name: 'k' }, error: null }))
|
|
199
|
+
await apiKeysGet({ ...makeCtx(systemAdmin()), query: { id: 'key-9' } }, {})
|
|
200
|
+
const b = built[0]
|
|
201
|
+
const accountEq = b.eq.mock.calls.filter((c: any[]) => c[0] === 'account_id')
|
|
202
|
+
expect(accountEq).toHaveLength(0)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('get rejects a non-system principal without account context', async () => {
|
|
206
|
+
const ctx = makeCtx(scopedMachine(null))
|
|
207
|
+
await expect(apiKeysGet({ ...ctx, query: { id: 'key-9' } }, {})).rejects.toThrow('Account context required')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('validate scopes the key lookup to ctx.accountId', async () => {
|
|
211
|
+
const ctx = makeCtx(scopedMachine())
|
|
212
|
+
const res = await apiKeysValidate(ctx, { key_value: 'sk_abcdef123456' })
|
|
213
|
+
expect(res.is_valid).toBe(false)
|
|
214
|
+
const b = dbBuilt[0]
|
|
215
|
+
expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('validate requires account context for non-system principals', async () => {
|
|
219
|
+
const ctx = makeCtx(scopedMachine(null))
|
|
220
|
+
await expect(apiKeysValidate(ctx, { key_value: 'sk_abcdef123456' })).rejects.toThrow('Account context required')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('revoke scopes the update to ctx.accountId', async () => {
|
|
224
|
+
queue.push(q({ data: { id: 'key-9' }, error: null }))
|
|
225
|
+
const ctx = makeCtx(scopedMachine())
|
|
226
|
+
await apiKeysRevoke(ctx, { id: 'key-9' })
|
|
227
|
+
const b = dbBuilt[0]
|
|
228
|
+
expect(b.eq).toHaveBeenCalledWith('id', 'key-9')
|
|
229
|
+
expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('usage-logs scopes the query to ctx.accountId', async () => {
|
|
233
|
+
const ctx = makeCtx(scopedMachine())
|
|
234
|
+
await listUsageLogs(ctx, {})
|
|
235
|
+
const b = dbBuilt[0]
|
|
236
|
+
expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('list scopes the query to ctx.accountId for machine principals', async () => {
|
|
240
|
+
const ctx = makeCtx(scopedMachine())
|
|
241
|
+
await apiKeysList(ctx, {})
|
|
242
|
+
const b = dbBuilt[0]
|
|
243
|
+
expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe('embeddings account scoping (S-007)', () => {
|
|
248
|
+
const item = (accountId: string | null) => ({
|
|
249
|
+
id: 'item-1',
|
|
250
|
+
title: 'Doc',
|
|
251
|
+
description: 'body',
|
|
252
|
+
account_id: accountId,
|
|
253
|
+
data: {},
|
|
254
|
+
type: { slug: 'kb_article', design_schema: {} },
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('generate rejects a scoped caller on another account\u2019s item with 403', async () => {
|
|
258
|
+
queue.push(q({ data: item('acct-2'), error: null }))
|
|
259
|
+
const ctx = makeCtx(scopedMachine())
|
|
260
|
+
await expect(embeddingsGenerate(ctx, { item_id: 'item-1' })).rejects.toMatchObject({ statusCode: 403 })
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('generate writes vectors under ctx.accountId and ignores account_id_override for scoped callers', async () => {
|
|
264
|
+
queue.push(q({ data: item('acct-1'), error: null })) // items fetch
|
|
265
|
+
queue.push(q({ data: [], error: null })) // existing embeddings
|
|
266
|
+
const ctx = makeCtx(scopedMachine())
|
|
267
|
+
const res = await embeddingsGenerate(ctx, { item_id: 'item-1', account_id_override: 'acct-9' })
|
|
268
|
+
expect(res.embeddings_created).toBeGreaterThan(0)
|
|
269
|
+
const insertCalls = built.flatMap(b => b.insert?.mock.calls ?? [])
|
|
270
|
+
expect(insertCalls.length).toBeGreaterThan(0)
|
|
271
|
+
for (const [row] of insertCalls) {
|
|
272
|
+
expect(row.account_id).toBe('acct-1')
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('generate honors account_id_override for system_admin', async () => {
|
|
277
|
+
queue.push(q({ data: item('acct-1'), error: null }))
|
|
278
|
+
queue.push(q({ data: [], error: null }))
|
|
279
|
+
const ctx = makeCtx(systemAdmin())
|
|
280
|
+
await embeddingsGenerate(ctx, { item_id: 'item-1', account_id_override: 'acct-9' })
|
|
281
|
+
const insertCalls = built.flatMap(b => b.insert?.mock.calls ?? [])
|
|
282
|
+
expect(insertCalls.length).toBeGreaterThan(0)
|
|
283
|
+
for (const [row] of insertCalls) {
|
|
284
|
+
expect(row.account_id).toBe('acct-9')
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('revoke scopes the update to ctx.accountId', async () => {
|
|
289
|
+
queue.push(q({ data: [{ id: 'e1' }], error: null }))
|
|
290
|
+
const ctx = makeCtx(scopedMachine())
|
|
291
|
+
const res = await embeddingsRevoke(ctx, { document_id: 'doc-1' })
|
|
292
|
+
expect(res.revoked_count).toBe(1)
|
|
293
|
+
const b = built[0]
|
|
294
|
+
expect(b.eq).toHaveBeenCalledWith('document_id', 'doc-1')
|
|
295
|
+
expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it('revoke does not scope for system_admin', async () => {
|
|
299
|
+
queue.push(q({ data: [{ id: 'e1' }], error: null }))
|
|
300
|
+
const ctx = makeCtx(systemAdmin())
|
|
301
|
+
await embeddingsRevoke(ctx, { document_id: 'doc-1' })
|
|
302
|
+
const accountEq = built[0].eq.mock.calls.filter((c: any[]) => c[0] === 'account_id')
|
|
303
|
+
expect(accountEq).toHaveLength(0)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('approve scopes the update to ctx.accountId', async () => {
|
|
307
|
+
queue.push(q({ data: [{ id: 'e1' }], error: null }))
|
|
308
|
+
const ctx = makeCtx(scopedMachine())
|
|
309
|
+
const res = await embeddingsApprove(ctx, { document_id: 'doc-1' })
|
|
310
|
+
expect(res.approved_count).toBe(1)
|
|
311
|
+
expect(built[0].eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('list scopes embeddings to ctx.accountId for machine principals', async () => {
|
|
315
|
+
const ctx = makeCtx(scopedMachine())
|
|
316
|
+
await embeddingsList(ctx, {})
|
|
317
|
+
expect(dbBuilt[0].eq).toHaveBeenCalledWith('account_id', 'acct-1')
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('searchSimilar forces filter_account_ids to ctx.accountId for scoped callers', async () => {
|
|
321
|
+
const ctx = makeCtx(scopedMachine())
|
|
322
|
+
await searchSimilar(ctx, {
|
|
323
|
+
query_embedding: [0.1],
|
|
324
|
+
filter_account_ids: ['acct-2'],
|
|
325
|
+
})
|
|
326
|
+
const rpcArgs = vi.mocked(ctx.db.rpc).mock.calls[0][1] as any
|
|
327
|
+
expect(rpcArgs.filter_account_ids).toEqual(['acct-1'])
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
describe('integration-outbound delivery_id account check (S-007)', () => {
|
|
332
|
+
const delivery = (accountId: string | null) => ({
|
|
333
|
+
id: 'del-1',
|
|
334
|
+
account_id: accountId,
|
|
335
|
+
direction: 'outbound',
|
|
336
|
+
status: 'pending',
|
|
337
|
+
attempt_count: 0,
|
|
338
|
+
max_attempts: 3,
|
|
339
|
+
payload: {
|
|
340
|
+
integration_slug: 'generic-http',
|
|
341
|
+
action_config: { url: 'https://hooks.example.com/in' },
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('rejects a scoped internal key replaying another account\u2019s delivery with 403', async () => {
|
|
346
|
+
queue.push(q({ data: delivery('acct-2'), error: null }))
|
|
347
|
+
const ctx = makeCtx(internalScopedMachine())
|
|
348
|
+
await expect(executeOutbound(ctx, { delivery_id: 'del-1' })).rejects.toMatchObject({ statusCode: 403 })
|
|
349
|
+
// Nothing beyond the delivery lookup should have run.
|
|
350
|
+
expect(vi.mocked(adminDb.from)).toHaveBeenCalledTimes(1)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('rejects a non-internal scoped principal touching another account\u2019s delivery with 403', async () => {
|
|
354
|
+
queue.push(q({ data: delivery('acct-2'), error: null }))
|
|
355
|
+
const ctx = makeCtx(scopedMachine())
|
|
356
|
+
await expect(
|
|
357
|
+
executeOutbound(ctx, { delivery_id: 'del-1', integration_slug: 'generic-http', action_config: {} })
|
|
358
|
+
).rejects.toMatchObject({ statusCode: 403 })
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('lets a scoped internal key replay a delivery in its own account', async () => {
|
|
362
|
+
queue.push(q({ data: delivery('acct-1'), error: null })) // delivery select
|
|
363
|
+
queue.push(q({ data: null, error: null })) // processing update
|
|
364
|
+
queue.push(q({ data: { id: 'int-1', slug: 'generic-http', credentials: {}, config: { outbound_adapter: 'adapters/generic-webhook-outbound' }, is_active: true }, error: null })) // tenant integration
|
|
365
|
+
queue.push(q({ data: null, error: null })) // result update
|
|
366
|
+
|
|
367
|
+
const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, text: async () => '{}' })
|
|
368
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
369
|
+
|
|
370
|
+
const ctx = makeCtx(internalScopedMachine())
|
|
371
|
+
const res = await executeOutbound(ctx, { delivery_id: 'del-1' })
|
|
372
|
+
expect(res.success).toBe(true)
|
|
373
|
+
expect(res.is_retry).toBe(true)
|
|
374
|
+
vi.unstubAllGlobals()
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('lets the SYSTEM_PRINCIPAL replay any account\u2019s delivery (cron retry path)', async () => {
|
|
378
|
+
queue.push(q({ data: delivery('acct-2'), error: null })) // delivery select
|
|
379
|
+
queue.push(q({ data: null, error: null })) // processing update
|
|
380
|
+
queue.push(q({ data: { id: 'int-1', slug: 'generic-http', credentials: {}, config: { outbound_adapter: 'adapters/generic-webhook-outbound' }, is_active: true }, error: null })) // system integration
|
|
381
|
+
queue.push(q({ data: null, error: null })) // result update
|
|
382
|
+
|
|
383
|
+
const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, text: async () => '{}' })
|
|
384
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
385
|
+
|
|
386
|
+
const ctx = makeCtx(systemPrincipal(), { accountId: null })
|
|
387
|
+
const res = await executeOutbound(ctx, { delivery_id: 'del-1' })
|
|
388
|
+
expect(res.success).toBe(true)
|
|
389
|
+
expect(res.is_retry).toBe(true)
|
|
390
|
+
vi.unstubAllGlobals()
|
|
391
|
+
})
|
|
392
|
+
})
|