@segmentstream/ontology 0.0.0-stage → 0.92.0
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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +1655 -2
- package/dist/action.d.ts +193 -0
- package/dist/action.js +41 -0
- package/dist/agent-chat.d.ts +44 -0
- package/dist/agent-chat.js +91 -0
- package/dist/agent-task-contract.d.ts +64 -0
- package/dist/agent-task-contract.js +15 -0
- package/dist/agent-task.d.ts +39 -0
- package/dist/agent-task.js +112 -0
- package/dist/analyze.d.ts +215 -0
- package/dist/analyze.js +2438 -0
- package/dist/app-feedback.d.ts +167 -0
- package/dist/app-feedback.js +176 -0
- package/dist/authoring-input.d.ts +3 -0
- package/dist/authoring-input.js +107 -0
- package/dist/authoring-names.d.ts +4 -0
- package/dist/authoring-names.js +13 -0
- package/dist/authoring-records.d.ts +35 -0
- package/dist/authoring-records.js +264 -0
- package/dist/authoring.d.ts +15 -0
- package/dist/authoring.js +331 -0
- package/dist/bin/check.d.ts +30 -0
- package/dist/bin/check.js +99 -0
- package/dist/bin/load.d.ts +48 -0
- package/dist/bin/load.js +139 -0
- package/dist/bin/main.d.ts +31 -0
- package/dist/bin/main.js +121 -0
- package/dist/bin/migrate.d.ts +57 -0
- package/dist/bin/migrate.js +170 -0
- package/dist/bin/output.d.ts +31 -0
- package/dist/bin/output.js +84 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +31 -0
- package/dist/blob.d.ts +52 -0
- package/dist/blob.js +87 -0
- package/dist/channel-session.d.ts +16 -0
- package/dist/channel-session.js +16 -0
- package/dist/chat.d.ts +43 -0
- package/dist/chat.js +34 -0
- package/dist/confirmation.d.ts +46 -0
- package/dist/confirmation.js +41 -0
- package/dist/connection.d.ts +213 -0
- package/dist/connection.js +289 -0
- package/dist/deployment.d.ts +35 -0
- package/dist/deployment.js +76 -0
- package/dist/diagnostics.d.ts +27 -0
- package/dist/diagnostics.js +106 -0
- package/dist/emails.d.ts +70 -0
- package/dist/emails.js +221 -0
- package/dist/entity.d.ts +331 -0
- package/dist/entity.js +153 -0
- package/dist/errors.d.ts +154 -0
- package/dist/errors.js +109 -0
- package/dist/feedback-config.d.ts +24 -0
- package/dist/feedback-config.js +126 -0
- package/dist/files.d.ts +39 -0
- package/dist/files.js +4 -0
- package/dist/groups.d.ts +95 -0
- package/dist/groups.js +280 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +70 -0
- package/dist/internal/diagnostics.js +594 -0
- package/dist/legacy.d.ts +160 -0
- package/dist/legacy.js +270 -0
- package/dist/manifest-compatibility.d.ts +8 -0
- package/dist/manifest-compatibility.js +87 -0
- package/dist/manifest.d.ts +3329 -0
- package/dist/manifest.js +757 -0
- package/dist/me.d.ts +46 -0
- package/dist/me.js +109 -0
- package/dist/metric-filters.d.ts +48 -0
- package/dist/metric-filters.js +252 -0
- package/dist/metric-routes.d.ts +132 -0
- package/dist/metric-routes.js +498 -0
- package/dist/metric.d.ts +172 -0
- package/dist/metric.js +132 -0
- package/dist/migrate/apply.d.ts +94 -0
- package/dist/migrate/apply.js +526 -0
- package/dist/migrate/errors.d.ts +46 -0
- package/dist/migrate/errors.js +38 -0
- package/dist/migrate/index.d.ts +10 -0
- package/dist/migrate/index.js +7 -0
- package/dist/migrate/model.d.ts +2311 -0
- package/dist/migrate/model.js +148 -0
- package/dist/migrate/plan.d.ts +51 -0
- package/dist/migrate/plan.js +1856 -0
- package/dist/migrate/preview.d.ts +13 -0
- package/dist/migrate/preview.js +65 -0
- package/dist/migrate/storage.d.ts +226 -0
- package/dist/migrate/storage.js +774 -0
- package/dist/model-metrics.d.ts +22 -0
- package/dist/model-metrics.js +545 -0
- package/dist/names.d.ts +49 -0
- package/dist/names.js +110 -0
- package/dist/navigation-artifact.d.ts +13 -0
- package/dist/navigation-artifact.js +11 -0
- package/dist/navigation-catalog.d.ts +55 -0
- package/dist/navigation-catalog.js +297 -0
- package/dist/nitro-navigation.d.ts +3 -0
- package/dist/nitro-navigation.js +64 -0
- package/dist/nitro-workflows.d.ts +108 -0
- package/dist/nitro-workflows.js +286 -0
- package/dist/nitro.d.ts +40 -0
- package/dist/nitro.js +50 -0
- package/dist/ontology.d.ts +41 -0
- package/dist/ontology.js +46 -0
- package/dist/permissions.d.ts +198 -0
- package/dist/permissions.js +272 -0
- package/dist/property.d.ts +188 -0
- package/dist/property.js +301 -0
- package/dist/query.d.ts +245 -0
- package/dist/query.js +615 -0
- package/dist/relationship.d.ts +99 -0
- package/dist/relationship.js +33 -0
- package/dist/render.d.ts +81 -0
- package/dist/render.js +512 -0
- package/dist/replication/baseline.d.ts +51 -0
- package/dist/replication/baseline.js +215 -0
- package/dist/replication/bigquery.d.ts +37 -0
- package/dist/replication/bigquery.js +211 -0
- package/dist/replication/coverage.d.ts +14 -0
- package/dist/replication/coverage.js +53 -0
- package/dist/replication/evolution.d.ts +25 -0
- package/dist/replication/evolution.js +184 -0
- package/dist/replication/identities.d.ts +6 -0
- package/dist/replication/identities.js +47 -0
- package/dist/replication/legacy-baseline.d.ts +27 -0
- package/dist/replication/legacy-baseline.js +109 -0
- package/dist/replication/legacy-store.d.ts +19 -0
- package/dist/replication/legacy-store.js +63 -0
- package/dist/replication/lineage.d.ts +36 -0
- package/dist/replication/lineage.js +72 -0
- package/dist/replication/migrate.d.ts +21 -0
- package/dist/replication/migrate.js +84 -0
- package/dist/replication/model.d.ts +52 -0
- package/dist/replication/model.js +99 -0
- package/dist/replication/scans.d.ts +34 -0
- package/dist/replication/scans.js +158 -0
- package/dist/replication/schema.d.ts +11 -0
- package/dist/replication/schema.js +226 -0
- package/dist/replication/shadow-schema.d.ts +4 -0
- package/dist/replication/shadow-schema.js +61 -0
- package/dist/replication/shadow.d.ts +127 -0
- package/dist/replication/shadow.js +614 -0
- package/dist/replication/source-replacement.d.ts +33 -0
- package/dist/replication/source-replacement.js +76 -0
- package/dist/replication/store.d.ts +36 -0
- package/dist/replication/store.js +382 -0
- package/dist/replication/transaction.d.ts +23 -0
- package/dist/replication/transaction.js +88 -0
- package/dist/role.d.ts +61 -0
- package/dist/role.js +76 -0
- package/dist/runtime/action-deadlocks.d.ts +21 -0
- package/dist/runtime/action-deadlocks.js +130 -0
- package/dist/runtime/action-transactions.d.ts +35 -0
- package/dist/runtime/action-transactions.js +54 -0
- package/dist/runtime/agent-counters.d.ts +26 -0
- package/dist/runtime/agent-counters.js +98 -0
- package/dist/runtime/agent-tasks.d.ts +63 -0
- package/dist/runtime/agent-tasks.js +120 -0
- package/dist/runtime/agents.d.ts +15 -0
- package/dist/runtime/agents.js +457 -0
- package/dist/runtime/batch-diagnostics.d.ts +22 -0
- package/dist/runtime/batch-diagnostics.js +89 -0
- package/dist/runtime/batch.d.ts +52 -0
- package/dist/runtime/batch.js +534 -0
- package/dist/runtime/channel-session-revocation.d.ts +3 -0
- package/dist/runtime/channel-session-revocation.js +5 -0
- package/dist/runtime/channel-sessions.d.ts +4 -0
- package/dist/runtime/channel-sessions.js +61 -0
- package/dist/runtime/confirmations.d.ts +29 -0
- package/dist/runtime/confirmations.js +57 -0
- package/dist/runtime/cursor.d.ts +23 -0
- package/dist/runtime/cursor.js +100 -0
- package/dist/runtime/deployment.d.ts +7 -0
- package/dist/runtime/deployment.js +4 -0
- package/dist/runtime/errors.d.ts +118 -0
- package/dist/runtime/errors.js +109 -0
- package/dist/runtime/federation-tables.d.ts +2 -0
- package/dist/runtime/federation-tables.js +20 -0
- package/dist/runtime/federation.d.ts +42 -0
- package/dist/runtime/federation.js +222 -0
- package/dist/runtime/files.d.ts +39 -0
- package/dist/runtime/files.js +44 -0
- package/dist/runtime/generated-actions.d.ts +37 -0
- package/dist/runtime/generated-actions.js +307 -0
- package/dist/runtime/history.d.ts +91 -0
- package/dist/runtime/history.js +77 -0
- package/dist/runtime/ids.d.ts +6 -0
- package/dist/runtime/ids.js +39 -0
- package/dist/runtime/member-tables.d.ts +8 -0
- package/dist/runtime/member-tables.js +33 -0
- package/dist/runtime/member-writes.d.ts +78 -0
- package/dist/runtime/member-writes.js +202 -0
- package/dist/runtime/metric-query.d.ts +19 -0
- package/dist/runtime/metric-query.js +411 -0
- package/dist/runtime/model.d.ts +67 -0
- package/dist/runtime/model.js +150 -0
- package/dist/runtime/new-user-hook.d.ts +6 -0
- package/dist/runtime/new-user-hook.js +37 -0
- package/dist/runtime/oauth.d.ts +9 -0
- package/dist/runtime/oauth.js +299 -0
- package/dist/runtime/pipeline.d.ts +155 -0
- package/dist/runtime/pipeline.js +597 -0
- package/dist/runtime/postgres-tls.d.ts +10 -0
- package/dist/runtime/postgres-tls.js +14 -0
- package/dist/runtime/principals.d.ts +302 -0
- package/dist/runtime/principals.js +1307 -0
- package/dist/runtime/reads.d.ts +55 -0
- package/dist/runtime/reads.js +313 -0
- package/dist/runtime/records.d.ts +42 -0
- package/dist/runtime/records.js +245 -0
- package/dist/runtime/resolve-agent-identity.d.ts +6 -0
- package/dist/runtime/resolve-agent-identity.js +181 -0
- package/dist/runtime/service-binding.d.ts +18 -0
- package/dist/runtime/service-binding.js +266 -0
- package/dist/runtime/sign-in-links.d.ts +68 -0
- package/dist/runtime/sign-in-links.js +147 -0
- package/dist/runtime/sql-runtime.d.ts +131 -0
- package/dist/runtime/sql-runtime.js +362 -0
- package/dist/runtime/statement.d.ts +18 -0
- package/dist/runtime/statement.js +31 -0
- package/dist/runtime/user-identities.d.ts +66 -0
- package/dist/runtime/user-identities.js +106 -0
- package/dist/runtime/user-identity-sql.d.ts +86 -0
- package/dist/runtime/user-identity-sql.js +297 -0
- package/dist/runtime/user-roles.d.ts +16 -0
- package/dist/runtime/user-roles.js +32 -0
- package/dist/runtime/validation.d.ts +16 -0
- package/dist/runtime/validation.js +26 -0
- package/dist/runtime/where.d.ts +57 -0
- package/dist/runtime/where.js +404 -0
- package/dist/runtime/workflow-admission.d.ts +49 -0
- package/dist/runtime/workflow-admission.js +92 -0
- package/dist/runtime/workflow-argument-codec.d.ts +10 -0
- package/dist/runtime/workflow-argument-codec.js +120 -0
- package/dist/runtime/workflow-arguments.d.ts +17 -0
- package/dist/runtime/workflow-arguments.js +99 -0
- package/dist/runtime/workflow-starts.d.ts +203 -0
- package/dist/runtime/workflow-starts.js +493 -0
- package/dist/runtime/workflow-users.d.ts +9 -0
- package/dist/runtime/workflow-users.js +17 -0
- package/dist/runtime/workload.d.ts +25 -0
- package/dist/runtime/workload.js +24 -0
- package/dist/runtime/writes.d.ts +108 -0
- package/dist/runtime/writes.js +491 -0
- package/dist/runtime-source.d.ts +22 -0
- package/dist/runtime-source.js +28 -0
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.js +31 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +107 -0
- package/dist/service/agent/attachments.d.ts +33 -0
- package/dist/service/agent/attachments.js +228 -0
- package/dist/service/agent/authentication.d.ts +22 -0
- package/dist/service/agent/authentication.js +58 -0
- package/dist/service/agent/confirmations.d.ts +5 -0
- package/dist/service/agent/confirmations.js +73 -0
- package/dist/service/agent/conversation-routes.d.ts +14 -0
- package/dist/service/agent/conversation-routes.js +45 -0
- package/dist/service/agent/conversations.d.ts +40 -0
- package/dist/service/agent/conversations.js +69 -0
- package/dist/service/agent/files.d.ts +12 -0
- package/dist/service/agent/files.js +69 -0
- package/dist/service/agent/protocol.d.ts +90 -0
- package/dist/service/agent/protocol.js +114 -0
- package/dist/service/agent/server.d.ts +28 -0
- package/dist/service/agent/server.js +374 -0
- package/dist/service/agent/task-caller.d.ts +21 -0
- package/dist/service/agent/task-caller.js +35 -0
- package/dist/service/agent/task-start.d.ts +53 -0
- package/dist/service/agent/task-start.js +161 -0
- package/dist/service/agent/tasks.d.ts +40 -0
- package/dist/service/agent/tasks.js +155 -0
- package/dist/service/api.d.ts +50 -0
- package/dist/service/api.js +288 -0
- package/dist/service/authentication.d.ts +227 -0
- package/dist/service/authentication.js +767 -0
- package/dist/service/caller-context.d.ts +24 -0
- package/dist/service/caller-context.js +6 -0
- package/dist/service/catalog.d.ts +42 -0
- package/dist/service/catalog.js +66 -0
- package/dist/service/channel/provider.d.ts +10 -0
- package/dist/service/channel/provider.js +1 -0
- package/dist/service/channel/providers/telegram.d.ts +18 -0
- package/dist/service/channel/providers/telegram.js +108 -0
- package/dist/service/channel/sessions.d.ts +5 -0
- package/dist/service/channel/sessions.js +53 -0
- package/dist/service/channel/sign-in.d.ts +7 -0
- package/dist/service/channel/sign-in.js +78 -0
- package/dist/service/chat/archive.d.ts +64 -0
- package/dist/service/chat/archive.js +62 -0
- package/dist/service/chat/attribution.d.ts +26 -0
- package/dist/service/chat/attribution.js +28 -0
- package/dist/service/chat/delivery.d.ts +18 -0
- package/dist/service/chat/delivery.js +251 -0
- package/dist/service/chat/diagnostics.d.ts +2 -0
- package/dist/service/chat/diagnostics.js +35 -0
- package/dist/service/chat/envelope.d.ts +96 -0
- package/dist/service/chat/envelope.js +39 -0
- package/dist/service/chat/idempotency.d.ts +19 -0
- package/dist/service/chat/idempotency.js +37 -0
- package/dist/service/chat/providers/telegram.d.ts +36 -0
- package/dist/service/chat/providers/telegram.js +133 -0
- package/dist/service/chat/reads.d.ts +25 -0
- package/dist/service/chat/reads.js +75 -0
- package/dist/service/chat/resolve.d.ts +34 -0
- package/dist/service/chat/resolve.js +150 -0
- package/dist/service/chat/routes.d.ts +23 -0
- package/dist/service/chat/routes.js +75 -0
- package/dist/service/chat/schemas.d.ts +43 -0
- package/dist/service/chat/schemas.js +31 -0
- package/dist/service/config.d.ts +218 -0
- package/dist/service/config.js +265 -0
- package/dist/service/connection-stream.d.ts +10 -0
- package/dist/service/connection-stream.js +191 -0
- package/dist/service/connections.d.ts +60 -0
- package/dist/service/connections.js +271 -0
- package/dist/service/decisions.d.ts +35 -0
- package/dist/service/decisions.js +63 -0
- package/dist/service/descriptions.d.ts +8 -0
- package/dist/service/descriptions.js +20 -0
- package/dist/service/discovery-schemas.d.ts +92 -0
- package/dist/service/discovery-schemas.js +59 -0
- package/dist/service/discovery-search.d.ts +30 -0
- package/dist/service/discovery-search.js +95 -0
- package/dist/service/discovery.d.ts +37 -0
- package/dist/service/discovery.js +436 -0
- package/dist/service/docs.d.ts +9 -0
- package/dist/service/docs.js +197 -0
- package/dist/service/errors.d.ts +145 -0
- package/dist/service/errors.js +704 -0
- package/dist/service/federation-provider.d.ts +36 -0
- package/dist/service/federation-provider.js +110 -0
- package/dist/service/federation-server.d.ts +20 -0
- package/dist/service/federation-server.js +249 -0
- package/dist/service/federation-status.d.ts +40 -0
- package/dist/service/federation-status.js +151 -0
- package/dist/service/handlers.d.ts +18 -0
- package/dist/service/handlers.js +287 -0
- package/dist/service/mcp/confirmation.d.ts +68 -0
- package/dist/service/mcp/confirmation.js +178 -0
- package/dist/service/mcp/find-input.d.ts +12 -0
- package/dist/service/mcp/find-input.js +122 -0
- package/dist/service/mcp/input-schema.d.ts +6 -0
- package/dist/service/mcp/input-schema.js +126 -0
- package/dist/service/mcp/listing.d.ts +26 -0
- package/dist/service/mcp/listing.js +92 -0
- package/dist/service/mcp/metric-tools.d.ts +85 -0
- package/dist/service/mcp/metric-tools.js +258 -0
- package/dist/service/mcp/request-log.d.ts +5 -0
- package/dist/service/mcp/request-log.js +53 -0
- package/dist/service/mcp/runtime-policy.d.ts +27 -0
- package/dist/service/mcp/runtime-policy.js +62 -0
- package/dist/service/mcp/server.d.ts +52 -0
- package/dist/service/mcp/server.js +861 -0
- package/dist/service/mcp/tools.d.ts +99 -0
- package/dist/service/mcp/tools.js +938 -0
- package/dist/service/member-file-routes.d.ts +22 -0
- package/dist/service/member-file-routes.js +52 -0
- package/dist/service/member-files.d.ts +245 -0
- package/dist/service/member-files.js +258 -0
- package/dist/service/metric-query-platform.d.ts +77 -0
- package/dist/service/metric-query-platform.js +198 -0
- package/dist/service/metric-query-scopes.d.ts +43 -0
- package/dist/service/metric-query-scopes.js +147 -0
- package/dist/service/metric-query.d.ts +48 -0
- package/dist/service/metric-query.js +200 -0
- package/dist/service/name-suggestions.d.ts +3 -0
- package/dist/service/name-suggestions.js +38 -0
- package/dist/service/navigation-schemas.d.ts +80 -0
- package/dist/service/navigation-schemas.js +49 -0
- package/dist/service/navigation.d.ts +20 -0
- package/dist/service/navigation.js +165 -0
- package/dist/service/oauth/protocol.d.ts +70 -0
- package/dist/service/oauth/protocol.js +157 -0
- package/dist/service/oauth/refresh-replay.d.ts +2 -0
- package/dist/service/oauth/refresh-replay.js +20 -0
- package/dist/service/oauth/server.d.ts +28 -0
- package/dist/service/oauth/server.js +769 -0
- package/dist/service/operations.d.ts +170 -0
- package/dist/service/operations.js +392 -0
- package/dist/service/plan.d.ts +57 -0
- package/dist/service/plan.js +96 -0
- package/dist/service/rate-limit.d.ts +22 -0
- package/dist/service/rate-limit.js +52 -0
- package/dist/service/record-reads.d.ts +21 -0
- package/dist/service/record-reads.js +155 -0
- package/dist/service/replication.d.ts +5 -0
- package/dist/service/replication.js +39 -0
- package/dist/service/runtime-contract.d.ts +508 -0
- package/dist/service/runtime-contract.js +18 -0
- package/dist/service/runtime-identity.d.ts +71 -0
- package/dist/service/runtime-identity.js +122 -0
- package/dist/service/schemas.d.ts +961 -0
- package/dist/service/schemas.js +639 -0
- package/dist/service/service.d.ts +21 -0
- package/dist/service/service.js +526 -0
- package/dist/service/warehouse-platform.d.ts +17 -0
- package/dist/service/warehouse-platform.js +122 -0
- package/dist/service/warehouse-query.d.ts +24 -0
- package/dist/service/warehouse-query.js +280 -0
- package/dist/service/webhooks.d.ts +106 -0
- package/dist/service/webhooks.js +233 -0
- package/dist/service/workflow-admission-provider.d.ts +31 -0
- package/dist/service/workflow-admission-provider.js +205 -0
- package/dist/service/workflow-argument-serialization.d.ts +2 -0
- package/dist/service/workflow-argument-serialization.js +14 -0
- package/dist/service/workflow-callback.d.ts +22 -0
- package/dist/service/workflow-callback.js +75 -0
- package/dist/service/workflow-deployment.d.ts +8 -0
- package/dist/service/workflow-deployment.js +131 -0
- package/dist/service/workflow-diagnostics.d.ts +23 -0
- package/dist/service/workflow-diagnostics.js +263 -0
- package/dist/service/workflow-errors.d.ts +26 -0
- package/dist/service/workflow-errors.js +181 -0
- package/dist/service/workflow-logging.d.ts +2 -0
- package/dist/service/workflow-logging.js +50 -0
- package/dist/service/workflow-queue-fence.d.ts +6 -0
- package/dist/service/workflow-queue-fence.js +66 -0
- package/dist/service/workflows.d.ts +200 -0
- package/dist/service/workflows.js +1549 -0
- package/dist/sign-in.d.ts +58 -0
- package/dist/sign-in.js +57 -0
- package/dist/source.d.ts +30 -0
- package/dist/source.js +107 -0
- package/dist/start-recovery-contract.d.ts +49 -0
- package/dist/start-recovery-contract.js +26 -0
- package/dist/start-recovery.d.ts +27 -0
- package/dist/start-recovery.js +81 -0
- package/dist/storage/api.d.ts +4 -0
- package/dist/storage/api.js +40 -0
- package/dist/storage/attachments.d.ts +146 -0
- package/dist/storage/attachments.js +733 -0
- package/dist/storage/authorization.d.ts +38 -0
- package/dist/storage/authorization.js +83 -0
- package/dist/storage/file-access.d.ts +17 -0
- package/dist/storage/file-access.js +8 -0
- package/dist/storage/limits.d.ts +12 -0
- package/dist/storage/limits.js +17 -0
- package/dist/storage/maintenance-auth.d.ts +20 -0
- package/dist/storage/maintenance-auth.js +24 -0
- package/dist/storage/maintenance.d.ts +11 -0
- package/dist/storage/maintenance.js +67 -0
- package/dist/storage/neon.d.ts +4 -0
- package/dist/storage/neon.js +318 -0
- package/dist/storage/operator.d.ts +11 -0
- package/dist/storage/operator.js +30 -0
- package/dist/storage/provider.d.ts +93 -0
- package/dist/storage/provider.js +36 -0
- package/dist/storage/sessions.d.ts +184 -0
- package/dist/storage/sessions.js +688 -0
- package/dist/storage/tables.d.ts +54 -0
- package/dist/storage/tables.js +205 -0
- package/dist/storage/transfers.d.ts +16 -0
- package/dist/storage/transfers.js +153 -0
- package/dist/storage/writes.d.ts +7 -0
- package/dist/storage/writes.js +88 -0
- package/dist/telegram.d.ts +35 -0
- package/dist/telegram.js +173 -0
- package/dist/user-identity.d.ts +28 -0
- package/dist/user-identity.js +106 -0
- package/dist/user.d.ts +75 -0
- package/dist/user.js +111 -0
- package/dist/warehouse-backed.d.ts +6 -0
- package/dist/warehouse-backed.js +495 -0
- package/dist/webhook-token.d.ts +11 -0
- package/dist/webhook-token.js +18 -0
- package/dist/webhooks.d.ts +40 -0
- package/dist/webhooks.js +55 -0
- package/dist/wire.d.ts +10 -0
- package/dist/wire.js +35 -0
- package/dist/workflow-runtime.d.ts +116 -0
- package/dist/workflow-runtime.js +109 -0
- package/dist/workflow-topics.d.ts +28 -0
- package/dist/workflow-topics.js +28 -0
- package/dist/workflow.d.ts +284 -0
- package/dist/workflow.js +259 -0
- package/dist/workflows.d.ts +24 -0
- package/dist/workflows.js +18 -0
- package/package.json +127 -4
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Data, Effect, Redacted } from "effect";
|
|
2
|
+
import { MetricQueryUpstreamUnavailable } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* How long one metric query may take end to end, the response included. BigQuery answers most within
|
|
5
|
+
* seconds; the margin covers a cold warehouse and the hops to and from SegmentStream.
|
|
6
|
+
*/
|
|
7
|
+
export const METRIC_QUERY_TIMEOUT_MS = 60_000;
|
|
8
|
+
/** The header that carries the service's request id to SegmentStream, as connection calls do. */
|
|
9
|
+
const REQUEST_ID_HEADER = "x-segmentstream-request-id";
|
|
10
|
+
/**
|
|
11
|
+
* The hash of the manifest the service resolved and checked the request against. SegmentStream
|
|
12
|
+
* answers `409 ontology_version_mismatch` when its deployed manifest differs, so a request checked
|
|
13
|
+
* against one ontology never runs against another.
|
|
14
|
+
*/
|
|
15
|
+
export const MANIFEST_HASH_HEADER = "x-ontology-manifest-hash";
|
|
16
|
+
/**
|
|
17
|
+
* The entities the service checked `<entity>:read` on, sorted and comma-separated. SegmentStream
|
|
18
|
+
* answers `409 aggregate_scope_mismatch` when it would read any other, so the service's resolution
|
|
19
|
+
* and SegmentStream's can never drift apart unnoticed.
|
|
20
|
+
*/
|
|
21
|
+
export const TOUCHED_HEADER = "x-ontology-touched";
|
|
22
|
+
/**
|
|
23
|
+
* The caller's read scopes of the stored entities the request reads scoped, base64url JSON of entity
|
|
24
|
+
* name to `where`, `me` resolved. SegmentStream keeps only each scope's rows wherever it reads the
|
|
25
|
+
* entity, confirms the scopes in `execution.rowScopes`, and answers `422
|
|
26
|
+
* scoped_aggregate_unavailable` for a scope the warehouse cannot apply.
|
|
27
|
+
*/
|
|
28
|
+
export const ROW_SCOPES_HEADER = "x-ontology-row-scopes";
|
|
29
|
+
/**
|
|
30
|
+
* Statuses that refuse the deployment rather than the caller: its credential, its admission or the
|
|
31
|
+
* route itself. Passing them on would make the caller sign in again or look for a missing route.
|
|
32
|
+
*/
|
|
33
|
+
const DEPLOYMENT_FAULTS = {
|
|
34
|
+
401: "credential_rejected",
|
|
35
|
+
403: "credential_rejected",
|
|
36
|
+
404: "upstream_misconfigured",
|
|
37
|
+
};
|
|
38
|
+
/** SegmentStream would read beyond what the service checked: this deployment's SDK is behind. */
|
|
39
|
+
export const SCOPE_MISMATCH = "aggregate_scope_mismatch";
|
|
40
|
+
/** Statuses whose `Retry-After` the caller receives as SegmentStream sent it. */
|
|
41
|
+
const RETRY_AFTER_STATUSES = new Set([429, 503]);
|
|
42
|
+
/** A `Retry-After` value: seconds or an HTTP date. Anything else is not passed on. */
|
|
43
|
+
const RETRY_AFTER_VALUE = /^[0-9A-Za-z ,:+-]{1,64}$/;
|
|
44
|
+
/** The most of SegmentStream's body an operator log keeps. */
|
|
45
|
+
const SNIPPET_LENGTH = 500;
|
|
46
|
+
/** How many causes deep an operator log follows a failed call. */
|
|
47
|
+
const CAUSE_DEPTH = 3;
|
|
48
|
+
/** Hosts a deployment may reach over plain http: the machine itself, in development. */
|
|
49
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "[::1]"]);
|
|
50
|
+
/** The call ended without an answer; `aborted` when the caller went away first. */
|
|
51
|
+
class CallFailed extends Data.TaggedError("CallFailed") {
|
|
52
|
+
}
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
/** The credential travels only to SegmentStream over TLS, or over plain http to this machine. */
|
|
57
|
+
export function securePlatform(origin) {
|
|
58
|
+
const { protocol, hostname } = new URL(origin);
|
|
59
|
+
if (protocol === "https:") {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return (protocol === "http:" && (LOOPBACK_HOSTS.has(hostname) || /^127(?:\.\d{1,3}){3}$/.test(hostname)));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A failed call for the operator: name, message and stack, then each cause, with every occurrence
|
|
66
|
+
* of the credential replaced.
|
|
67
|
+
*/
|
|
68
|
+
export function describeError(error, redact, depth = 0) {
|
|
69
|
+
if (!(error instanceof Error)) {
|
|
70
|
+
return redact(String(error));
|
|
71
|
+
}
|
|
72
|
+
const code = error.code;
|
|
73
|
+
const head = error.stack ?? `${error.name}: ${error.message}`;
|
|
74
|
+
const own = typeof code === "string" ? `${head} [${code}]` : head;
|
|
75
|
+
const cause = error.cause !== undefined && depth < CAUSE_DEPTH
|
|
76
|
+
? `\ncaused by: ${describeError(error.cause, redact, depth + 1)}`
|
|
77
|
+
: "";
|
|
78
|
+
return redact(`${own}${cause}`);
|
|
79
|
+
}
|
|
80
|
+
function parseObject(text) {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(text);
|
|
83
|
+
return isRecord(parsed) ? { body: parsed } : { error: new TypeError("Not a JSON object.") };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return { error };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* SegmentStream's `POST /api/v1/workspaces/{id}/metrics/query`, and its per-entity
|
|
91
|
+
* `…/entities/{entity}/metrics/query`, called with the workspace app credential. `undefined` when
|
|
92
|
+
* the deployment has none, or when SegmentStream's origin is plain http to another machine, which
|
|
93
|
+
* is logged once. Every request names the manifest it was checked
|
|
94
|
+
* against, and a redirect is refused, so the credential never leaves for another origin.
|
|
95
|
+
*
|
|
96
|
+
* Every answer but a 2xx is logged with both request ids, the status and the start of the body; a
|
|
97
|
+
* failed call with its message, stack and causes. The credential is redacted from all of them. A
|
|
98
|
+
* caller who goes away interrupts the call, which is not SegmentStream's failure.
|
|
99
|
+
*/
|
|
100
|
+
export function platformMetricQueries(environment, fetchImpl = fetch) {
|
|
101
|
+
const { appCredential, segmentstreamUrl } = environment;
|
|
102
|
+
if (appCredential === undefined) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
if (!securePlatform(segmentstreamUrl)) {
|
|
106
|
+
Effect.runSync(Effect.logError("ontology.metric_query.insecure_platform").pipe(Effect.annotateLogs({
|
|
107
|
+
diagnostic: "SEGMENTSTREAM_URL is not https; metric queries are unavailable.",
|
|
108
|
+
platform: segmentstreamUrl,
|
|
109
|
+
})));
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const credential = Redacted.value(appCredential);
|
|
113
|
+
const redact = (text) => text.split(credential).join("[redacted]");
|
|
114
|
+
const workspace = `${segmentstreamUrl}/api/v1/workspaces/` + encodeURIComponent(environment.workspaceId);
|
|
115
|
+
const urlOf = (entity) => entity === undefined
|
|
116
|
+
? `${workspace}/metrics/query`
|
|
117
|
+
: `${workspace}/entities/${encodeURIComponent(entity)}/metrics/query`;
|
|
118
|
+
const exchange = (input) => Effect.tryPromise({
|
|
119
|
+
try: async (signal) => {
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetchImpl(urlOf(input.entity), {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: {
|
|
124
|
+
authorization: `Bearer ${credential}`,
|
|
125
|
+
"content-type": "application/json",
|
|
126
|
+
accept: "application/json",
|
|
127
|
+
[REQUEST_ID_HEADER]: input.requestId,
|
|
128
|
+
[MANIFEST_HASH_HEADER]: environment.manifestHash,
|
|
129
|
+
[TOUCHED_HEADER]: input.touched.join(","),
|
|
130
|
+
...(input.rowScopes === undefined ? {} : { [ROW_SCOPES_HEADER]: input.rowScopes }),
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(input.body),
|
|
133
|
+
redirect: "error",
|
|
134
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(METRIC_QUERY_TIMEOUT_MS)]),
|
|
135
|
+
});
|
|
136
|
+
const text = await response.text();
|
|
137
|
+
return {
|
|
138
|
+
status: response.status,
|
|
139
|
+
text,
|
|
140
|
+
retryAfter: response.headers.get("retry-after"),
|
|
141
|
+
platformRequestId: response.headers.get("x-request-id"),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw new CallFailed({ error, aborted: signal.aborted });
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
catch: (error) => error instanceof CallFailed ? error : new CallFailed({ error, aborted: false }),
|
|
149
|
+
});
|
|
150
|
+
/** No answer: a timeout or an unreachable SegmentStream. */
|
|
151
|
+
const unanswered = (error, requestId) => {
|
|
152
|
+
const reason = error instanceof DOMException && error.name === "TimeoutError" ? "timeout" : "unreachable";
|
|
153
|
+
return Effect.logWarning("ontology.metric_query.upstream_failed").pipe(Effect.annotateLogs({ requestId, diagnostic: reason, error: describeError(error, redact) }), Effect.andThen(Effect.fail(new MetricQueryUpstreamUnavailable({ reason }))));
|
|
154
|
+
};
|
|
155
|
+
/** An answer: passed on, or a 502 when it faults the deployment or is not a JSON object. */
|
|
156
|
+
const judge = (answer, requestId) => Effect.gen(function* () {
|
|
157
|
+
const parsed = parseObject(answer.text);
|
|
158
|
+
const body = "body" in parsed ? parsed.body : undefined;
|
|
159
|
+
const fault = DEPLOYMENT_FAULTS[answer.status] ?? (body === undefined ? "invalid_response" : undefined);
|
|
160
|
+
const platformRequestId = typeof body?.requestId === "string" ? body.requestId : answer.platformRequestId;
|
|
161
|
+
if (fault !== undefined || answer.status < 200 || answer.status >= 300) {
|
|
162
|
+
const code = typeof body?.error === "string" ? body.error : undefined;
|
|
163
|
+
const level = fault !== undefined || code === SCOPE_MISMATCH
|
|
164
|
+
? Effect.logError
|
|
165
|
+
: answer.status >= 500
|
|
166
|
+
? Effect.logWarning
|
|
167
|
+
: Effect.logInfo;
|
|
168
|
+
yield* level(fault === undefined
|
|
169
|
+
? "ontology.metric_query.platform_refused"
|
|
170
|
+
: "ontology.metric_query.upstream_failed").pipe(Effect.annotateLogs({
|
|
171
|
+
requestId,
|
|
172
|
+
platformRequestId: platformRequestId ?? "none",
|
|
173
|
+
status: answer.status,
|
|
174
|
+
diagnostic: fault ?? code ?? "refused",
|
|
175
|
+
body: redact(answer.text.slice(0, SNIPPET_LENGTH)),
|
|
176
|
+
...("error" in parsed ? { error: describeError(parsed.error, redact) } : {}),
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
if (fault !== undefined || body === undefined) {
|
|
180
|
+
return yield* new MetricQueryUpstreamUnavailable({
|
|
181
|
+
reason: fault ?? "invalid_response",
|
|
182
|
+
status: answer.status,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const retryAfter = RETRY_AFTER_STATUSES.has(answer.status) &&
|
|
186
|
+
answer.retryAfter !== null &&
|
|
187
|
+
RETRY_AFTER_VALUE.test(answer.retryAfter)
|
|
188
|
+
? answer.retryAfter
|
|
189
|
+
: undefined;
|
|
190
|
+
return {
|
|
191
|
+
status: answer.status,
|
|
192
|
+
text: answer.text,
|
|
193
|
+
body,
|
|
194
|
+
...(retryAfter === undefined ? {} : { retryAfter }),
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
return (input) => exchange(input).pipe(Effect.catchTag("CallFailed", (failed) => failed.aborted ? Effect.interrupt : unanswered(failed.error, input.requestId)), Effect.flatMap((answer) => judge(answer, input.requestId)));
|
|
198
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type PermissionHolder, type ScopeWhere } from "../permissions.js";
|
|
2
|
+
import type { OntologyModel } from "../runtime/model.js";
|
|
3
|
+
/**
|
|
4
|
+
* Row scopes of metric queries (docs/ontology-scoped-permissions-spec.md, "Aggregates"). A metric
|
|
5
|
+
* query that reads an entity the caller reads through a scoped permission sends that scope along, so that
|
|
6
|
+
* SegmentStream keeps only the scope's rows wherever it reads the entity. `me` is the caller's user
|
|
7
|
+
* id, resolved here; a path from `me` needs the ontology's records to resolve, so a scope with one
|
|
8
|
+
* cannot travel yet and the metric query is refused, as before. A scope the service's own reads would
|
|
9
|
+
* compile to FALSE travels as one that matches nothing: resolving `me` would otherwise turn a marker
|
|
10
|
+
* on the wrong field into a valid comparison with the caller's id.
|
|
11
|
+
*/
|
|
12
|
+
/** The longest `x-ontology-row-scopes` SegmentStream accepts. */
|
|
13
|
+
export declare const MAX_ROW_SCOPES_HEADER = 8192;
|
|
14
|
+
/** The caller's read scopes of the entities a metric query reads, as SegmentStream applies them. */
|
|
15
|
+
export type RowScopes = {
|
|
16
|
+
readonly ok: true;
|
|
17
|
+
readonly scopes: Readonly<Record<string, ScopeWhere>>;
|
|
18
|
+
}
|
|
19
|
+
/** `entity`'s scope cannot travel: it follows a path from `me`, or is too large to send. */
|
|
20
|
+
| {
|
|
21
|
+
readonly ok: false;
|
|
22
|
+
readonly entity: string;
|
|
23
|
+
readonly reason: "me_path" | "too_large";
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Each touched entity the caller reads through a scope, with its `where` and `me` replaced by the
|
|
27
|
+
* caller's user id: a bare `me` compares as `{ eq: <id> }`, an operand `me` becomes the id. A scope
|
|
28
|
+
* the service's reads would compile to FALSE (an unknown field or operator, an operand of the wrong
|
|
29
|
+
* type, `me` on a field that is not a key, or a caller whose id is not a record id) matches nothing.
|
|
30
|
+
* A warehouse-backed entity has no stored model to check against; its scope travels as written and
|
|
31
|
+
* SegmentStream refuses it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function rowScopesOf(touched: ReadonlyArray<string>, principal: PermissionHolder & {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
}, model: OntologyModel): RowScopes;
|
|
36
|
+
/** `x-ontology-row-scopes`: base64url of the scopes' JSON, or `undefined` without any. */
|
|
37
|
+
export declare function encodeRowScopes(scopes: Readonly<Record<string, ScopeWhere>>): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Whether SegmentStream confirmed every scope it was sent: its answer's `execution.rowScopes` names
|
|
40
|
+
* exactly the scoped entities. A platform that predates row scopes ignores them and confirms none,
|
|
41
|
+
* so its answer, which counts rows the caller may not see, is never passed on.
|
|
42
|
+
*/
|
|
43
|
+
export declare function confirmsRowScopes(body: Readonly<Record<string, unknown>>, scopes: Readonly<Record<string, ScopeWhere>>): boolean;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { mePathOf } from "../me.js";
|
|
2
|
+
import { readScope } from "../permissions.js";
|
|
3
|
+
import { FILTER_OPERATORS, MANY_QUANTIFIERS } from "../query.js";
|
|
4
|
+
import { isRecordId } from "../runtime/records.js";
|
|
5
|
+
import { isWellFormedScope } from "../runtime/where.js";
|
|
6
|
+
/**
|
|
7
|
+
* Row scopes of metric queries (docs/ontology-scoped-permissions-spec.md, "Aggregates"). A metric
|
|
8
|
+
* query that reads an entity the caller reads through a scoped permission sends that scope along, so that
|
|
9
|
+
* SegmentStream keeps only the scope's rows wherever it reads the entity. `me` is the caller's user
|
|
10
|
+
* id, resolved here; a path from `me` needs the ontology's records to resolve, so a scope with one
|
|
11
|
+
* cannot travel yet and the metric query is refused, as before. A scope the service's own reads would
|
|
12
|
+
* compile to FALSE travels as one that matches nothing: resolving `me` would otherwise turn a marker
|
|
13
|
+
* on the wrong field into a valid comparison with the caller's id.
|
|
14
|
+
*/
|
|
15
|
+
/** The longest `x-ontology-row-scopes` SegmentStream accepts. */
|
|
16
|
+
export const MAX_ROW_SCOPES_HEADER = 8_192;
|
|
17
|
+
/** No row: a scope `check` would have refused, as the service's own reads treat it. */
|
|
18
|
+
const NO_ROWS = { or: [] };
|
|
19
|
+
/** Query and wire names of field operators: their operands are values, never wheres. */
|
|
20
|
+
const OPERATORS = new Set([
|
|
21
|
+
...FILTER_OPERATORS,
|
|
22
|
+
"is_null",
|
|
23
|
+
"starts_with",
|
|
24
|
+
"has_any",
|
|
25
|
+
]);
|
|
26
|
+
const QUANTIFIERS = new Set(MANY_QUANTIFIERS);
|
|
27
|
+
/** A path from `me`: only the ontology's records can resolve it. */
|
|
28
|
+
class MePath extends Error {
|
|
29
|
+
}
|
|
30
|
+
function isPlainObject(value) {
|
|
31
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Each touched entity the caller reads through a scope, with its `where` and `me` replaced by the
|
|
35
|
+
* caller's user id: a bare `me` compares as `{ eq: <id> }`, an operand `me` becomes the id. A scope
|
|
36
|
+
* the service's reads would compile to FALSE (an unknown field or operator, an operand of the wrong
|
|
37
|
+
* type, `me` on a field that is not a key, or a caller whose id is not a record id) matches nothing.
|
|
38
|
+
* A warehouse-backed entity has no stored model to check against; its scope travels as written and
|
|
39
|
+
* SegmentStream refuses it.
|
|
40
|
+
*/
|
|
41
|
+
export function rowScopesOf(touched, principal, model) {
|
|
42
|
+
const scopes = {};
|
|
43
|
+
for (const entity of touched) {
|
|
44
|
+
const where = readScope(entity, principal);
|
|
45
|
+
if (where === undefined) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const stored = model.entities.get(entity);
|
|
49
|
+
if (stored !== undefined && !isWellFormedScope(model, stored, { reader: principal, where })) {
|
|
50
|
+
scopes[entity] = NO_ROWS;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
scopes[entity] = resolveWhere(where, principal.id);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error instanceof MePath) {
|
|
58
|
+
return { ok: false, entity, reason: "me_path" };
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const encoded = encodeRowScopes(scopes);
|
|
64
|
+
if (encoded !== undefined && encoded.length > MAX_ROW_SCOPES_HEADER) {
|
|
65
|
+
return { ok: false, entity: Object.keys(scopes).sort()[0], reason: "too_large" };
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, scopes };
|
|
68
|
+
}
|
|
69
|
+
/** `x-ontology-row-scopes`: base64url of the scopes' JSON, or `undefined` without any. */
|
|
70
|
+
export function encodeRowScopes(scopes) {
|
|
71
|
+
return Object.keys(scopes).length === 0
|
|
72
|
+
? undefined
|
|
73
|
+
: Buffer.from(JSON.stringify(scopes), "utf8").toString("base64url");
|
|
74
|
+
}
|
|
75
|
+
/** The caller's id; a stored entity's scope with `me` was checked to have a record id. */
|
|
76
|
+
function me(readerId) {
|
|
77
|
+
return isRecordId(readerId) ? readerId.toLowerCase() : readerId;
|
|
78
|
+
}
|
|
79
|
+
/** A `where`: `and`, `or` and `not` hold wheres; every other key a member's condition. */
|
|
80
|
+
function resolveWhere(where, readerId) {
|
|
81
|
+
if (!isPlainObject(where)) {
|
|
82
|
+
return where;
|
|
83
|
+
}
|
|
84
|
+
return Object.fromEntries(Object.entries(where).map(([key, condition]) => {
|
|
85
|
+
if (key === "and" || key === "or") {
|
|
86
|
+
return [
|
|
87
|
+
key,
|
|
88
|
+
Array.isArray(condition)
|
|
89
|
+
? condition.map((sub) => resolveWhere(sub, readerId))
|
|
90
|
+
: condition,
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
if (key === "not") {
|
|
94
|
+
return [key, resolveWhere(condition, readerId)];
|
|
95
|
+
}
|
|
96
|
+
return [key, resolveMember(condition, readerId)];
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A field's or relationship's condition: bare `me`, an object of operators whose operands may be
|
|
101
|
+
* `me`, a to-many filter of quantifiers, or a related record's where.
|
|
102
|
+
*/
|
|
103
|
+
function resolveMember(condition, readerId) {
|
|
104
|
+
const hops = mePathOf(condition);
|
|
105
|
+
if (hops !== undefined) {
|
|
106
|
+
if (hops.length > 0) {
|
|
107
|
+
throw new MePath();
|
|
108
|
+
}
|
|
109
|
+
return { eq: me(readerId) };
|
|
110
|
+
}
|
|
111
|
+
if (!isPlainObject(condition)) {
|
|
112
|
+
return condition;
|
|
113
|
+
}
|
|
114
|
+
const keys = Object.keys(condition);
|
|
115
|
+
if (keys.length > 0 && keys.every((key) => OPERATORS.has(key))) {
|
|
116
|
+
return Object.fromEntries(Object.entries(condition).map(([operator, operand]) => {
|
|
117
|
+
const operandHops = mePathOf(operand);
|
|
118
|
+
if (operandHops === undefined) {
|
|
119
|
+
return [operator, operand];
|
|
120
|
+
}
|
|
121
|
+
if (operandHops.length > 0) {
|
|
122
|
+
throw new MePath();
|
|
123
|
+
}
|
|
124
|
+
return [operator, me(readerId)];
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
if (keys.length > 0 && keys.every((key) => QUANTIFIERS.has(key))) {
|
|
128
|
+
return Object.fromEntries(Object.entries(condition).map(([quantifier, where]) => [
|
|
129
|
+
quantifier,
|
|
130
|
+
resolveWhere(where, readerId),
|
|
131
|
+
]));
|
|
132
|
+
}
|
|
133
|
+
return resolveWhere(condition, readerId);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Whether SegmentStream confirmed every scope it was sent: its answer's `execution.rowScopes` names
|
|
137
|
+
* exactly the scoped entities. A platform that predates row scopes ignores them and confirms none,
|
|
138
|
+
* so its answer, which counts rows the caller may not see, is never passed on.
|
|
139
|
+
*/
|
|
140
|
+
export function confirmsRowScopes(body, scopes) {
|
|
141
|
+
const execution = body.execution;
|
|
142
|
+
const confirmed = isPlainObject(execution) ? execution.rowScopes : undefined;
|
|
143
|
+
const expected = Object.keys(scopes).sort();
|
|
144
|
+
return (Array.isArray(confirmed) &&
|
|
145
|
+
confirmed.length === expected.length &&
|
|
146
|
+
[...confirmed].sort().every((entity, index) => entity === expected[index]));
|
|
147
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { Ontology } from "../ontology.js";
|
|
3
|
+
import type { MetricQueryTransport, PlatformAnswer } from "./metric-query-platform.js";
|
|
4
|
+
import { CurrentPrincipal } from "./authentication.js";
|
|
5
|
+
import { MetricQueryLimited, MetricQueriesUnavailable, MetricQueryUpstreamUnavailable, InvalidMetricQuery, InvalidRequestBody, ReadDenied, RequestId, ScopedMetricQueryUnavailable } from "./errors.js";
|
|
6
|
+
import { type RateLimit, type RateLimiter } from "./rate-limit.js";
|
|
7
|
+
/**
|
|
8
|
+
* Metric queries one principal may run per minute, through both routes and the MCP tool together.
|
|
9
|
+
* Each runs a warehouse query with the deployment's credential, so one caller must not spend without
|
|
10
|
+
* bound.
|
|
11
|
+
*/
|
|
12
|
+
export declare const METRIC_QUERY_LIMIT: RateLimit;
|
|
13
|
+
/** Every way the facade refuses or fails a metric query before SegmentStream answers it. */
|
|
14
|
+
export type MetricQueryFailure = MetricQueryLimited | InvalidRequestBody | InvalidMetricQuery | ReadDenied | ScopedMetricQueryUnavailable | MetricQueriesUnavailable | MetricQueryUpstreamUnavailable;
|
|
15
|
+
/**
|
|
16
|
+
* Runs a metric query as the caller, whatever the transport: the HTTP route passes the parsed body,
|
|
17
|
+
* the MCP tool its arguments. Its answer is SegmentStream's, error statuses included.
|
|
18
|
+
*/
|
|
19
|
+
export type QueryMetrics = (body: unknown) => Effect.Effect<PlatformAnswer, MetricQueryFailure, CurrentPrincipal | RequestId>;
|
|
20
|
+
/** The service's metric queries: model-level, and the per-entity shorthand of each entity. */
|
|
21
|
+
export interface MetricQueries {
|
|
22
|
+
/** `POST /api/ontology/query_metrics` and the MCP `query_metrics` tool. */
|
|
23
|
+
readonly model: QueryMetrics;
|
|
24
|
+
/** `POST /api/ontology/<entity>/query_metrics`, the shorthand `segmentstream.ontology.<entity>`. */
|
|
25
|
+
readonly entity: (entity: string) => QueryMetrics;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The ontology service's metric query facade (docs/ontology-aggregates-spec.md, "Surfaces"). Both
|
|
29
|
+
* routes and the MCP tool run as the caller:
|
|
30
|
+
* 0. the call counts against the caller's {@link METRIC_QUERY_LIMIT}, whichever way it came;
|
|
31
|
+
* 1. the request's shape and bounds are checked, and its names resolved against the service's own
|
|
32
|
+
* manifest into every entity it reads;
|
|
33
|
+
* 2. the caller needs `<entity>:read` on all of them; a read scope on any of them travels along,
|
|
34
|
+
* `me` resolved, and SegmentStream keeps only the scope's rows wherever it reads that entity. A
|
|
35
|
+
* scope with a path from `me` is refused with `ScopedMetricQueryUnavailable`;
|
|
36
|
+
* 3. the body goes to SegmentStream as it came, earlier names renamed (`withCurrentMetricQueryNames`),
|
|
37
|
+
* with the workspace app credential, naming the entities checked, or the answer is
|
|
38
|
+
* `aggregate_unavailable` where the deployment has no credential;
|
|
39
|
+
* 4. an answer to a scoped request must confirm every scope it was sent in `execution.rowScopes`,
|
|
40
|
+
* and a scope SegmentStream cannot apply is refused with `ScopedMetricQueryUnavailable`: a platform
|
|
41
|
+
* that ignored the scopes never answers a scoped caller.
|
|
42
|
+
*/
|
|
43
|
+
export declare function makeMetricQueries(input: {
|
|
44
|
+
readonly ontology: Ontology;
|
|
45
|
+
readonly transport: MetricQueryTransport | undefined;
|
|
46
|
+
/** One bucket per principal; {@link METRIC_QUERY_LIMIT} by default. */
|
|
47
|
+
readonly limiter?: RateLimiter;
|
|
48
|
+
}): MetricQueries;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { resolveMetricRoutes } from "../metric-routes.js";
|
|
3
|
+
import { withCurrentMetricQueryNames } from "../legacy.js";
|
|
4
|
+
import { isAllowed, permissionOf, READ_VERB } from "../permissions.js";
|
|
5
|
+
import { ontologyModel } from "../runtime/model.js";
|
|
6
|
+
import { confirmsRowScopes, encodeRowScopes, rowScopesOf } from "./metric-query-scopes.js";
|
|
7
|
+
import { CurrentPrincipal } from "./authentication.js";
|
|
8
|
+
import { MetricQueryLimited, MetricQueriesUnavailable, InvalidMetricQuery, InvalidRequestBody, ReadDenied, RequestId, schemaIssues, ScopedMetricQueryUnavailable, } from "./errors.js";
|
|
9
|
+
import { fixedWindowLimiter } from "./rate-limit.js";
|
|
10
|
+
import { MetricQuery, EntityMetricQuery } from "./schemas.js";
|
|
11
|
+
/**
|
|
12
|
+
* Metric queries one principal may run per minute, through both routes and the MCP tool together.
|
|
13
|
+
* Each runs a warehouse query with the deployment's credential, so one caller must not spend without
|
|
14
|
+
* bound.
|
|
15
|
+
*/
|
|
16
|
+
export const METRIC_QUERY_LIMIT = { limit: 30, windowMs: 60_000, maxKeys: 10_000 };
|
|
17
|
+
function decode(schema, body) {
|
|
18
|
+
return Schema.decodeUnknownEffect(schema)(body, { errors: "all" }).pipe(Effect.mapError((error) => new InvalidRequestBody({ reason: "invalid_request", issues: schemaIssues(error.issue) })));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The first `orderBy` entry that names neither a requested metric nor a requested dimension, by
|
|
22
|
+
* its requested name or its identity. Ordering by anything else would read what the permission
|
|
23
|
+
* check never saw.
|
|
24
|
+
*/
|
|
25
|
+
function unrequestedOrder(request, routes) {
|
|
26
|
+
const dimensions = new Set([
|
|
27
|
+
...(request.dimensions ?? []),
|
|
28
|
+
...(routes.entities[0]?.dimensions ?? []).map(({ identity }) => identity),
|
|
29
|
+
]);
|
|
30
|
+
for (const order of request.orderBy ?? []) {
|
|
31
|
+
if (order.metric !== undefined && !request.metrics.includes(order.metric)) {
|
|
32
|
+
return order.metric;
|
|
33
|
+
}
|
|
34
|
+
if (order.dimension !== undefined && !dimensions.has(order.dimension)) {
|
|
35
|
+
return order.dimension;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A model-level request's entities, as the shared resolver finds them: each metric's entity,
|
|
42
|
+
* every entity on a dimension's or a filter's route, intermediates included, and every entity a
|
|
43
|
+
* filter names even where it does not apply.
|
|
44
|
+
*/
|
|
45
|
+
function modelTouched(manifest, body) {
|
|
46
|
+
return Effect.gen(function* () {
|
|
47
|
+
const request = yield* decode(MetricQuery, body);
|
|
48
|
+
const routes = resolveMetricRoutes(manifest, {
|
|
49
|
+
metrics: request.metrics,
|
|
50
|
+
dimensions: request.dimensions ?? [],
|
|
51
|
+
filters: (request.filters ?? []).map((filter) => filter.dimension),
|
|
52
|
+
...(request.granularityOf === undefined ? {} : { granularityOf: request.granularityOf }),
|
|
53
|
+
});
|
|
54
|
+
if (!routes.ok) {
|
|
55
|
+
return yield* new InvalidMetricQuery({ reason: routes.reason });
|
|
56
|
+
}
|
|
57
|
+
const unordered = unrequestedOrder(request, routes);
|
|
58
|
+
if (unordered !== undefined) {
|
|
59
|
+
return yield* new InvalidMetricQuery({
|
|
60
|
+
reason: `orderBy names ${unordered}, which the request does not ask for.`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return routes.touched;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A per-entity request's entities: the entity, and every entity along each relative name it
|
|
68
|
+
* groups, filters, buckets or orders by, following the manifest's `rel.one` relationships. As on
|
|
69
|
+
* SegmentStream's per-entity route, a path may follow a self-relationship (`manager.name`).
|
|
70
|
+
*/
|
|
71
|
+
function entityTouched(manifest, entity, body) {
|
|
72
|
+
return Effect.gen(function* () {
|
|
73
|
+
const request = yield* decode(EntityMetricQuery, body);
|
|
74
|
+
const entities = new Map(manifest.entities.map((candidate) => [candidate.name, candidate]));
|
|
75
|
+
const touched = new Set([entity]);
|
|
76
|
+
const names = [
|
|
77
|
+
...request.dimensions,
|
|
78
|
+
...(request.filters ?? []).map((filter) => filter.dimension),
|
|
79
|
+
...(request.granularityOf === undefined ? [] : [request.granularityOf]),
|
|
80
|
+
...(request.orderBy ?? []).flatMap((order) => order.dimension === undefined ? [] : [order.dimension]),
|
|
81
|
+
];
|
|
82
|
+
for (const name of names) {
|
|
83
|
+
const segments = name.split(".");
|
|
84
|
+
let current = entities.get(entity);
|
|
85
|
+
if (segments.some((segment) => segment === "")) {
|
|
86
|
+
return yield* new InvalidMetricQuery({ reason: `${name} is not a valid name.` });
|
|
87
|
+
}
|
|
88
|
+
for (const relationshipName of segments.slice(0, -1)) {
|
|
89
|
+
const relationship = current?.relationships.find((candidate) => candidate.name === relationshipName);
|
|
90
|
+
if (current === undefined || relationship === undefined) {
|
|
91
|
+
return yield* new InvalidMetricQuery({
|
|
92
|
+
reason: `${current?.name ?? entity} has no relationship ${relationshipName}.`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (relationship.cardinality !== "one") {
|
|
96
|
+
return yield* new InvalidMetricQuery({
|
|
97
|
+
reason: `${relationshipName} is not a to-one relationship of ${current.name}.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
current = entities.get(relationship.to);
|
|
101
|
+
touched.add(relationship.to);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return [...touched].sort();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* SegmentStream's answer to a scoped metric query: passed on when it confirms every scope; a refusal of
|
|
109
|
+
* a scope it cannot apply, or an answer that does not confirm the scopes, fails as the caller's
|
|
110
|
+
* {@link ScopedMetricQueryUnavailable}.
|
|
111
|
+
*/
|
|
112
|
+
function scopedAnswer(answer, scopes, requestId) {
|
|
113
|
+
const [first] = Object.keys(scopes).sort();
|
|
114
|
+
const { body } = answer;
|
|
115
|
+
if (body.error === "scoped_aggregate_unavailable") {
|
|
116
|
+
// SegmentStream's explanation is for operators; the caller gets the reason's code.
|
|
117
|
+
return Effect.logWarning("ontology.metric_query.row_scopes_unplannable").pipe(Effect.annotateLogs({
|
|
118
|
+
requestId,
|
|
119
|
+
diagnostic: typeof body.detail === "string" ? body.detail : "No detail.",
|
|
120
|
+
scoped: Object.keys(scopes).sort().join(","),
|
|
121
|
+
}), Effect.andThen(Effect.fail(new ScopedMetricQueryUnavailable({
|
|
122
|
+
entity: typeof body.entity === "string" ? body.entity : first,
|
|
123
|
+
reason: "unplannable",
|
|
124
|
+
}))));
|
|
125
|
+
}
|
|
126
|
+
if (answer.status < 200 || answer.status >= 300 || confirmsRowScopes(body, scopes)) {
|
|
127
|
+
return Effect.succeed(answer);
|
|
128
|
+
}
|
|
129
|
+
return Effect.logError("ontology.metric_query.row_scopes_unconfirmed").pipe(Effect.annotateLogs({
|
|
130
|
+
requestId,
|
|
131
|
+
diagnostic: "SegmentStream answered a scoped metric query without confirming its row scopes.",
|
|
132
|
+
scoped: Object.keys(scopes).sort().join(","),
|
|
133
|
+
}), Effect.andThen(Effect.fail(new ScopedMetricQueryUnavailable({
|
|
134
|
+
entity: first,
|
|
135
|
+
reason: "unconfirmed",
|
|
136
|
+
}))));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The ontology service's metric query facade (docs/ontology-aggregates-spec.md, "Surfaces"). Both
|
|
140
|
+
* routes and the MCP tool run as the caller:
|
|
141
|
+
* 0. the call counts against the caller's {@link METRIC_QUERY_LIMIT}, whichever way it came;
|
|
142
|
+
* 1. the request's shape and bounds are checked, and its names resolved against the service's own
|
|
143
|
+
* manifest into every entity it reads;
|
|
144
|
+
* 2. the caller needs `<entity>:read` on all of them; a read scope on any of them travels along,
|
|
145
|
+
* `me` resolved, and SegmentStream keeps only the scope's rows wherever it reads that entity. A
|
|
146
|
+
* scope with a path from `me` is refused with `ScopedMetricQueryUnavailable`;
|
|
147
|
+
* 3. the body goes to SegmentStream as it came, earlier names renamed (`withCurrentMetricQueryNames`),
|
|
148
|
+
* with the workspace app credential, naming the entities checked, or the answer is
|
|
149
|
+
* `aggregate_unavailable` where the deployment has no credential;
|
|
150
|
+
* 4. an answer to a scoped request must confirm every scope it was sent in `execution.rowScopes`,
|
|
151
|
+
* and a scope SegmentStream cannot apply is refused with `ScopedMetricQueryUnavailable`: a platform
|
|
152
|
+
* that ignored the scopes never answers a scoped caller.
|
|
153
|
+
*/
|
|
154
|
+
export function makeMetricQueries(input) {
|
|
155
|
+
const { ontology, transport } = input;
|
|
156
|
+
const { manifest } = ontology;
|
|
157
|
+
const limiter = input.limiter ?? fixedWindowLimiter(METRIC_QUERY_LIMIT);
|
|
158
|
+
const run = (request) => Effect.gen(function* () {
|
|
159
|
+
const principal = yield* CurrentPrincipal;
|
|
160
|
+
const requestId = yield* RequestId;
|
|
161
|
+
const retryAfterSeconds = limiter.take(principal.id);
|
|
162
|
+
if (retryAfterSeconds !== undefined) {
|
|
163
|
+
return yield* new MetricQueryLimited({ retryAfterSeconds });
|
|
164
|
+
}
|
|
165
|
+
const touched = yield* request.touchedBy(request.body);
|
|
166
|
+
const missing = touched
|
|
167
|
+
.map((entity) => permissionOf(entity, READ_VERB))
|
|
168
|
+
.filter((permission) => !isAllowed(permission, principal));
|
|
169
|
+
if (missing.length > 0) {
|
|
170
|
+
return yield* new ReadDenied({ permissions: missing });
|
|
171
|
+
}
|
|
172
|
+
// Counting rows a list hides would leak them: each read scope travels to the warehouse.
|
|
173
|
+
const rowScopes = rowScopesOf(touched, principal, ontologyModel(ontology));
|
|
174
|
+
if (!rowScopes.ok) {
|
|
175
|
+
return yield* new ScopedMetricQueryUnavailable({
|
|
176
|
+
entity: rowScopes.entity,
|
|
177
|
+
reason: rowScopes.reason,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (transport === undefined) {
|
|
181
|
+
return yield* new MetricQueriesUnavailable();
|
|
182
|
+
}
|
|
183
|
+
const encoded = encodeRowScopes(rowScopes.scopes);
|
|
184
|
+
const answer = yield* transport({
|
|
185
|
+
...(request.entity === undefined ? {} : { entity: request.entity }),
|
|
186
|
+
body: withCurrentMetricQueryNames(request.body),
|
|
187
|
+
requestId,
|
|
188
|
+
touched,
|
|
189
|
+
...(encoded === undefined ? {} : { rowScopes: encoded }),
|
|
190
|
+
});
|
|
191
|
+
if (encoded === undefined) {
|
|
192
|
+
return answer;
|
|
193
|
+
}
|
|
194
|
+
return yield* scopedAnswer(answer, rowScopes.scopes, requestId);
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
model: (body) => run({ body, touchedBy: (value) => modelTouched(manifest, value) }),
|
|
198
|
+
entity: (entity) => (body) => run({ entity, body, touchedBy: (value) => entityTouched(manifest, entity, value) }),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Candidates must already be filtered for this authenticated caller's visibility. */
|
|
2
|
+
export declare function suggestNames(name: string, candidates: ReadonlyArray<string>): Array<string>;
|
|
3
|
+
export declare function suggestionDetail(detail: string, suggestions: ReadonlyArray<string>): string;
|