@segmentstream/ontology 0.0.0-stage → 0.92.1
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
package/README.md
CHANGED
|
@@ -1,3 +1,1656 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @segmentstream/ontology
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Releases up to the scope change were published as `@segmentstream-cloud/ontology`; versions continue
|
|
4
|
+
from them, with the same exports and API.
|
|
5
|
+
|
|
6
|
+
The backend of a SegmentStream workspace app, written with [Effect](https://effect.website).
|
|
7
|
+
The `ontology/` folder of the workspace repository declares entities, their properties,
|
|
8
|
+
the relationships between them, the roles and the actions people and agents may take.
|
|
9
|
+
From that one definition, the SDK builds the manifest, the tables in the workspace's
|
|
10
|
+
database and the HTTP service under `/api/ontology`. Every read and every action requires
|
|
11
|
+
a permission that the caller's roles grant, and every change to a record goes through an
|
|
12
|
+
action and is kept in history.
|
|
13
|
+
|
|
14
|
+
TypeScript definitions, handler records and SDK calls use camelCase: `jobTitle`,
|
|
15
|
+
`assignedOwnerId`, `createdAt`, and `changeStatus`. The SDK converts ontology names,
|
|
16
|
+
select identifiers and declared action input fields to snake_case for the generated
|
|
17
|
+
REST API, version 10 manifest and storage. TypeScript helpers and options stay
|
|
18
|
+
camelCase. JSON property contents, provider payloads, opaque action results and
|
|
19
|
+
historical change names remain data and are preserved. `formatManifest` serializes
|
|
20
|
+
the canonical wire contract shared by REST, storage and future transports.
|
|
21
|
+
|
|
22
|
+
When calling REST or MCP directly, use the snake_case wire name:
|
|
23
|
+
|
|
24
|
+
| TypeScript action | REST path | MCP tool |
|
|
25
|
+
| ------------------ | --------------------------------------------------------- | ----------------------------------- |
|
|
26
|
+
| `createDraft` | `/api/ontology/expense/actions/create_draft` | `expense_create_draft` |
|
|
27
|
+
| `attributePayroll` | `/api/ontology/bank_allocation/actions/attribute_payroll` | `bank_allocation_attribute_payroll` |
|
|
28
|
+
|
|
29
|
+
`ontology check` lists canonical wire action names. MCP `tools/list` gives exact tool names.
|
|
30
|
+
Incorrect casing and close typos
|
|
31
|
+
produce hints with a `suggestions` array for authenticated callers who may run the suggested
|
|
32
|
+
actions (MCP unknown-tool errors put it in `error.data`). Hints never execute an action or create
|
|
33
|
+
an alias; retry explicitly with the suggested name.
|
|
34
|
+
|
|
35
|
+
## Changes
|
|
36
|
+
|
|
37
|
+
Workspace deployment is automatic on main pushes by default. To accumulate changes for an
|
|
38
|
+
explicit release, commit `.segmentstream/deployment.json` with
|
|
39
|
+
`{ "schemaVersion": 1, "trigger": "manual" }`. Cloud keeps storing and indexing source,
|
|
40
|
+
while `segmentstream deploy` explicitly deploys current main with its existing authorization
|
|
41
|
+
and idempotency checks. The configuration takes effect from current main without an ontology
|
|
42
|
+
migration. Removing the file or selecting `"push"` restores automatic deployment; no scheduler
|
|
43
|
+
is added, and already-started deployments continue.
|
|
44
|
+
|
|
45
|
+
For an existing hourly release process, the optional `productionWindow` setting
|
|
46
|
+
in the same file restricts explicit production admission to configured UTC minutes.
|
|
47
|
+
Outside that window, create and retry return `deployment_window_closed` with the
|
|
48
|
+
next opening time; no attempt starts. This adds no scheduler or preview restriction.
|
|
49
|
+
|
|
50
|
+
SDK 0.33.0 requires `ActionContext.principal.identities`. Code that invokes an action handler
|
|
51
|
+
directly, including unit tests, must provide it (use `identities: []` when there are none),
|
|
52
|
+
plus `createdVia` and `authenticatedVia`. Identity facts use `provider`, `subject`, optional
|
|
53
|
+
`username`, and `verified: true`.
|
|
54
|
+
The general `Principal` and `prepare` context do not include this field.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npm install @segmentstream/ontology effect@4.0.0-rc.113
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`effect` is a peer dependency. Install the exact version above: each SDK release supports
|
|
63
|
+
one Effect version.
|
|
64
|
+
|
|
65
|
+
`ontology/` is a package of its own. The SDK is an ES module, so `ontology/package.json`
|
|
66
|
+
must set `"type": "module"`.
|
|
67
|
+
|
|
68
|
+
A deployment applies its ontology whether or not workflows are running. A run that is in flight
|
|
69
|
+
keeps executing the code of the deployment that started it, against the catalog the new deployment
|
|
70
|
+
applied, and current users, roles and revocations are still checked on every action. So change
|
|
71
|
+
what running workflows use in steps: add the new field, action or permission and deploy; move the
|
|
72
|
+
workflows to it; remove what earlier code used in a later deploy, once its runs have finished.
|
|
73
|
+
|
|
74
|
+
Manifest version 15 changes no field. Ontology SDKs 0.86.0 to 0.88.0 introduced it with a deploy
|
|
75
|
+
guard that refused or paused such deployments; the guard was withdrawn. A workspace still deployed
|
|
76
|
+
on one of those SDKs interrupts its own in-flight runs when a later deployment changes the ontology
|
|
77
|
+
beyond labels and descriptions, so upgrade it while no workflow is running.
|
|
78
|
+
|
|
79
|
+
Manifest version 14 names analytics as dbt does: metrics and
|
|
80
|
+
metric queries. Declare `entity(…, { metrics })` and `defineOntology({ metrics })`; query in
|
|
81
|
+
actions with `Order.queryMetrics({ metrics, … })`; the routes are `POST /api/ontology/query_metrics`
|
|
82
|
+
and `POST /api/ontology/<entity>/query_metrics`, with `metrics` and `orderBy[].metric`, and the
|
|
83
|
+
manifest lists `metrics` with `{ op: "metric" }` expression nodes. Code, requests and manifests
|
|
84
|
+
that use the earlier names keep working as deprecated aliases, which the release notes list; a
|
|
85
|
+
request that uses both names for one field is refused. Error codes such as
|
|
86
|
+
`invalid_aggregate_query` and `aggregate_unavailable` are unchanged.
|
|
87
|
+
|
|
88
|
+
SDK 0.37.0 writes manifest version 9, which adds `workflows` (see [Workflows](#workflows)).
|
|
89
|
+
Version 8, since SDK 0.23.0, added the metrics of the model.
|
|
90
|
+
`defineOntology({ metrics })` declares metrics of the model: arithmetic over metrics of entities,
|
|
91
|
+
`m.ref("acmeSales.ordered")`, and over other model metrics, `m.ref("planAttainment")`. `align` names the properties that stand for one another
|
|
92
|
+
across the entities a metric reads, or `null` for an entity without a counterpart, such as a
|
|
93
|
+
current snapshot without a date:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
defineOntology({
|
|
97
|
+
entities: [AcmeSales, AcmePlans],
|
|
98
|
+
metrics: {
|
|
99
|
+
planAttainment: m.divide(m.ref("acmeSales.ordered"), m.ref("acmePlans.plannedUnits"), {
|
|
100
|
+
align: [{ acmeSales: "orderDate", acmePlans: "month" }],
|
|
101
|
+
unit: "ratio",
|
|
102
|
+
description: "Ordered units over planned units for the same months.",
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Metric queries name a metric of the model by its bare name, `plan_attainment`, beside
|
|
109
|
+
`entity.metric`; a filter must reach every entity it reads unless its alignment says the entity has
|
|
110
|
+
no counterpart, and reading it requires reading each of those entities. `check` refuses aggregates
|
|
111
|
+
inside a metric of the model, unknown references, cycles and alignments that do not name every
|
|
112
|
+
entity once with properties of one type. Entity metrics take no `align`.
|
|
113
|
+
|
|
114
|
+
SDK 0.17.0 writes manifest version 7. It adds `m.avg`, `m.min` and `m.max`; a `where` option on
|
|
115
|
+
every aggregate, so that a metric reads only the rows its conditions accept (`m.sum("amount",
|
|
116
|
+
{ where: [{ property: "status", operator: "equals", value: "won" }], description })`); and
|
|
117
|
+
`balanceOver`, which names the date a metric's rows are snapshots over, so that requests group by
|
|
118
|
+
it or filter it to one day. Aggregate requests
|
|
119
|
+
take the `notEquals`, `notIn`, `gt`, `gte`, `lt`, `lte`, `contains`, `startsWith`, `endsWith` and
|
|
120
|
+
`inPeriod` operators, the `hour`, `quarter` and `year` granularities and a `timezone`. See
|
|
121
|
+
[ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md).
|
|
122
|
+
|
|
123
|
+
SDK 0.14.0 lets the app's own agent call the service as a paired member (see
|
|
124
|
+
[Agents](#agents)). Its migration adds the agent's runtime tables.
|
|
125
|
+
|
|
126
|
+
SDK 0.18.0 lets blob properties store any file. `accept` is optional and can restrict a
|
|
127
|
+
property to exact MIME types or ranges. Existing explicit restrictions keep their meaning.
|
|
128
|
+
See [File storage](#file-storage).
|
|
129
|
+
|
|
130
|
+
SDK 0.12.0 adds warehouse replication enabled by default for all stored business entities,
|
|
131
|
+
fields and business relationships. Existing history captures immutable snapshots and the
|
|
132
|
+
service wakes the publisher after commits. Upgrade and redeploy to activate it; workspace
|
|
133
|
+
configuration can intentionally exclude exported data. Automatic repairs build a private replacement
|
|
134
|
+
while the serving analytical projection keeps receiving changes and answering queries. See
|
|
135
|
+
[replication lifecycle](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-replication.md).
|
|
136
|
+
The next release supports schema changes during active copies: property renames adapt views over retained
|
|
137
|
+
JSON, and restored or newly included data is exported through scoped resumable scans. No manual pause
|
|
138
|
+
is required for deployment or recovery. Explicit opt-outs and supported-writer fences remain in force.
|
|
139
|
+
|
|
140
|
+
SDK 0.13.0 writes manifest version 6. Relationships may target warehouse-backed entities, from
|
|
141
|
+
stored or warehouse-backed entities, matched by value through `on`, and `unique` is allowed on
|
|
142
|
+
warehouse-backed properties as an unenforced assertion. Upgrading: 0.12 accepted an explicit `onDelete`, or `optional: true`, on a
|
|
143
|
+
warehouse → stored relationship; 0.13.0 `check` refuses both, so remove them. See
|
|
144
|
+
[ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md#relationships).
|
|
145
|
+
|
|
146
|
+
SDK 0.13.0 also serves aggregates. Upgrade notes:
|
|
147
|
+
|
|
148
|
+
- `POST /api/ontology/aggregate`, the MCP tool `aggregate` and, for each entity with metrics,
|
|
149
|
+
`POST /api/ontology/<entity>/aggregate` answer metrics of the model as the caller. The
|
|
150
|
+
service checks `<entity>:read` on every entity a request touches, then forwards it to
|
|
151
|
+
SegmentStream with `SEGMENTSTREAM_APP_CREDENTIAL`. Previews and local development have no
|
|
152
|
+
credential and answer `503 aggregate_unavailable`.
|
|
153
|
+
- `m.countDistinct(property)` counts the distinct values of a text, email, url, select,
|
|
154
|
+
integer, date or timestamp property.
|
|
155
|
+
- Stored entities may declare `metrics`, computed over their replicated records; the built-in
|
|
156
|
+
`User` cannot.
|
|
157
|
+
- `aggregate` is reserved for the route and the MCP tool: rename an entity with that name
|
|
158
|
+
before upgrading.
|
|
159
|
+
- Under `signIn: { policy: "open" }`, anyone who signs in holds `member`, so `check` refuses a
|
|
160
|
+
`member` whose wildcard (`*:read`, the default, or `*:*`) would let strangers read
|
|
161
|
+
warehouse-backed entities through aggregates. Grant those to `member` by name
|
|
162
|
+
(`acme_sales:read`), or keep the `invited` policy.
|
|
163
|
+
|
|
164
|
+
See [ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md)
|
|
165
|
+
and [ontology analytics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-aggregates-spec.md).
|
|
166
|
+
|
|
167
|
+
## File storage
|
|
168
|
+
|
|
169
|
+
Use `p.blob({ description: "Original source file." })` to retain a file's original bytes.
|
|
170
|
+
Without `accept`, a blob property accepts every concrete MIME type, including custom formats
|
|
171
|
+
and `application/octet-stream`. `p.blob()` also defaults to all files, though ontology checks
|
|
172
|
+
require a description. The manifest publishes this policy as `accept: ["*/*"]`.
|
|
173
|
+
|
|
174
|
+
Restrict a property when its business rules require a format:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
content: p.blob({
|
|
178
|
+
description: "Original POS report or bank statement.",
|
|
179
|
+
accept: [
|
|
180
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
181
|
+
"application/vnd.ms-excel",
|
|
182
|
+
"text/csv",
|
|
183
|
+
"text/tab-separated-values",
|
|
184
|
+
],
|
|
185
|
+
maxBytes: 10 * 1024 * 1024,
|
|
186
|
+
}),
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`accept` takes MIME types, not extensions. Matching ignores case; `image/*` accepts image
|
|
190
|
+
types and `*/*` accepts all types. Empty lists, duplicate entries, invalid MIME types and
|
|
191
|
+
parameters such as `; charset=utf-8` are rejected during ontology checks.
|
|
192
|
+
`BLOB_CONTENT_TYPES` lists common types as a convenience; it is not a global allow-list.
|
|
193
|
+
|
|
194
|
+
| Source format | MIME type |
|
|
195
|
+
| --------------------- | ------------------------------------------------------------------------- |
|
|
196
|
+
| XLSX | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
|
|
197
|
+
| XLS | `application/vnd.ms-excel` |
|
|
198
|
+
| CSV | `text/csv` |
|
|
199
|
+
| TSV | `text/tab-separated-values` |
|
|
200
|
+
| JSON | `application/json` |
|
|
201
|
+
| TXT | `text/plain` |
|
|
202
|
+
| DOCX | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
|
|
203
|
+
| ZIP | `application/zip` |
|
|
204
|
+
| Unknown binary format | `application/octet-stream` |
|
|
205
|
+
|
|
206
|
+
Uploads still require a valid concrete MIME type, a nonempty file within the property's
|
|
207
|
+
`maxBytes` and the 250 MiB service limit, available workspace quota and action permissions.
|
|
208
|
+
A refusal for a platform limit is an `action_rejected` body with `limit`: `storage_quota` with
|
|
209
|
+
`used_bytes`, `requested_bytes` and `quota_bytes`; `pending_uploads` with `pending` and `max`; or
|
|
210
|
+
`member_attachments` with `live` and `max`, so an app or agent can explain it in its own words.
|
|
211
|
+
Completion verifies the stored MIME metadata, exact byte count and upload-session binding.
|
|
212
|
+
PDF, JPEG and PNG retain their prefix-signature checks; plain text retains its UTF-8 prefix
|
|
213
|
+
check. Other formats are stored as opaque bytes, without parsing, conversion, archive
|
|
214
|
+
extraction or malware scanning. A MIME declaration does not prove what a file contains.
|
|
215
|
+
Downloads use attachment disposition; only JPEG and PNG may request inline display.
|
|
216
|
+
Agent attachment promotion honors the same `accept` and `maxBytes` restrictions.
|
|
217
|
+
|
|
218
|
+
Server-side `OntologyBackend` extensions can use `blobs.target(uploadId, principal)` to
|
|
219
|
+
inspect a session's persisted entity, action, property, record ID or create intent before
|
|
220
|
+
continuing an upload. It checks the session owner, workspace branch, deployed manifest
|
|
221
|
+
and current member permissions without provider calls or writes. It is not an HTTP
|
|
222
|
+
endpoint and returns no storage key or transfer grant. Custom blob adapters may omit
|
|
223
|
+
this optional method; extensions requiring target inspection must then deny the operation.
|
|
224
|
+
|
|
225
|
+
Upgrade the ontology SDK to 0.18.0 or later, update any existing `accept` list that should
|
|
226
|
+
allow more types, then check, migrate and redeploy the ontology. The app SDK storage client
|
|
227
|
+
0.13.1 or later uses `application/octet-stream` when the browser supplies no MIME type.
|
|
228
|
+
REST callers should supply that type themselves for unknown formats.
|
|
229
|
+
|
|
230
|
+
See [blob storage](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-blob-storage-implementation.md)
|
|
231
|
+
for upload tickets, transfer grants and retained revisions.
|
|
232
|
+
|
|
233
|
+
SDK 0.56.0 adds server-side file transfers through `Files`. In an action, declare
|
|
234
|
+
`files: [uploads.target(self, "content")]`, stage a stream in `prepare`, then attach the
|
|
235
|
+
returned ticket in `handler`. This uses the same private bucket, upload sessions,
|
|
236
|
+
permissions, quotas and completion checks as browser uploads. `Files.open` streams an
|
|
237
|
+
authorized retained revision for sending to another provider. Use `files: []` when
|
|
238
|
+
preparation only reads files and has no connections. Callers of staging actions provide
|
|
239
|
+
an idempotency key; workflow action calls derive a stable key from the run, step and input
|
|
240
|
+
when omitted. Streams stay within the step; carry tickets and references between steps.
|
|
241
|
+
See [server-side file transfers](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-blob-storage-implementation.md#server-side-file-transfers)
|
|
242
|
+
for the API and recovery rules.
|
|
243
|
+
|
|
244
|
+
Buffered `get`/`post`/`request` calls receive the provider's response through a streamed
|
|
245
|
+
proxy call and collect up to 64 MiB (`MAXIMUM_CONNECTION_RESPONSE_BYTES`); a larger response
|
|
246
|
+
fails with `invalid_response`. To process a response incrementally, or one larger than that,
|
|
247
|
+
use `connection.stream({ method: "GET", path })` inside `Effect.scoped` and consume its `body`
|
|
248
|
+
as an Effect Stream before closing the scope. Request bodies remain capped at 2 MiB.
|
|
249
|
+
|
|
250
|
+
## Define the ontology
|
|
251
|
+
|
|
252
|
+
Actions run their handler at most once by default. Declare `retryOnDeadlock: true`
|
|
253
|
+
in `action({ ... })` only when its handler and any wrappers have solely
|
|
254
|
+
transactional database effects, treat input and prepared data as immutable, and
|
|
255
|
+
recreate mutable state per invocation. Trigger predicates and mappings must be
|
|
256
|
+
pure. The default SQL runtime retries a confirmed PostgreSQL `40P01` at most twice,
|
|
257
|
+
after completed rollback and connection release, with short randomized backoff
|
|
258
|
+
inside the original handler deadline and admission slot. It rebuilds runtime,
|
|
259
|
+
edit and trigger state while preserving action and request identity. `prepare`
|
|
260
|
+
runs once; workflow dispatch and after-commit hooks run after the successful
|
|
261
|
+
commit. They are never replayed. This declaration is independent of
|
|
262
|
+
`hints.idempotent`, appears in the deployed action manifest, and does not retry
|
|
263
|
+
timeouts, cancellation, permission failures, cleanup failures or uncertain commits.
|
|
264
|
+
Custom service runtime implementations own their retry policy; wrappers around
|
|
265
|
+
the default backend are not replayed. See
|
|
266
|
+
[action deadlines and load](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology.md#action-deadlines-and-load)
|
|
267
|
+
for the full replay contract.
|
|
268
|
+
|
|
269
|
+
An action with `hints: { connectionsReadOnly: true, destructive: false }` may read
|
|
270
|
+
providers in `prepare` and write local records in `handler`. All its connection
|
|
271
|
+
clients enforce GET/HEAD/OPTIONS without bodies or method overrides. MCP treats
|
|
272
|
+
these provider reads as closed-world; destructive or irreversible local writes
|
|
273
|
+
still require confirmation. This differs from `readOnly: true`, which requires
|
|
274
|
+
a prepare-only action and permits no local writes.
|
|
275
|
+
|
|
276
|
+
An action that agents confirm can describe its own confirmation card with `confirm`: a
|
|
277
|
+
read-only function of the decoded input, the principal and `locale`, the language of the chat
|
|
278
|
+
the agent asks in. It runs after the permission check, with reads and no writes, and returns
|
|
279
|
+
the card's `title` (a verb phrase), an optional `icon` emoji, the `facts` the person checks
|
|
280
|
+
before confirming (one line each, optionally linked with an https `url`), collapsed `details`
|
|
281
|
+
and an optional page `link`. All of it is plain text in that language, without ids, codes or
|
|
282
|
+
markup: these are the workspace's words, and agents refuse a card that is not. Fail with
|
|
283
|
+
`ActionRejected` when the action would refuse this input: the agent then tells the person why
|
|
284
|
+
instead of asking. Without `confirm`, agents show the action's fields.
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
send: action({
|
|
288
|
+
description: "Send the envelope for signature.",
|
|
289
|
+
hints: { destructive: false, irreversible: true },
|
|
290
|
+
input: Schema.Struct({ envelope: self.ref }),
|
|
291
|
+
confirm: ({ input, locale }) =>
|
|
292
|
+
Effect.gen(function* () {
|
|
293
|
+
const envelope = yield* self.get(input.envelope);
|
|
294
|
+
|
|
295
|
+
if (envelope.status !== "draft") {
|
|
296
|
+
return yield* new ActionRejected({ message: texts(locale).alreadySent });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
title: texts(locale).send,
|
|
301
|
+
icon: "✍️",
|
|
302
|
+
facts: [{ icon: "📄", text: envelope.title }],
|
|
303
|
+
details: [texts(locale).emailNow],
|
|
304
|
+
};
|
|
305
|
+
}),
|
|
306
|
+
handler: ({ input }) => self.update(input.envelope, { status: "sent" }),
|
|
307
|
+
}),
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
// ontology/lead.ts
|
|
312
|
+
import { action, ActionRejected, entity, p, rel, role, User } from "@segmentstream/ontology";
|
|
313
|
+
import { Effect, Schema } from "effect";
|
|
314
|
+
|
|
315
|
+
export const sales = role("sales", {
|
|
316
|
+
description: "Works leads to a pilot.",
|
|
317
|
+
permissions: ["lead:read", "lead:win"],
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
export const Lead = entity("lead", {
|
|
321
|
+
id: "ent_01J9Q4M8ZK",
|
|
322
|
+
label: "Lead",
|
|
323
|
+
plural: "Leads",
|
|
324
|
+
title: "email",
|
|
325
|
+
description: "A person who asked for a pilot.",
|
|
326
|
+
properties: {
|
|
327
|
+
email: p.email({ unique: true, description: "The address they submitted." }),
|
|
328
|
+
status: p.select(
|
|
329
|
+
[
|
|
330
|
+
{ value: "new", label: "New", description: "Nobody has replied yet." },
|
|
331
|
+
{ value: "won", label: "Won", description: "They became a customer. Final." },
|
|
332
|
+
],
|
|
333
|
+
{ default: "new", managed: true },
|
|
334
|
+
),
|
|
335
|
+
},
|
|
336
|
+
relationships: {
|
|
337
|
+
owner: rel.one(User, {
|
|
338
|
+
optional: true,
|
|
339
|
+
inverse: "ownedLeads",
|
|
340
|
+
onDelete: "setNull",
|
|
341
|
+
phrase: ["is owned by", "owns"],
|
|
342
|
+
description: "The team member responsible for the next step.",
|
|
343
|
+
}),
|
|
344
|
+
},
|
|
345
|
+
}).actions((self) => ({
|
|
346
|
+
win: action({
|
|
347
|
+
description: "Mark the lead as won.",
|
|
348
|
+
input: Schema.Struct({ lead: self.ref }),
|
|
349
|
+
// Requires lead:win, the default: <entity>:<action name>.
|
|
350
|
+
handler: ({ input }) =>
|
|
351
|
+
Effect.gen(function* () {
|
|
352
|
+
const lead = yield* self.get(input.lead, { forUpdate: true });
|
|
353
|
+
|
|
354
|
+
if (lead.status === "won") {
|
|
355
|
+
return yield* new ActionRejected({ message: "The lead is already won." });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
yield* self.update(lead, { status: "won" });
|
|
359
|
+
}),
|
|
360
|
+
}),
|
|
361
|
+
}));
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
// ontology/index.ts
|
|
366
|
+
import { defineOntology } from "@segmentstream/ontology";
|
|
367
|
+
|
|
368
|
+
import { Lead, sales } from "./lead";
|
|
369
|
+
|
|
370
|
+
export default defineOntology({
|
|
371
|
+
entities: [Lead],
|
|
372
|
+
roles: [sales],
|
|
373
|
+
// What every active app user may do. Without it, members may only read: ["*:read"].
|
|
374
|
+
member: { permissions: ["*:read"] },
|
|
375
|
+
// Who may become a member by signing in: only invited people (the default), or anyone ("open").
|
|
376
|
+
signIn: { policy: "invited" },
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
- `entity(name, { … })` declares an entity. `id` is its stable identity; do not change it.
|
|
381
|
+
- `p.text`, `p.email`, `p.url`, `p.integer`, `p.number`, `p.boolean`, `p.date`,
|
|
382
|
+
`p.timestamp`, `p.select` and `p.json` declare properties. `managed: true` keeps a
|
|
383
|
+
property out of the generated `update` action.
|
|
384
|
+
- `rel.one` and `rel.many` declare relationships. Between stored entities, `rel.one` adds a
|
|
385
|
+
key and `rel.many` a join table. With a warehouse-backed end, `rel.one` declares one `on`
|
|
386
|
+
pair and is matched by value. `User` is the built-in entity for app users and service
|
|
387
|
+
principals; `AgentChat` (`agent_chat`) is the built-in entity for the group chats of the
|
|
388
|
+
app's agent (see [Agents](#agents)). Every ontology includes both. Text feedback defaults on with built-in `AppFeedback`; files follow the non-feedback-blob policy
|
|
389
|
+
(or an explicit opt-in) and add `AppFeedbackFile` (see [App feedback](#app-feedback)).
|
|
390
|
+
- `role(name, { description, permissions })` declares a role: a named set of permissions.
|
|
391
|
+
`member` is built in: every active app user holds it, with the permissions of
|
|
392
|
+
`defineOntology({ member })`.
|
|
393
|
+
- `.actions((self) => ({ … }))` adds actions. Each action has a description, an input
|
|
394
|
+
schema and an Effect handler. It requires the permission `<entity>:<action name>`, or the
|
|
395
|
+
one its `permission` names.
|
|
396
|
+
- `signIn.browser` optionally declares `{ methods: ["email", "segmentstream"], primary: "email", autoRedirect: false, local: { port: 3000 } }`.
|
|
397
|
+
Methods must be nonempty and unique, and primary must be enabled. Automatic redirect is allowed only
|
|
398
|
+
for sole SegmentStream authentication. Local OIDC is optional and requires SegmentStream plus a port
|
|
399
|
+
from 1 through 65535. Omission preserves existing authentication. Deployment derives the versioned
|
|
400
|
+
`SEGMENTSTREAM_BROWSER_AUTH` binding from manifest capability 12; mismatches fail closed.
|
|
401
|
+
SegmentStream enrollment claims only active unclaimed invitations using a verified provider email;
|
|
402
|
+
established members require explicit independent login proof. An OIDC member can explicitly attach
|
|
403
|
+
email authentication in dual mode without changing their member ID. Reverse linking requires the
|
|
404
|
+
independently verified email to match their canonical workspace email; missing or different email
|
|
405
|
+
is rejected as `email_identity_mismatch`. Forward email-to-OIDC linking may use a different account
|
|
406
|
+
email because it proves both identities explicitly. Keep OIDC available until every
|
|
407
|
+
affected member has linked email before restoring an email-only deployment. `User.sendSignInLink`
|
|
408
|
+
preserves Neon links in compatibility and dual mode; sole SegmentStream uses interactive central
|
|
409
|
+
sign-in, preserving templates, reply routing and the requested landing path.
|
|
410
|
+
- `signIn: { policy }` decides who may become a member by signing in. `invited`, the
|
|
411
|
+
default, admits only people whose email a member already has, from an invitation or a
|
|
412
|
+
role grant; anyone else is answered 403 `not_invited`. `open` makes anyone who signs in a
|
|
413
|
+
member holding only `member`. The manifest carries the policy.
|
|
414
|
+
`signIn.codeLink: true` makes the platform's sign-in code emails also link to the app's
|
|
415
|
+
`/sign-in` page with the code in the URL fragment; the app SDK's `useSignIn()` fills it in and
|
|
416
|
+
waits for the person to confirm. Off when omitted; see the app authentication guide.
|
|
417
|
+
Explicit app opt-in (never a platform or starter default): channel admission is independent: `signIn: { policy: "invited", channels: { telegram: "open" } }`
|
|
418
|
+
lets private channel senders become email-less members. Missing channels require pairing; email
|
|
419
|
+
`policy: "open"` never opens a channel. `CHANNEL_PROVIDERS` lists supported providers (`telegram`).
|
|
420
|
+
Every configured channel requires a matching `chat.<provider>` adapter; object values are reserved.
|
|
421
|
+
Open channels require explicit `member.permissions`: entity wildcards and generated create/update/delete
|
|
422
|
+
grants (including grants through entity `:*`) are errors. Named shared reads warn for review; public
|
|
423
|
+
catalogs are valid. The hint is “move staff reads into a named role”. Actions such as
|
|
424
|
+
`updateMine` must check ownership with `principal.id`. Closing a channel stops new registrations;
|
|
425
|
+
existing members keep working. Blocking is sticky and independent of activation; deleting a member releases identities.
|
|
426
|
+
`signIn.onNewUser: "person:ensureMine"` optionally names an app-owned onboarding action. It must exist,
|
|
427
|
+
accept `{}`, be runnable by `member`, declare `hints.idempotent: true`, and have no `prepare` or
|
|
428
|
+
external connections. Its handler must have no external effects. The runtime runs it as the new user
|
|
429
|
+
with only member permissions after admission commits, before the first model turn or Mini App session.
|
|
430
|
+
Handler writes and its completion marker commit together, under a five-second deadline. Failure logs
|
|
431
|
+
diagnostics and leaves it pending for retry on the next eligible contact; admission still succeeds.
|
|
432
|
+
The service shares one action admission gate between onboarding and ordinary actions, reserving
|
|
433
|
+
database capacity for other requests before either kind of handler acquires a connection.
|
|
434
|
+
Invited/paired-only members never run this hook. The platform supplies mechanism; the app owns channel
|
|
435
|
+
policy, grants, hook content, notice text, and lower limits. Nothing defaults to open.
|
|
436
|
+
`check(config, { agentConfig })` warns if open channels lack `firstContact.notice` or `limits` when
|
|
437
|
+
agent config is supplied. The ontology CLI cannot see that config and says those checks are skipped.
|
|
438
|
+
- `emails: { agreementReview: { subject, eyebrow?, heading, card?, note?, paragraphs?, action, footer?, replyTo? } }`
|
|
439
|
+
declares the workspace's own emails, in plain text with `{{value}}` placeholders and the
|
|
440
|
+
built-in `{{member.name}}`, `{{member.firstName}}` and `{{workspace.name}}`. A handler sends
|
|
441
|
+
one with `User.sendSignInLink(user, { template: "agreementReview", returnTo: "/shared/1", values })`:
|
|
442
|
+
after the commit, in production, the platform emails the member a link that signs them in and
|
|
443
|
+
opens `returnTo`, laid out in its design, from the workspace's name, with replies to `replyTo`
|
|
444
|
+
or else the member who ran the action. Without `template` the
|
|
445
|
+
member gets the platform's own invitation or sign-in email. `check` reports `invalid-email`.
|
|
446
|
+
- `defineOntology` checks the definition and builds the manifest. It throws
|
|
447
|
+
`InvalidOntology` with every diagnostic when the definition has errors. Entities,
|
|
448
|
+
actions and select options need a description.
|
|
449
|
+
|
|
450
|
+
## Workflows
|
|
451
|
+
|
|
452
|
+
A workflow is a durable process over time, written with the
|
|
453
|
+
[Vercel Workflow SDK](https://useworkflow.dev): it can wait, retry a step, run several actions and
|
|
454
|
+
call connections. The destination decides the home, not the data source: work that outputs
|
|
455
|
+
tables is a job in `pipeline/`; records change through actions; work that runs on its own over
|
|
456
|
+
time and outputs records, paged provider reads included, is a workflow in `ontology/workflows/`.
|
|
457
|
+
Declare one per
|
|
458
|
+
file with `workflow` and `cron` from `@segmentstream/ontology/workflows`, and list them in
|
|
459
|
+
`defineOntology({ workflows })`:
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
// ontology/workflows/remind-stale-leads.ts
|
|
463
|
+
import { cron, p, run, workflow } from "@segmentstream/ontology/workflows";
|
|
464
|
+
import { Effect, Schema } from "effect";
|
|
465
|
+
import { Lead } from "../lead";
|
|
466
|
+
|
|
467
|
+
async function remindStaleLeads(input: { olderThanDays: number }) {
|
|
468
|
+
"use workflow";
|
|
469
|
+
const leads = await findStale(input.olderThanDays);
|
|
470
|
+
for (const lead of leads) await remind(lead.id);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function findStale(olderThanDays: number) {
|
|
474
|
+
"use step";
|
|
475
|
+
const before = new Date(Date.now() - olderThanDays * 86_400_000).toISOString();
|
|
476
|
+
|
|
477
|
+
return run(
|
|
478
|
+
Effect.map(
|
|
479
|
+
Lead.find({ where: { status: "new", updatedAt: { lt: before } }, limit: 100 }),
|
|
480
|
+
(leads) => leads.map((lead) => ({ id: lead.id })),
|
|
481
|
+
),
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function remind(leadId: string) {
|
|
486
|
+
"use step";
|
|
487
|
+
return run(Lead.actions.addNote({ lead: leadId, body: "No movement for three days." }));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export default workflow("remindStaleLeads", {
|
|
491
|
+
description: "Write a reminder note on every lead that has not moved.",
|
|
492
|
+
role: "reminders",
|
|
493
|
+
input: Schema.Struct({ olderThanDays: p.integer({ description: "Days without movement." }) }),
|
|
494
|
+
run: remindStaleLeads,
|
|
495
|
+
triggers: [
|
|
496
|
+
cron("weekdayMorning", "0 8 * * 1-5", {
|
|
497
|
+
description: "Every weekday at 08:00 UTC.",
|
|
498
|
+
input: { olderThanDays: 3 },
|
|
499
|
+
}),
|
|
500
|
+
],
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
- Import what the file uses at its top level from `@segmentstream/ontology/workflows`,
|
|
505
|
+
which re-exports `p` for the input, and `Schema` from `effect`. The Workflow SDK bundles a
|
|
506
|
+
workflow file's top-level imports for a runtime without Node built-ins; this entry, and entity
|
|
507
|
+
and action declarations, import none, while the package root does. Steps may use anything:
|
|
508
|
+
the SDK strips their bodies from that bundle.
|
|
509
|
+
- `workflow(name, { description, role, input?, run, triggers?, connections? })` only records
|
|
510
|
+
the declaration; `check` and the evaluation of `index.ts` never call `run`, and the
|
|
511
|
+
`"use workflow"` and `"use step"` directives are inert strings until the Workflow SDK's
|
|
512
|
+
compiler builds the service (`ontologyWorkflows()` in `ontology/nitro.config.ts`, below).
|
|
513
|
+
Keep workflow files in the directories it compiles (`./workflows` by default): a `run` the
|
|
514
|
+
SDK did not compile is logged as `ontology.workflow.not_compiled` when the service starts, and
|
|
515
|
+
starting it answers 503 `workflow_not_compiled`.
|
|
516
|
+
- `name` is camelCase and unique; it names the workflow's user. `description` is required.
|
|
517
|
+
- `role` is a declared role other than `member`, and not one that grants `*:*`. The runs hold
|
|
518
|
+
what it and `member` grant.
|
|
519
|
+
- `input` is a `Schema.Struct`; omitted means `{}`. `run` receives the decoded input in
|
|
520
|
+
TypeScript names, and as its second argument `{ startedBy }`: the trigger, the member, or the
|
|
521
|
+
platform (with the person it acted for) that started the run.
|
|
522
|
+
- `triggers` lists what starts the workflow on its own. Each carries a `kind`: `cron`,
|
|
523
|
+
`action` or `webhook`. `cron(name, expression, { description, input? })` takes five fields in UTC, each `*`, numbers,
|
|
524
|
+
ranges, steps after `*` or a range (`*/5`, `0-30/10`; not `5/15`) or lists, with at most one of
|
|
525
|
+
day-of-month and day-of-week restricted and no month or day names, as Vercel accepts them. Its `input` is
|
|
526
|
+
written in TypeScript names and must decode with the workflow's `input`. Trigger names are
|
|
527
|
+
camelCase, `<workflow>.<trigger>` on the wire (`remind_stale_leads.weekday_morning`).
|
|
528
|
+
- `after(name, target, { description, when?, input? })` starts the workflow after an action
|
|
529
|
+
commits: `target` is an action handle (`Lead.actions.changeStatus`) or an entity handle (`Lead`,
|
|
530
|
+
any of its actions). `when` and `input` are synchronous functions of the event
|
|
531
|
+
(`{ action, actionId, at, principal, input, result, edits }`) and the record
|
|
532
|
+
(`{ record, before, after }`, typed as the entity's records); `input` omitted means `{}`.
|
|
533
|
+
`changed(field, { from?, to? })` is a `when` for the common case. A workflow reacts to an action,
|
|
534
|
+
not to a field. The manifest records the kind, name, description, entity and action, never the
|
|
535
|
+
functions. At most 20 action triggers per ontology; `check` reports `invalid-trigger`,
|
|
536
|
+
`trigger_action_unknown`, `trigger_function_async` and `workflow_trigger_limit_exceeded`.
|
|
537
|
+
- `webhook(name, { description, signature?, headers? })` starts the workflow for each delivery a
|
|
538
|
+
provider pushes to `/api/webhooks/<name>`, whose wire name is unique across the ontology. The
|
|
539
|
+
workflow's `input` is `webhookDelivery` (`body`, `contentType`, `query`, `headers`,
|
|
540
|
+
`receivedAt`) and it has only webhook triggers. Without `signature`, the URL ends in a token
|
|
541
|
+
derived from the workspace's webhook secret; with `{ scheme: "stripe", secret }`, each delivery's
|
|
542
|
+
signature is checked against that workspace secret through SegmentStream. `headers` lists up to
|
|
543
|
+
10 request headers the delivery carries. The route answers `200` once the run started, `404`,
|
|
544
|
+
`401`, `413` or `415` to refused deliveries and `503` when the run could not start. Production
|
|
545
|
+
only. `check` reports the `workflow_webhook_*` codes; at most 20 webhooks per ontology.
|
|
546
|
+
- `connections` lists the connections the steps call directly; each must be in
|
|
547
|
+
`defineOntology({ connections })`. Connections declared on an action stay that action's.
|
|
548
|
+
- At most 50 workflows. `check` reports `workflow_role_unknown`, `workflow_role_is_member`,
|
|
549
|
+
`workflow_role_grants_nothing` (a warning: with member's grants, the role grants no action),
|
|
550
|
+
`workflow_role_wildcard` (the role grants `*:*`), `workflow_input_invalid`,
|
|
551
|
+
`workflow_cron_invalid`, `workflow_cron_never_fires` (a warning: no listed month has the
|
|
552
|
+
day), `workflow_run_missing` (`run` is not a function), `invalid-workflow`, `invalid-property`
|
|
553
|
+
(a property option its constructor does not take), and the usual name, duplicate and
|
|
554
|
+
description checks.
|
|
555
|
+
|
|
556
|
+
### Code inside a step
|
|
557
|
+
|
|
558
|
+
Inside a `"use step"` function, `run(effect)` runs an Effect with the ontology's runtime as its
|
|
559
|
+
services, bound to the run's workflow user. Entity handles work as in an action handler:
|
|
560
|
+
`Lead.find` and `Lead.get` read, `Lead.create`, `update`, `upsertBy`, `delete`, `link` and
|
|
561
|
+
`unlink` run the generated actions, and `Lead.actions.addNote(input)` runs the authored action
|
|
562
|
+
with its input in TypeScript names; so do the workflow's `connections`. Every read and action goes
|
|
563
|
+
through the same path as the HTTP routes: it is checked against the workflow user's permissions,
|
|
564
|
+
validated, and recorded in history under the workflow user. Batch writes (`createMany`,
|
|
565
|
+
`updateMany`, `upsertMany`) are refused in steps; run an action that writes the batch. In production,
|
|
566
|
+
workflow roles can explicitly grant `agent_chat:post_message` and `agent_chat:preview_message` for
|
|
567
|
+
attributed chat delivery and its destination preview. Preview deployments refuse both actions;
|
|
568
|
+
agent tasks cannot post to chat on either target.
|
|
569
|
+
|
|
570
|
+
A step calls a model through Vercel AI Gateway with the AI SDK (`ai`), outside `run`: the
|
|
571
|
+
deployment authenticates with its Vercel project identity, so the workspace holds no key. Give
|
|
572
|
+
each model call a step of its own that returns its result, and store the result with an action
|
|
573
|
+
in the next step, since a retried step calls the model again. A workspace's spent Gateway budget
|
|
574
|
+
refuses the call with `402`. The ontology SDK does not wrap these calls.
|
|
575
|
+
|
|
576
|
+
`run` resolves with the effect's value, which the step returns, so keep it JSON-serializable.
|
|
577
|
+
A failure rejects: a typed failure of the handles (`NotFound`, `Conflict`, `ActionRejected`,
|
|
578
|
+
`PermissionDenied`, `ConnectionError`) becomes an `Error` named after it with its message and the
|
|
579
|
+
request id; anything else becomes a generic message with the request id, and the service logs
|
|
580
|
+
the full cause as `ontology.workflow.step.failed`. The Workflow SDK records the failed step and
|
|
581
|
+
retries it, except a refusal that would repeat (`PermissionDenied`, `ActionRejected`, an invalid
|
|
582
|
+
input), which is marked fatal. A read the role does not grant fails the step. Calling `run` in
|
|
583
|
+
the `"use workflow"` body or outside a run throws `WorkflowStepRequired`. `run` reads the
|
|
584
|
+
step from the Workflow SDK's process-wide step context and never imports the SDK, which a
|
|
585
|
+
deployed function need not be able to resolve.
|
|
586
|
+
|
|
587
|
+
A retried step runs its actions again: the SDK has no idempotency for actions, so write steps
|
|
588
|
+
whose actions tolerate a repeat (an `upsertBy`, a check before a write), as the example's
|
|
589
|
+
`submitLead` does with its submission id.
|
|
590
|
+
|
|
591
|
+
The binding: when the ontology service builds its routes it installs, on `globalThis`, the
|
|
592
|
+
runtime that serves this process's runs, with the services the routes run with; the step
|
|
593
|
+
callback builds the service first when this instance has not. Inside a step, `run` reads the run
|
|
594
|
+
from the Workflow SDK's step context (`getWorkflowMetadata()`), finds the declared workflow whose
|
|
595
|
+
compiled `run` has the message's workflow id, and checks it against the run's own record in the
|
|
596
|
+
world (`runs.get(runId).workflowName`, read once per run), refusing a step whose workflow is not
|
|
597
|
+
its run's; a run the world does not show yet fails the step, which is retried. It then resolves
|
|
598
|
+
the workflow's user and provides the runtime bound to it. No token is
|
|
599
|
+
involved; connection calls carry the callback request's Vercel OIDC token
|
|
600
|
+
(`x-vercel-oidc-token`), else the deployment's.
|
|
601
|
+
|
|
602
|
+
### Agent tasks
|
|
603
|
+
|
|
604
|
+
A workflow hands the workspace's agent a prompt with `agentTask` from
|
|
605
|
+
`@segmentstream/ontology/workflows`, in its `"use workflow"` body, and waits for the
|
|
606
|
+
agent's final answer without holding compute. It is the agent members reach in chats, with the
|
|
607
|
+
same instructions, models, tools and sandbox, acting as the workflow's own user: it sees and does
|
|
608
|
+
exactly what the workflow's role and `member` grant, and ontology actions run without
|
|
609
|
+
confirmation, since no person is present. Keep the role narrow: a prompt may carry untrusted text.
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
import { agentTask, workflow } from "@segmentstream/ontology/workflows";
|
|
613
|
+
|
|
614
|
+
async function draftReply(input: { message: string }) {
|
|
615
|
+
"use workflow";
|
|
616
|
+
const prompt = await describeMessage(input.message); // a step that reads records
|
|
617
|
+
const result = await agentTask({ prompt, timeoutMs: 15 * 60_000 });
|
|
618
|
+
|
|
619
|
+
if (result.status === "completed") {
|
|
620
|
+
await saveDraft(input.message, result.text); // a step that runs an action
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
- `prompt` is 1 to 32,768 characters, all of it the workspace's: nothing is added to it.
|
|
626
|
+
`timeoutMs` defaults to 900,000 (15 minutes) and is 60,000 to 3,600,000. Invalid options fail
|
|
627
|
+
the workflow with `AgentTaskOptionsInvalid`, which names the option, and a call outside a
|
|
628
|
+
`"use workflow"` body (in a step, or outside a workflow) with `AgentTaskWorkflowRequired`;
|
|
629
|
+
neither is retried.
|
|
630
|
+
- It resolves with `{ status: "completed", text, truncated }`, the agent's final text;
|
|
631
|
+
`{ status: "needs_input", text, truncated }`, its question, when the agent would need a person;
|
|
632
|
+
`{ status: "failed", reason }` with `agent_unavailable` (the deployment has no agent, its agent
|
|
633
|
+
predates tasks, or the run is outside production), `busy` (the workspace has 10 live tasks; the
|
|
634
|
+
agent was not called) or `agent_failed` (its turn failed, its cost cap included); or
|
|
635
|
+
`{ status: "timed_out" }`. Text beyond 65,536 characters is cut and marked `truncated`.
|
|
636
|
+
- A task cannot post to chat in this release: `agent_chat.post_message` is refused for a task
|
|
637
|
+
whatever the role grants. The answer reaches people through the workflow's later steps.
|
|
638
|
+
- At least once: a start step retried after the agent accepted the task hands it over again, and
|
|
639
|
+
the agent ends the earlier session. Write prompts and later steps that tolerate a repeat.
|
|
640
|
+
- Tasks run only in production, where the agent's runtime tokens are valid. Locally and in
|
|
641
|
+
previews `agentTask` resolves with `agent_unavailable`.
|
|
642
|
+
|
|
643
|
+
`agentTask` creates a Workflow SDK hook and runs its start step, one of two steps the package
|
|
644
|
+
ships; the Workflow SDK finds and compiles them through `@segmentstream/ontology/workflows`
|
|
645
|
+
with the workflow's own. The step records the task in `_agent_tasks` under the hook's token,
|
|
646
|
+
mints its secret (32 random bytes, base64url, of which only the SHA-256 is stored; it is never
|
|
647
|
+
returned, logged or written to the run's event log) and posts `{ task_id, secret }` to the
|
|
648
|
+
agent's `POST /eve/segmentstream/v1/tasks` at `https://$VERCEL_PROJECT_PRODUCTION_URL`, with the
|
|
649
|
+
service's own Vercel OIDC token. `202` starts the task and `404` ends it as `agent_unavailable`;
|
|
650
|
+
no answer, `5xx`, and a `409` while the task is still pending fail the step, and the retry hands
|
|
651
|
+
the pending task over with a new secret. A retry finds a started or ended task and does not hand
|
|
652
|
+
it over again; any other answer ends the task as `agent_unavailable` and fails the step without a
|
|
653
|
+
retry. The workflow then races the hook against a sleep until the task's deadline, `timeoutMs`
|
|
654
|
+
after the start created it, so start-step retries count against the timeout. The agent's report through the task routes (see [Agents](#agents))
|
|
655
|
+
ends the task, the first report winning, and resumes the hook; if the sleep wins, the second step
|
|
656
|
+
times the task out unless it already ended and returns its outcome, so the workflow's result is
|
|
657
|
+
always the task's stored outcome. Each start deletes the tasks whose deadline passed 30 days ago.
|
|
658
|
+
|
|
659
|
+
### Runtime and routes
|
|
660
|
+
|
|
661
|
+
`ontology/nitro.config.ts` lists `ontologyWorkflows()` from `@segmentstream/ontology/nitro`
|
|
662
|
+
in `modules`, in place of the SDK's own `workflow/nitro`:
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
import { ontologyRoutes, ontologyWorkflows } from "@segmentstream/ontology/nitro";
|
|
666
|
+
import { defineNitroConfig } from "nitro/config";
|
|
667
|
+
|
|
668
|
+
export default defineNitroConfig({
|
|
669
|
+
compatibilityDate: "2026-09-11",
|
|
670
|
+
serverDir: false,
|
|
671
|
+
routes: ontologyRoutes("./server/handler.ts"),
|
|
672
|
+
plugins: ["./server/plugin.ts"],
|
|
673
|
+
modules: [ontologyWorkflows({ dirs: ["./workflows"] })],
|
|
674
|
+
});
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
It adds the SDK's transform to the server build and, before each build, bundles the workflows of
|
|
678
|
+
`dirs` and their steps into Nitro's build directory, with `/api/ontology` as the SDK's base path
|
|
679
|
+
and `ontology` as its queue namespace, both written into the generated entrypoint at build time
|
|
680
|
+
(no environment variable). It mounts one route in the ontology service's own server bundle,
|
|
681
|
+
`/api/ontology/.well-known/workflow/v1/flow`: since Workflow SDK 5 the flow handler runs the
|
|
682
|
+
steps itself, and background steps come back on the workflow topic. The route answers only a
|
|
683
|
+
`POST` queue delivery of the ontology's queue (`ce-vqsqueuename`, or the local world's
|
|
684
|
+
`x-vqs-queue-name`, starting with `__ontology_wkf_workflow_`) at its exact path, a trailing slash
|
|
685
|
+
refused; anything else is a 404. Before the first step of an instance it builds that instance's
|
|
686
|
+
ontology service through Nitro's own fetch of `/api/ontology/status`; a failure fails the delivery
|
|
687
|
+
for the queue to retry. On Vercel the route gets its own function with `maxDuration: "max"` and
|
|
688
|
+
the queue trigger `__ontology_wkf_workflow_*`; runs start on the same queue (`start` with the
|
|
689
|
+
namespace `ontology`). The app's own workflows use the SDK's default topics and the agent's use
|
|
690
|
+
topics of its own, so none of them receive another's messages. Nitro states that routes with queue
|
|
691
|
+
triggers are not reachable from the web; that is still to be confirmed on a deploy. If they are
|
|
692
|
+
reachable, the queue-header and run-record checks still refuse a request that is not the
|
|
693
|
+
ontology's own delivery.
|
|
694
|
+
|
|
695
|
+
Webhooks are not supported yet: no webhook route is mounted, since this version of the SDK derives
|
|
696
|
+
a webhook's token from the run. Hooks resumed from server code with `resumeHook` work, as agent
|
|
697
|
+
tasks' do.
|
|
698
|
+
|
|
699
|
+
`server/plugin.ts` calls `installWorkflowRuntime()` from `@segmentstream/ontology/server`
|
|
700
|
+
at startup: until the flow handler loads and sets the base path from the build, it sets it to
|
|
701
|
+
`/api/ontology`, so that runs started first call back there, and it registers the Workflow SDK,
|
|
702
|
+
loaded on first use, for the workflow routes. It also sets `WORKFLOW_QUEUE_NAMESPACE` to
|
|
703
|
+
`ontology` in the service's process, which serves only the ontology's queue: `resumeHook` takes
|
|
704
|
+
no namespace and enqueues the resumed run on the one this variable names. Only that entry imports the Workflow SDK: the package
|
|
705
|
+
root does not, so a process that bundles the service on another Workflow SDK major carries none of
|
|
706
|
+
it, and neither does `@segmentstream/ontology/workflows`, which the Workflow SDK bundles with
|
|
707
|
+
every workflow file.
|
|
708
|
+
|
|
709
|
+
| Route | Who | What |
|
|
710
|
+
| --------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------- |
|
|
711
|
+
| `GET /api/ontology/workflows/:workflow/triggers/:trigger` | Vercel cron, `Authorization: Bearer $CRON_SECRET` | Starts the workflow with the trigger's input; 202 `{ runId }`. |
|
|
712
|
+
| `POST /api/ontology/workflows/:workflow/start` | Holders of `workflow:<workflow>:start`; the platform | Starts it with `{ "input": … }` in wire names; 202 `{ runId }`. |
|
|
713
|
+
| `GET /api/ontology/workflows` | Any caller; each sees the workflows it may read | The declarations, each trigger with its `path` and `nextAt`. |
|
|
714
|
+
| `GET /api/ontology/workflows/:workflow/runs` | Holders of `workflow:<workflow>:read`, the platform | The Workflow SDK's runs, newest first; `?limit` and `?after`. |
|
|
715
|
+
| `GET /api/ontology/workflows/:workflow/runs/:runId` | As above | One run with its steps. |
|
|
716
|
+
|
|
717
|
+
`CRON_SECRET` is read from the service's environment; without it, the trigger routes refuse every
|
|
718
|
+
request. Workflow permissions, `workflow:<workflow>:start` and `workflow:<workflow>:read`
|
|
719
|
+
(`workflow:*:read`, `workflow:<workflow>:*`), are granted by roles like action permissions; only
|
|
720
|
+
a permission that starts with `workflow:` grants them, so `*:*` and `*:read` do not. The
|
|
721
|
+
platform authenticates with a workload token whose subject is `platform:<workspace id>`; it is
|
|
722
|
+
admitted to these routes only, and its start requests may name `startedBy`, which the run
|
|
723
|
+
receives. The deployment's runtime callers hold what the member they act for holds, and never
|
|
724
|
+
start runs. Vercel may deliver a cron invocation more than once, and `start` does not deduplicate:
|
|
725
|
+
a trigger's workflow tolerates a repeated run. A body that is not JSON answers 400.
|
|
726
|
+
|
|
727
|
+
Every route takes the workflow's TypeScript name or its wire name, `remindStaleLeads` or
|
|
728
|
+
`remind_stale_leads`, and so does the trigger segment; the start body's `input` is in wire names, as
|
|
729
|
+
an action's input is.
|
|
730
|
+
|
|
731
|
+
Pages take `?limit` (1–100, default 20) and `?after`, the previous page's `nextCursor`, which is
|
|
732
|
+
`null` on the last page. Success bodies use camelCase; errors are the service's usual
|
|
733
|
+
`{ error, detail, request_id }`. `tests/fixtures/ontology-workflow-service.json` at the repository
|
|
734
|
+
root pins every body, for the CLI and backend-next.
|
|
735
|
+
|
|
736
|
+
Run records come from the Workflow SDK's world (`runs.list`, `runs.get`, `steps.list`) with
|
|
737
|
+
the provider's status, times and safe error diagnostics. Run detail includes `deploymentId` when
|
|
738
|
+
the provider recorded it. Steps are paged newest first, up to 100 per request: `stepsNextCursor`
|
|
739
|
+
reads older steps. Attempt history follows provider event cursors within a 10,000-event budget.
|
|
740
|
+
`attemptsTruncated: true` means history or error details are incomplete or unavailable, including
|
|
741
|
+
provider/encryption failures; an empty history is not proof no attempt ran. When the world cannot
|
|
742
|
+
list a run's steps, detail retains the run with `steps: null`. Failure events resolve errors without
|
|
743
|
+
loading workflow inputs or outputs. Operator logs retain the complete redacted causes, while public
|
|
744
|
+
diagnostics contain safe codes, messages and correlation references.
|
|
745
|
+
|
|
746
|
+
Runs that ontology SDKs 0.86.0 to 0.88.0 failed with `DeploymentInterrupted` or
|
|
747
|
+
`DeploymentSwitching` stay readable, with `category` `deployment_interrupted` or
|
|
748
|
+
`deployment_switching`. Permission revocation remains terminal. Historical catalog mismatches stored
|
|
749
|
+
as `PermissionDenied` retain their original code and add `category: "deployment_interrupted"` and
|
|
750
|
+
`reason: "legacy_catalog_mismatch"`; reading them does not restart the failed run. Failure envelopes
|
|
751
|
+
from older deployments remain readable.
|
|
752
|
+
|
|
753
|
+
Durable admission journals the decoded arguments with a versioned Workflow value encoding,
|
|
754
|
+
preserving dates, bigints, undefined, special numbers, collections and supported shared references
|
|
755
|
+
and cycles. Recovery replays the original arguments, compiled workflow, routing and attributes
|
|
756
|
+
without decoding them against a newer declaration. Historical plain JSON argument rows remain
|
|
757
|
+
readable. Live streams, HTTP request/response objects, abort handles and unsupported custom values
|
|
758
|
+
are refused before commit with `workflow_arguments_unsupported`; registered Workflow serde classes
|
|
759
|
+
must round-trip deterministically with the installed runtime.
|
|
760
|
+
|
|
761
|
+
### Workflow users
|
|
762
|
+
|
|
763
|
+
Each workflow runs as its own keyless service user: name = the workflow's wire name,
|
|
764
|
+
`identity_provider` `segmentstream-ontology-workflow`, subject
|
|
765
|
+
`workflows:<workspace id>:<workflow name>`, roles = exactly the declared role, no API key. The
|
|
766
|
+
migration that applies the ontology creates it: `applyMigration` given the workspace's
|
|
767
|
+
`workspaceId` runs `reconcileWorkflowUsers` in the transaction that records the manifest, under
|
|
768
|
+
the migration lock, also on a no-op plan, and `segmentstream-ontology migrate` does the same when
|
|
769
|
+
`SEGMENTSTREAM_WORKSPACE_ID` is set. It deactivates the users of removed workflows and
|
|
770
|
+
reactivates redeclared ones. A run before the deploy creates a missing user
|
|
771
|
+
(`resolveWorkflowUser`), but a run never changes roles: the deployed database is the authority,
|
|
772
|
+
and a user whose roles are not exactly the declared role is refused (`role_mismatch`) until a
|
|
773
|
+
deploy reconciles it. Runs that are in flight on an older deployment when a deploy changes the
|
|
774
|
+
workflow's role fail fatally at their next step, since their deployment declares the old role. The
|
|
775
|
+
user never holds `member` as a role; every action re-reads its roles in
|
|
776
|
+
the action's transaction and refuses any assignment other than the declared role.
|
|
777
|
+
|
|
778
|
+
## Batch writes in actions
|
|
779
|
+
|
|
780
|
+
Stored entity handles expose `createMany(records, options?)`,
|
|
781
|
+
`updateMany([{ id, values }], options?)`, and `upsertMany({ on, records, returning? })`
|
|
782
|
+
inside authored action handlers. The action remains the permission and commit
|
|
783
|
+
boundary; a batch does not run other authored actions for its rows.
|
|
784
|
+
|
|
785
|
+
For example, an action can write sales plan lines by a declared compound unique
|
|
786
|
+
key:
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
const PlanLine = entity("planLine", {
|
|
790
|
+
id: "ent_plan_line",
|
|
791
|
+
label: "Plan line",
|
|
792
|
+
plural: "Plan lines",
|
|
793
|
+
title: "planCode",
|
|
794
|
+
description: "A sales plan decision for a period.",
|
|
795
|
+
properties: {
|
|
796
|
+
planCode: p.text({ description: "The plan code." }),
|
|
797
|
+
period: p.date({ description: "The period start." }),
|
|
798
|
+
target: p.number({ description: "The agreed target." }),
|
|
799
|
+
},
|
|
800
|
+
indexes: [{ properties: ["planCode", "period"], unique: true }],
|
|
801
|
+
}).actions((self) => ({
|
|
802
|
+
setTargets: action({
|
|
803
|
+
description: "Set targets for several plan lines together.",
|
|
804
|
+
input: Schema.Struct({
|
|
805
|
+
lines: Schema.Array(Schema.Struct(self.properties)),
|
|
806
|
+
}),
|
|
807
|
+
handler: ({ input }) =>
|
|
808
|
+
self.upsertMany({
|
|
809
|
+
on: ["planCode", "period"],
|
|
810
|
+
records: input.lines,
|
|
811
|
+
returning: "summary",
|
|
812
|
+
}),
|
|
813
|
+
}),
|
|
814
|
+
}));
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
Both result modes return `{ created, updated, unchanged }`, whose counts sum to
|
|
818
|
+
the input length. The default, `returning: "records"`, also returns `items` in
|
|
819
|
+
input order: `{ index, status, record }`, with `status` equal to `"created"`,
|
|
820
|
+
`"updated"`, or `"unchanged"`. `returning: "summary"` omits `items`; the enclosing
|
|
821
|
+
action still returns its normal `edits`. These types are exported as
|
|
822
|
+
`BatchRecordResult<Record>`, `BatchSummaryResult`, and `BatchResult<Record>`.
|
|
823
|
+
|
|
824
|
+
Creates retain normal defaults and validation. Updates patch distinct IDs:
|
|
825
|
+
omitted fields remain unchanged, and explicit `null` follows the property's
|
|
826
|
+
nullability. An unchanged patch preserves timestamps and attribution and adds no
|
|
827
|
+
history or replication work. Upsert keys must be a nonempty list of ordinary
|
|
828
|
+
properties matching a declared unique property or compound unique index. Each
|
|
829
|
+
input explicitly supplies non-null key values and enough values to create a
|
|
830
|
+
record; existing records receive only supplied non-key fields. Duplicate IDs or
|
|
831
|
+
upsert keys fail the batch.
|
|
832
|
+
|
|
833
|
+
Batch values support ordinary properties and stored `rel.one` assignments. Blob
|
|
834
|
+
and `rel.many` assignments are rejected; use the existing single-record methods
|
|
835
|
+
for those fields. Warehouse-backed and platform-owned built-in entities are not
|
|
836
|
+
batch write targets. `OntologyWrites` exposes the same operations with an
|
|
837
|
+
explicit `entity` and snake_case field names; entity handles convert authored
|
|
838
|
+
camelCase names and select values.
|
|
839
|
+
|
|
840
|
+
Each call accepts at most 200 records and 2 MiB of UTF-8 JSON input, including
|
|
841
|
+
record values, update IDs, and upsert key fields. One action may attempt at most
|
|
842
|
+
500 rows across nonempty batch calls, including failed calls its handler catches.
|
|
843
|
+
Empty input returns zero counts and, in record mode, empty `items`, without SQL.
|
|
844
|
+
Every nonempty batch uses one savepoint inside the action transaction. A failure
|
|
845
|
+
rolls back all of that batch's records, history, replication references, and
|
|
846
|
+
edits; a handler may catch it and continue. A later uncaught action failure also
|
|
847
|
+
rolls back earlier successful batches.
|
|
848
|
+
|
|
849
|
+
Existing `ActionRejected`, `Conflict`, and `NotFound` errors may include
|
|
850
|
+
`batch: { indices, fields? }`: zero-based input positions and safe wire field
|
|
851
|
+
names. Batch calls are never automatically retried. Checkpoint a loader only
|
|
852
|
+
after confirmed action success; replay after a lost response is safe only when
|
|
853
|
+
the complete action is explicitly idempotent, including its other writes and
|
|
854
|
+
side effects. Generic upsert alone does not establish that guarantee.
|
|
855
|
+
|
|
856
|
+
## Fresh metric queries in actions
|
|
857
|
+
|
|
858
|
+
Stored entity handles expose `queryMetrics({ metrics, groupBy?, where })` to
|
|
859
|
+
action handlers and `prepare`. It computes metrics the entity declares, with their
|
|
860
|
+
warehouse meaning, over the records `where` matches, in the action's transaction, so
|
|
861
|
+
it counts the handler's own writes:
|
|
862
|
+
|
|
863
|
+
```ts
|
|
864
|
+
const totalsOf = (invoice: string) =>
|
|
865
|
+
Effect.gen(function* () {
|
|
866
|
+
const [totals] = yield* InvoiceLine.queryMetrics({
|
|
867
|
+
metrics: ["net", "tax"],
|
|
868
|
+
where: { invoiceId: invoice },
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
return totals; // { net: "1501", tax: "300" }
|
|
872
|
+
});
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
Rows hold the `groupBy` fields and each metric as a decimal string or `null`;
|
|
876
|
+
without `groupBy` there is exactly one row. `where` is `find`'s query language and
|
|
877
|
+
must look records up through an index: `eq` or `in` on `id`, a unique property, a
|
|
878
|
+
`rel.one` key or the first field of a declared index. It aggregates every matching
|
|
879
|
+
record, with no row limit, so its cost follows the set the indexed condition
|
|
880
|
+
selects. Metric queries lock nothing: lock the record the decision depends on with
|
|
881
|
+
`forUpdate`. Balance
|
|
882
|
+
metrics, model metrics and workflow steps stay with the warehouse's `query_metrics`.
|
|
883
|
+
`aggregate`, the earlier name of `queryMetrics`, remains as a deprecated alias.
|
|
884
|
+
See [fresh metric queries inside an action](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology.md#fresh-metric-queries-inside-an-action).
|
|
885
|
+
|
|
886
|
+
## Permissions
|
|
887
|
+
|
|
888
|
+
`me` (also `GET /api/ontology/me`) includes `authentication: { kind, client, scope }`
|
|
889
|
+
and `attribution`. OAuth provenance carries the registered client's ID/name and the
|
|
890
|
+
grant's scope; its permissions remain restricted by that scope. Attribution uses
|
|
891
|
+
stored names through workspace-authored `chat.attribution.template` (default `{name}`), with
|
|
892
|
+
`{client}` naming the registered OAuth client (empty for runtime/service callers). Client names are
|
|
893
|
+
registration metadata, without a claim of brand ownership. Ordinary browser/app
|
|
894
|
+
members, member API keys and pipeline workloads receive null attribution and cannot use that
|
|
895
|
+
provenance to post. No bearer token or client secret appears in `me`.
|
|
896
|
+
|
|
897
|
+
Since 0.27.3, verified agent runtime calls cannot discover or execute agent
|
|
898
|
+
invocations, including the legacy `agent_chat.send_message` relay and reserved
|
|
899
|
+
`agent_start/get/update/cancel` operations. MCP, REST, discovery/catalog exports,
|
|
900
|
+
OpenAPI and attachment promotion apply the same policy. OAuth, service and ordinary
|
|
901
|
+
member callers retain their usual permission checks when runtime authentication is
|
|
902
|
+
configured. This preparation does not advertise or enable exact chat posting.
|
|
903
|
+
|
|
904
|
+
A permission is `<entity>:<verb>`. The verbs are `read`, the generated `create`, `update`,
|
|
905
|
+
`delete`, `link` and `unlink`, and the names of authored actions. A role may use `*` for the
|
|
906
|
+
entity, the verb or both: `lead:*`, `*:read`, `*:*`. An entity wildcard never covers the
|
|
907
|
+
built-in `agent_chat`, `app_feedback` or `app_feedback_file`: reading or changing them needs a
|
|
908
|
+
permission that names them, such as `agent_chat:read`.
|
|
909
|
+
|
|
910
|
+
- Reads require `<entity>:read`: lists, queries, records, history, both entities of a
|
|
911
|
+
relationship traversal, and every entity a metric query touches. `include` and a filter through a relationship require `read` on
|
|
912
|
+
the related entity. A missing permission answers 403 with `<permission> is required.`
|
|
913
|
+
- Each action requires one permission. The generated `upsert_by_<property>` requires both
|
|
914
|
+
`<entity>:create` and `<entity>:update`.
|
|
915
|
+
- A principal's permissions are the union of its roles' permissions in the ontology.
|
|
916
|
+
Members hold `member` and their assigned roles; an API key holds exactly the roles it was
|
|
917
|
+
created with; a workflow's runs hold the workflow's role and what `member` grants. A role the
|
|
918
|
+
ontology no longer declares grants nothing.
|
|
919
|
+
- The catalog, `describe`, `graph`, `openapi.json` and `me` need only authentication;
|
|
920
|
+
`status`, `sign-in/check` and the `docs` page need none. `?as=me` lists only the actions the caller may run.
|
|
921
|
+
- `check` fails on a role without permissions, on a permission whose entity or verb does not
|
|
922
|
+
exist, and on `allow`, which version 0.2 replaced. It warns on an action no role may run
|
|
923
|
+
and on `*:*`.
|
|
924
|
+
|
|
925
|
+
## Discover the ontology over MCP
|
|
926
|
+
|
|
927
|
+
`/mcp` uses stateless Streamable HTTP: POST returns JSON-RPC results, while authenticated
|
|
928
|
+
GET requests accepting `text/event-stream` receive an immediate SSE comment and 15-second
|
|
929
|
+
heartbeat comments. The GET stream carries no JSON-RPC messages or session ID. It closes
|
|
930
|
+
after 45 seconds with a reconnect hint; clients can reconnect independently to any instance.
|
|
931
|
+
Set `mcpStreamMaxDurationMs` in `createOntologyService` to change that lifetime, keeping it
|
|
932
|
+
below the deployment's function limit with room for startup and authentication. The generated
|
|
933
|
+
Vercel service enables Fluid and Nitro response streaming without overriding `maxDuration`;
|
|
934
|
+
the conservative default also fits a 60-second function limit. GET and POST share bearer
|
|
935
|
+
authentication and Origin rules. DELETE is unsupported because there are no sessions.
|
|
936
|
+
|
|
937
|
+
The tools carry the ontology's metadata: each `<entity>_find` says what its fields and their
|
|
938
|
+
values mean, its related records and its metrics; each action's tool says what it does and when
|
|
939
|
+
it is refused. The metric tools cover numbers: `list_metrics`, `get_dimensions`,
|
|
940
|
+
`get_dimension_values` and `query_metrics`, which replaces the `aggregate` tool. MCP no longer lists `describe`. `discover` is listed only by an ontology that ranks
|
|
941
|
+
entities by meaning with `defineOntology({ discovery: { model } })`. Stored schema visibility does
|
|
942
|
+
not grant permission to read records or run operations.
|
|
943
|
+
|
|
944
|
+
Optional page discovery uses the global read-only `navigation` tool. Opt into the app SDK's
|
|
945
|
+
`withPageCatalog` integration and add page metadata beside participating Next.js pages.
|
|
946
|
+
`segmentstream deploy` packages the generated catalog, and `ontologyWorkflows()` binds it
|
|
947
|
+
into the ontology build. The service uses that immutable bundled catalog automatically and
|
|
948
|
+
rejects a conflicting explicit `navigation` configuration.
|
|
949
|
+
|
|
950
|
+
For standalone services, pass `navigation: PageCatalogV1` to `createOntologyService` and
|
|
951
|
+
configure verified `appOrigins`. Omitting both catalogs preserves the existing tool surface; a catalog with
|
|
952
|
+
`pages: []` enables empty discovery. The dependency-free Node contract is exported from
|
|
953
|
+
`@segmentstream/ontology/navigation-catalog`: `createPageCatalog(pages, { ontologyHash,
|
|
954
|
+
sourceHash })` generates a deterministic revision, and `parsePageCatalog(value)` validates an
|
|
955
|
+
artifact. Entity references are also checked against the serving ontology.
|
|
956
|
+
|
|
957
|
+
Call `navigation({ entity?, query?, after?, limit?, expected_revision? })` to list or search page
|
|
958
|
+
titles and descriptions. Results contain complete metadata, a `revision`, and `next_cursor`.
|
|
959
|
+
Static pages have `url`; dynamic pages have `url_pattern` and their actual parameter shapes and
|
|
960
|
+
authored hints. Every associated entity must be visible through ordinary definition discovery.
|
|
961
|
+
The tool reads no records and grants no page or data access. Page size defaults to 20, is capped
|
|
962
|
+
at 50, and ends before the combined text and structured result exceeds 32 KiB. Cursors bind the
|
|
963
|
+
revision, filters, ordering, metadata visibility, and verified origin. Missing or invalid enabled
|
|
964
|
+
catalogs and unverified app origins fail explicitly instead of returning a misleading empty list.
|
|
965
|
+
|
|
966
|
+
Every MCP client receives the permitted operation tools directly through standard `tools/list`.
|
|
967
|
+
Large external catalogs are paginated: follow `nextCursor` by sending it as `params.cursor` until
|
|
968
|
+
it is absent. Each page contains at most 128 tools and normally fits within 500,000 UTF-8 bytes;
|
|
969
|
+
an individually larger tool gets its own page. Descriptions, input fields, annotations and trusted
|
|
970
|
+
metadata are preserved. Unicode property regex constraints (`\p{…}` and `\P{…}`) are omitted
|
|
971
|
+
from advertised input schemas because MCP clients use different regex engines. The original
|
|
972
|
+
action schema still enforces every constraint on each call. Optional output schemas are omitted.
|
|
973
|
+
|
|
974
|
+
The server no longer exposes `ontology_search_tools`, `ontology_describe_tool`, or
|
|
975
|
+
`ontology_call_tool`. Clients own tool search and call the selected original name with
|
|
976
|
+
`tools/call`. Refresh connector catalogs after upgrading, and remove `mcp.toolListing` from
|
|
977
|
+
workspace configuration. `discover`, where listed, ranks domain entities by meaning; it is not a
|
|
978
|
+
substitute for MCP tool discovery.
|
|
979
|
+
|
|
980
|
+
Each page rechecks permissions. Cursors belong to the current catalog and permission scope;
|
|
981
|
+
restart listing if either changes. Verified in-app runtimes receive the same direct operations
|
|
982
|
+
in one page because Eve 0.85 currently reads only the first `listTools` response. This preserves
|
|
983
|
+
running agents during the upgrade without exposing the retired helpers to either caller type.
|
|
984
|
+
|
|
985
|
+
`ontology://catalog` remains an explicit full manifest export; REST descriptions and CLI catalog
|
|
986
|
+
rendering retain whole-catalog behavior. Custom prompts that call `describe` should use the tools'
|
|
987
|
+
own descriptions, or request the full export.
|
|
988
|
+
|
|
989
|
+
## Serve the ontology
|
|
990
|
+
|
|
991
|
+
List and query endpoints accept `select` to avoid fetching unused base fields:
|
|
992
|
+
`GET /api/ontology/lead?select=email,status` or a query body with
|
|
993
|
+
`{ "select": ["email", "status"] }`. An explicit empty list returns ids only.
|
|
994
|
+
Selected pages include `id` and `projected: true`; omitted fields are absent.
|
|
995
|
+
`include` still adds complete related records under the same read permissions.
|
|
996
|
+
Filters, scopes and cursor ordering apply before projection and may use fields
|
|
997
|
+
not selected. Without `select`, the complete record response is unchanged.
|
|
998
|
+
Authored action reads (`Entity.find`, `get`) remain complete records.
|
|
999
|
+
|
|
1000
|
+
```ts
|
|
1001
|
+
import { configFromEnv, createOntologyService } from "@segmentstream/ontology";
|
|
1002
|
+
|
|
1003
|
+
import ontology from "./index";
|
|
1004
|
+
|
|
1005
|
+
const service = createOntologyService(ontology, configFromEnv());
|
|
1006
|
+
|
|
1007
|
+
// Give every request under /api/ontology to the service.
|
|
1008
|
+
export function handleOntologyRequest(request: Request): Promise<Response> {
|
|
1009
|
+
return service.handler(request);
|
|
1010
|
+
}
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
`GET /api/ontology/docs` serves a Scalar API reference over `openapi.json`. A browser signed
|
|
1014
|
+
in to the app authenticates with the app's Neon Auth session cookie
|
|
1015
|
+
(`__Secure-neon-auth.session_token`), which the service exchanges at Neon Auth for the
|
|
1016
|
+
session's token and verifies like a bearer token. A bearer token always takes precedence. The
|
|
1017
|
+
cookie authenticates a `POST` only when its `Origin` is the app's own.
|
|
1018
|
+
|
|
1019
|
+
`handler(request)` takes a web `Request` and returns a `Response`. `dispose()` closes the
|
|
1020
|
+
database pool. `configFromEnv()` reads these variables (`ENVIRONMENT_VARIABLES`, and the
|
|
1021
|
+
optional `NEON_AUTH_ENVIRONMENT_VARIABLES`). It throws `OntologyServiceConfigError` when
|
|
1022
|
+
one is missing or invalid, and never repeats the database URL.
|
|
1023
|
+
|
|
1024
|
+
| Variable | Purpose |
|
|
1025
|
+
| ------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
1026
|
+
| `SEGMENTSTREAM_ONTOLOGY_DATABASE_URL` | Pooled connection string of the `ontology_app` role. |
|
|
1027
|
+
| `SEGMENTSTREAM_URL` | SegmentStream origin. It issues and verifies app tokens, and answers metric queries over https. |
|
|
1028
|
+
| `SEGMENTSTREAM_APPLICATION_ID` | App tokens must be issued for this application. |
|
|
1029
|
+
| `SEGMENTSTREAM_WORKSPACE_ID` | The workspace of the app. |
|
|
1030
|
+
| `SEGMENTSTREAM_ORGANIZATION_ID` | The organization of the workspace. |
|
|
1031
|
+
| `NEON_AUTH_BASE_URL` | Optional. The workspace's Neon Auth URL; its origin issues users' tokens. |
|
|
1032
|
+
| `NEON_AUTH_JWKS_URL` | Optional, set with the base URL. The keys that verify those tokens. |
|
|
1033
|
+
| `SEGMENTSTREAM_APP_CREDENTIAL` | Optional. The workspace app credential; without it, metric queries answer 503. |
|
|
1034
|
+
| `SEGMENTSTREAM_RUNTIME_OIDC_ISSUER` | Optional. The team's Vercel OIDC issuer, for the app's agent. |
|
|
1035
|
+
| `SEGMENTSTREAM_RUNTIME_OIDC_AUDIENCE` | Optional, set with the issuer. The audience of the team's tokens. |
|
|
1036
|
+
|
|
1037
|
+
The platform sets these variables in the workspace deployment, the Neon Auth pair once the
|
|
1038
|
+
workspace has Neon Auth and the runtime pair on deployments with an agent. With the runtime
|
|
1039
|
+
pair, Vercel's `VERCEL_PROJECT_ID` must be set too.
|
|
1040
|
+
|
|
1041
|
+
### App preview deployments
|
|
1042
|
+
|
|
1043
|
+
A preview requires the emitted `workspace-app-previews-v1` runtime capability and
|
|
1044
|
+
`workspace-app-preview-workflows-v1` for workflow execution. The platform also
|
|
1045
|
+
probes the installed SDK's fixed capability profile and expiry checks on the locked dependencies
|
|
1046
|
+
before app code is exposed; preview support is never inferred from an unpublished release number.
|
|
1047
|
+
|
|
1048
|
+
`configFromEnv()` reads `SEGMENTSTREAM_DEPLOYMENT_TARGET` (production when omitted),
|
|
1049
|
+
`SEGMENTSTREAM_ONTOLOGY_DEPLOYMENT_ID` and `SEGMENTSTREAM_PREVIEW_EXPIRES_AT`. Explicit previews
|
|
1050
|
+
require a deployment UUID, a valid ISO deadline and child Neon Auth bindings. The returned
|
|
1051
|
+
`deployment` observation and `/api/ontology/status` report the deployment identity, expiry and
|
|
1052
|
+
capabilities. Preview admission uses the fixed preview profile even if a custom configuration
|
|
1053
|
+
supplies broader capability values. SDK failures use `DeploymentAdmissionError.code`; workspace
|
|
1054
|
+
code owns their presentation.
|
|
1055
|
+
|
|
1056
|
+
Preview services accept child Neon tokens and email sessions, checking the exact child issuer
|
|
1057
|
+
even when keys were copied from production. Legacy app and production workload tokens, opaque
|
|
1058
|
+
API keys, OAuth/channel sessions and Cloud federation are refused. Ontology CRUD, files and
|
|
1059
|
+
manual workflow starts retain their normal user permission checks. Action/change triggers and
|
|
1060
|
+
inbound webhooks can start workflows; `ontologyWorkflows()` compiles and mounts their queue
|
|
1061
|
+
handler. Trusted workspace code can make external network requests and cause real external
|
|
1062
|
+
side effects. Managed connections, after-commit hooks, agents (including workflow chat delivery
|
|
1063
|
+
and agent tasks), pipelines and replication remain unavailable. Scheduled business cron triggers
|
|
1064
|
+
are never registered, and direct calls to their trigger routes are refused even with a cron secret.
|
|
1065
|
+
|
|
1066
|
+
Workflow execution requires the runtime `VERCEL_DEPLOYMENT_ID` system variable. New preview runs
|
|
1067
|
+
carry immutable `segmentstream.deploymentTarget` and `segmentstream.deploymentId` attributes;
|
|
1068
|
+
run reads, steps, hook resumes and decoded queue callbacks require that exact app deployment UUID
|
|
1069
|
+
and the Workflow SDK's matching provider deployment ID. Missing or copied production provenance
|
|
1070
|
+
is refused. Production cannot read or resume preview runs. A stable alias is not run ownership.
|
|
1071
|
+
Every admission checks the deadline, including cached sessions, warm callbacks and workflow steps.
|
|
1072
|
+
Work already admitted can finish; preview expiry does not isolate or cancel arbitrary workspace code.
|
|
1073
|
+
|
|
1074
|
+
Platform workflow starts, reads and recovery use short-lived `platform:<workspace id>` workload
|
|
1075
|
+
tokens with `environment=preview`, both deployment IDs equal to the exact child UUID, the request's
|
|
1076
|
+
immutable candidate origin, and a token expiry no later than the preview deadline. These tokens
|
|
1077
|
+
grant no other ontology operations. Recovery calls `POST /api/ontology/workflows/sweep` with that
|
|
1078
|
+
token to retry persisted `_workflow_starts`; preview GET sweeps and cron-secret admission are refused.
|
|
1079
|
+
The platform must schedule this recovery separately without registering workspace business crons.
|
|
1080
|
+
|
|
1081
|
+
Each preview gets a fresh `SEGMENTSTREAM_WEBHOOK_SECRET` for unsigned URL tokens, never the
|
|
1082
|
+
production value. Signed webhooks and Meta registration retain their declared validation and use
|
|
1083
|
+
the backend's existing verification endpoints. Only those calls carry the child app credential as
|
|
1084
|
+
`x-segmentstream-preview-credential` plus `x-segmentstream-ontology-deployment-id`, runtime Vercel
|
|
1085
|
+
OIDC, organization and request headers. The backend verifies the credential's exact live deployment
|
|
1086
|
+
and the deployed secret declaration; named signing secret values are never injected into the preview.
|
|
1087
|
+
|
|
1088
|
+
Before exposing a verified child database, the provisioner runs:
|
|
1089
|
+
|
|
1090
|
+
```ts
|
|
1091
|
+
import { preparePreviewDatabase } from "@segmentstream/ontology";
|
|
1092
|
+
|
|
1093
|
+
yield * preparePreviewDatabase({ sql });
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
This transactional, repeatable preparation clears SDK-owned API keys, OAuth/federation
|
|
1097
|
+
sessions and tokens, agent pairings/identities/confirmations, workflow starts/tasks, upload
|
|
1098
|
+
sessions, file action replay results and replication delivery state. It disables copied
|
|
1099
|
+
replication while preserving users, roles, persistent member blocks, channel policy, business records, histories and retained file
|
|
1100
|
+
revisions. Older databases with fewer SDK tables are supported. It never changes `neon_auth`
|
|
1101
|
+
or any provider-owned schema: child Auth invalidation, origins, email delivery and secrets
|
|
1102
|
+
remain the provisioner's responsibility. Call it only on a verified, unexposed child, before new
|
|
1103
|
+
preview workflows can start; never repeat preparation on a serving child or recovery would lose
|
|
1104
|
+
that child's pending starts. Copied production starts and tasks must be cleared before exposure.
|
|
1105
|
+
|
|
1106
|
+
The preview `SEGMENTSTREAM_ONTOLOGY_STORAGE` binding additionally requires `deploymentId`
|
|
1107
|
+
matching this deployment and the provisioner's verified `productionBranchId`, which must differ
|
|
1108
|
+
from `branchId`. Endpoint validation still binds storage to that child branch. The provisioner
|
|
1109
|
+
must verify that DB, Auth and storage all belong to the same child and issue child-only
|
|
1110
|
+
credentials; SDK self-consistency checks cannot prove provider ownership. The runtime rebinds
|
|
1111
|
+
copied file revision metadata to that child's provider and bounds signed transfer grants by
|
|
1112
|
+
the preview deadline. Production storage bindings need neither new field.
|
|
1113
|
+
|
|
1114
|
+
### Authentication
|
|
1115
|
+
|
|
1116
|
+
Every route but `status` and `sign-in/check` takes `Authorization: Bearer …`:
|
|
1117
|
+
|
|
1118
|
+
- **Neon Auth token.** The app's users sign in with the workspace's Neon Auth (Managed
|
|
1119
|
+
Better Auth). The service verifies their JWT: EdDSA, issued by the origin of
|
|
1120
|
+
`NEON_AUTH_BASE_URL`, against the keys at `NEON_AUTH_JWKS_URL`. The token names the user
|
|
1121
|
+
(`sub`, `email`, `name`) and carries no roles. The email counts only when Neon Auth
|
|
1122
|
+
verified it.
|
|
1123
|
+
- **SegmentStream app token.** Still accepted while workspaces move to Neon Auth. The
|
|
1124
|
+
service picks the verifier by the token's issuer; a token from any other issuer is a 401.
|
|
1125
|
+
A verified app-token email also resolves an existing Neon Auth member, without replacing
|
|
1126
|
+
the member's Neon identity. The service reads that member's current roles and active state.
|
|
1127
|
+
- **API key.** `ssk_…`, held by a service user.
|
|
1128
|
+
- **Workload token.** Issued by SegmentStream for production operations and the exact preview
|
|
1129
|
+
workflow operations described above. Its subject
|
|
1130
|
+
decides who it is: `pipeline-workers:<workspace id>` is the pipeline worker;
|
|
1131
|
+
`platform:<workspace id>` is SegmentStream acting for the console and the CLI, admitted only
|
|
1132
|
+
to the [workflow routes](#runtime-and-routes) and refused everywhere else. Any other subject is
|
|
1133
|
+
a 401; a workload token never resolves as a member or the agent.
|
|
1134
|
+
|
|
1135
|
+
Sign-in finds the user linked to the token's provider and subject, else links the member
|
|
1136
|
+
invited with the same email, else follows the ontology's `signIn` policy: `invited` answers
|
|
1137
|
+
403 `{ "error": "not_invited", "detail": "Ask your administrator for an invitation." }`,
|
|
1138
|
+
`open` creates a member. Deactivated members are refused. Each sign-in sets the user's
|
|
1139
|
+
`last_sign_in_at`, at most once per 15 minutes, so the user's history records it.
|
|
1140
|
+
|
|
1141
|
+
Members without an email can be managed by their user UUID in the console and CLI:
|
|
1142
|
+
`ontology member grant|revoke|deactivate|reactivate MEMBER`. Find the UUID with
|
|
1143
|
+
`ontology member list`. The platform's `POST …/ontology/members/roles` accepts
|
|
1144
|
+
either `email` (including new invitations) or `memberId` (existing members only).
|
|
1145
|
+
The SDK's `updateMemberRoles(ontology, { id, grant, revoke })` uses the same role
|
|
1146
|
+
writer as email updates. Deactivation preserves the member's roles and identities.
|
|
1147
|
+
|
|
1148
|
+
`GET /api/ontology/me` answers who the caller is, for the app to render what they may do:
|
|
1149
|
+
`{ user: { id, name, email, kind, active }, roles, permissions, authentication, attribution }`. It needs only
|
|
1150
|
+
authentication; services get `email: null`.
|
|
1151
|
+
|
|
1152
|
+
`POST /api/ontology/sign-in/check` with `{ "email": "…" }` answers, before the app emails
|
|
1153
|
+
anyone, whether the address may sign in and how. Under `invited`, an active member who has
|
|
1154
|
+
signed in with Neon Auth gets `{ "allowed": true, "method": "code" }`, an active member who
|
|
1155
|
+
has not gets `method: "link"` (Neon Auth sends codes only to existing accounts; a sign-in
|
|
1156
|
+
link creates the account), and anyone else, deactivated members included, gets
|
|
1157
|
+
`{ "allowed": false }`. Under `open`, every address gets `link`. It reads nothing else and
|
|
1158
|
+
changes nothing. A malformed address answers 400. Each service instance allows a client
|
|
1159
|
+
address (`x-real-ip`, else the first `x-forwarded-for` hop) ten checks a minute, then
|
|
1160
|
+
answers 429 `rate_limited` with `retry-after`. `checkSignIn(ontology, email)` is the same
|
|
1161
|
+
lookup for code that holds a SQL client.
|
|
1162
|
+
|
|
1163
|
+
## App feedback
|
|
1164
|
+
|
|
1165
|
+
Text feedback is enabled when `feedback` is omitted. Every authenticated human member can
|
|
1166
|
+
submit, read their own items and acknowledge answers, including members with no assigned roles.
|
|
1167
|
+
It works through the app's REST API or the customer's own OAuth MCP client; an in-app agent,
|
|
1168
|
+
pairing and Telegram are optional. Upgrade the ontology SDK and deploy to apply these defaults;
|
|
1169
|
+
running deployments and historical manifests do not change retroactively.
|
|
1170
|
+
|
|
1171
|
+
The reserved built-in `feedback_reporter` role supplies this authority during human principal
|
|
1172
|
+
resolution. It is never stored or assignable, cannot be declared as a workspace role, and cannot
|
|
1173
|
+
be used by API keys, services, workflows, hooks or `feedback.builders`. The workspace's `member`
|
|
1174
|
+
permissions remain unchanged. OAuth `ontology:read` consent still removes writes.
|
|
1175
|
+
|
|
1176
|
+
Files follow a conservative policy resolved without network calls:
|
|
1177
|
+
|
|
1178
|
+
| Declaration | Text | Files |
|
|
1179
|
+
| ------------------ | -------- | --------------------------------------------------------- |
|
|
1180
|
+
| omitted or `{}` | enabled | only if another non-feedback entity has a blob |
|
|
1181
|
+
| `true` | enabled | enabled, preserving the original explicit opt-in contract |
|
|
1182
|
+
| `false` | disabled | disabled |
|
|
1183
|
+
| `{ files: false }` | enabled | disabled, even if another entity has a blob |
|
|
1184
|
+
| `{ files: true }` | enabled | explicitly enabled |
|
|
1185
|
+
|
|
1186
|
+
Text-only feedback adds `app_feedback` with submit, respond and acknowledge. When files resolve
|
|
1187
|
+
enabled, `app_feedback_file` adds a 25 MiB blob and attach action, and `app_feedback.files` is
|
|
1188
|
+
available as an inverse relationship. File storage still requires the platform's entitlement,
|
|
1189
|
+
region and deployment verification; declaring feedback never bypasses those refusals.
|
|
1190
|
+
`check` and `describe` report the resolved enabled/files/builders settings. Disabling feedback or
|
|
1191
|
+
files follows normal migration retention and drop protections; it never silently deletes records.
|
|
1192
|
+
|
|
1193
|
+
Builders default to declared roles explicitly granting `user:grantRoles` (or `user:grant_roles`)
|
|
1194
|
+
or `user:*`. `*:*` does not select a builder. Selected roles gain unscoped `app_feedback:read`,
|
|
1195
|
+
`app_feedback:respond`, and, only when files exist, `app_feedback_file:read`. Override the selection
|
|
1196
|
+
with declared roles or normalized role names:
|
|
1197
|
+
|
|
1198
|
+
```ts
|
|
1199
|
+
export default defineOntology({
|
|
1200
|
+
entities: [/* business entities */],
|
|
1201
|
+
roles: [admin, support],
|
|
1202
|
+
feedback: { builders: [admin, "support"], files: false },
|
|
1203
|
+
});
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
`builders: []` injects no builder grants. Authored responder grants still count. With no effective
|
|
1207
|
+
role able to read and answer, `check` emits `feedback-unanswerable` as a warning; configure
|
|
1208
|
+
`feedback: { builders: [...] }` to choose the responders. The starter has no qualifying member
|
|
1209
|
+
administrator and intentionally warns until its workspace chooses a builder; it invents no admin role.
|
|
1210
|
+
|
|
1211
|
+
Neither ordinary nor pipeline workload `*:*` grants feedback. Deliberately authored named service
|
|
1212
|
+
permissions keep their ordinary meaning, including their scopes. In particular, workflow `me`
|
|
1213
|
+
is the service user's ID; a named grant scoped to `reporter: me` can match its own records.
|
|
1214
|
+
There is no automatic reporter authority in workflows, pipeline workers or admission hooks.
|
|
1215
|
+
|
|
1216
|
+
The MCP server preserves submit in compact listing and adds feedback guidance only when submit
|
|
1217
|
+
is visible. Attachment guidance appears only when attach is visible. The caller offers once per
|
|
1218
|
+
problem, asks approval for the title and summary, then later reports and acknowledges the answer.
|
|
1219
|
+
Builders use `segmentstream ontology feedback list`, `show` and `respond` as their app member.
|
|
1220
|
+
|
|
1221
|
+
## Agents
|
|
1222
|
+
|
|
1223
|
+
The app's own agent runs in the same Vercel project as the service and calls it as the
|
|
1224
|
+
deployment: `Authorization: Bearer <Vercel OIDC token>`. The service accepts the token when it
|
|
1225
|
+
is RS256 from `SEGMENTSTREAM_RUNTIME_OIDC_ISSUER` (keys at `<issuer>/.well-known/jwks`), for
|
|
1226
|
+
`SEGMENTSTREAM_RUNTIME_OIDC_AUDIENCE`, with `project_id` equal to `VERCEL_PROJECT_ID` and
|
|
1227
|
+
`environment` `production`. Without the issuer and audience such tokens are a 401.
|
|
1228
|
+
|
|
1229
|
+
The agent acts for a member by adding `X-SegmentStream-Act-As: <user id>` on any route,
|
|
1230
|
+
`/mcp` included: the request runs with the member's current roles and history names the
|
|
1231
|
+
member, as if they had called. A member who is inactive or has no live pairing is a 403.
|
|
1232
|
+
|
|
1233
|
+
Running a task a workflow handed it (see [Agent tasks](#agent-tasks)), the agent instead presents
|
|
1234
|
+
the task's secret in `X-SegmentStream-Agent-Task: <secret>`, on `/mcp` and the two task routes
|
|
1235
|
+
only. While the task is live, neither ended nor past its deadline, the request runs as the
|
|
1236
|
+
workflow's user, as the workflow's steps do, with the caller `{ kind: "runtime" }` and
|
|
1237
|
+
`authenticatedVia` `agent_task`: its tool list, permission checks and history follow that user.
|
|
1238
|
+
Both headers on one request, a secret that names no live task, an inactive workflow user, and the
|
|
1239
|
+
task header on any other route are a 403.
|
|
1240
|
+
|
|
1241
|
+
| Route | Does |
|
|
1242
|
+
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1243
|
+
| `GET /api/ontology/agent/tasks/current` | `{ id, workflow, prompt, deadline_at, principal: { id, name, roles } }`; `roles` includes `member` |
|
|
1244
|
+
| `POST /api/ontology/agent/tasks/current/result` | `{ status: "completed" \| "needs_input", text, truncated }` or `{ status: "failed", reason: "agent_failed" }` → `{ accepted: true }`; 409 when the task already ended |
|
|
1245
|
+
|
|
1246
|
+
Without either header the agent may call only the agent routes:
|
|
1247
|
+
|
|
1248
|
+
| Route | Caller | Does |
|
|
1249
|
+
| ------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
1250
|
+
| `POST /api/ontology/agent/pairings` | agent | `{ provider, external_id, display? }` → `{ code, expires_at }` |
|
|
1251
|
+
| `GET /api/ontology/agent/pairings/{code}` | member | `{ provider, display, expires_at }`, for the connect page |
|
|
1252
|
+
| `POST /api/ontology/agent/pairings/{code}/approve` | member | Pairs the account with the member → `{ identity }` |
|
|
1253
|
+
| `POST /api/ontology/agent/identities/resolve` | agent | Admission and turn limits; see below |
|
|
1254
|
+
| `POST /api/ontology/agent/identities/notice` | agent | `{ provider, external_id, sha256, version? }` acknowledges notice delivery: 204 |
|
|
1255
|
+
| `GET /api/ontology/agent/identities/{provider}/{external_id}` | agent | `{ user: { id, name }, roles }`; 404 unpaired, 403 revoked, inactive |
|
|
1256
|
+
| `GET /api/ontology/agent/identities` | member | `{ identities }`: the member's live identities |
|
|
1257
|
+
| `DELETE /api/ontology/agent/identities/{id}` | member | Revokes one of them: 204 |
|
|
1258
|
+
| `POST /api/ontology/agent/chats/seen` | agent | `{ provider, externalId, title, memberId?, leftMemberId?, agentLeft?, migratedFrom? }` → `{ id, title, purpose, whenToUse }` |
|
|
1259
|
+
|
|
1260
|
+
A pairing code is eight characters without look-alikes (no 0, O, 1, I or L), stored as its
|
|
1261
|
+
SHA-256, valid ten minutes and used once. Each instance allows a client address 60 new
|
|
1262
|
+
pairings a minute. An account and a member each have one live identity per provider:
|
|
1263
|
+
approving a code replaces the member's own previous account on the same provider. An account
|
|
1264
|
+
already live on another member returns 409 `identity_in_use` without consuming the code;
|
|
1265
|
+
re-approving the same member/account returns the existing identity. To release a self-registered
|
|
1266
|
+
account for pairing to an email member, an administrator can hard-delete the channel-only member;
|
|
1267
|
+
its identities are deleted with it. This escape hatch discards that member record. Deactivation
|
|
1268
|
+
keeps the identity bound and cannot resolve a 409. Member routes take a Neon Auth session or token and refuse API keys and the
|
|
1269
|
+
agent, even acting as a member, so only members pair. Pairings and identities live in the
|
|
1270
|
+
runtime tables `_agent_pairings` and `_agent_identities`; the migration creates them in a
|
|
1271
|
+
database an earlier SDK migrated. Additive migrations also create `_agent_counters` and identity
|
|
1272
|
+
`origin`, `noticed_at`, `notice_sha256` metadata without changing existing bindings.
|
|
1273
|
+
|
|
1274
|
+
`POST …/identities/resolve` accepts `{ provider, external_id, chat: "private" | "group", turn,
|
|
1275
|
+
display?, username?, notice_version?, notice_sha256?, limits? }` with a verified runtime token.
|
|
1276
|
+
It returns 200 with `status`:
|
|
1277
|
+
|
|
1278
|
+
- `created` or `existing`: `{ user: { id, name }, roles, provisioned, notice_due, on_new_user_pending }`.
|
|
1279
|
+
The optional onboarding hook has already been attempted before this response; pending means retry.
|
|
1280
|
+
- `pairing_required`: unknown account on an invited channel, in a group, or with `turn: false`.
|
|
1281
|
+
- `refused`: `{ reason: "blocked" | "inactive" }`; revoked bindings also return `blocked` and never re-create.
|
|
1282
|
+
- `limited`: `{ scope, retry_after_seconds, notify, notification_id? }`, before expensive agent processing.
|
|
1283
|
+
|
|
1284
|
+
`limits` has the v2 agent shape:
|
|
1285
|
+
`{ perSender: { turnsPerMinute: 6, turnsPerDay: 80 }, newUsersPerHour: 30, workspaceTurnsPerDay: 3000 }`.
|
|
1286
|
+
These values are platform defaults **and ceilings**; trusted overrides can only lower them. Scopes are
|
|
1287
|
+
`turnsPerMinute`, `turnsPerDay`, `newUsersPerHour`, `workspaceTurnsPerDay`. The latest submitted effective
|
|
1288
|
+
limits persist in `_channel_limits`; omitted limits reuse them, or defaults before the first submission.
|
|
1289
|
+
Only trusted agent configuration may submit limits; browser assertions must never supply them.
|
|
1290
|
+
Counters are durable in `_agent_counters`. Sender counters use user ids, so adding/replacing an identity
|
|
1291
|
+
cannot reset them. Turn caps apply only to self-registered users with exactly `["member"]`; paired-only
|
|
1292
|
+
members and holders of any named role are exempt. Every new registration consumes the hourly cap.
|
|
1293
|
+
`turn: false` never creates or counts; groups never create. Capped registration writes roll back, with a
|
|
1294
|
+
separate durable refusal counter. Each cap notifies on its first rejection per effective threshold
|
|
1295
|
+
within the fixed window, including after configured limits change. A `notify` response leases
|
|
1296
|
+
`notification_id` for 30 seconds. After sending app-owned limit text, POST
|
|
1297
|
+
`{ notification_id, delivered: true }` to `…/identities/limit-notice`; after a failed send, complete it
|
|
1298
|
+
with `delivered: false` so delivery can retry. A lost process's lease expires automatically. Only the
|
|
1299
|
+
current claim can complete; successful delivery suppresses later notifications for that threshold
|
|
1300
|
+
and window. No notification acknowledgement is needed when no text was sent.
|
|
1301
|
+
The agent supplies app-owned policy from `firstContact` and `limits`; there is no platform default
|
|
1302
|
+
notice. When no notice is configured, send and acknowledge nothing. Notice delivery is at least once:
|
|
1303
|
+
when `notice_due`, send before processing,
|
|
1304
|
+
then POST `{provider, external_id, version, sha256}`. Resolve carries the selected version and content
|
|
1305
|
+
hash; a changed version/hash requests delivery again. Unversioned acknowledgements persist the
|
|
1306
|
+
delivered hash too, so hash-only renewal stops requesting delivery after acknowledgement. Delivery
|
|
1307
|
+
is not consent. GET lookup remains compatible with old agents.
|
|
1308
|
+
|
|
1309
|
+
Server adapters share `resolveChannelIdentity(ontology, sql, input)` (camelCase `externalId`,
|
|
1310
|
+
`noticeVersion`, `noticeSha256`) and `sqlAgentStore(ontology, sql).resolveIdentity(input)`.
|
|
1311
|
+
Admission must run outside any SQL transaction (enforced), so the user commits before onboarding.
|
|
1312
|
+
A verified Mini App exchange calls `resolveChannelSignIn(ontology, sql, {provider, externalId, display?,
|
|
1313
|
+
username?})` before issuing a session. This separate verified sign-in context has no bot-turn fields or
|
|
1314
|
+
limit overrides: it consumes the same registration/sender/workspace caps and runs the same onboarding.
|
|
1315
|
+
Agent lookup-only events still use `turn: false` and never create. Browser routes must derive provider
|
|
1316
|
+
facts from a verified assertion, never from untrusted identity fields. Session issuance must lock the
|
|
1317
|
+
User row (`FOR SHARE`) and re-check active/unblocked membership in its transaction against concurrent
|
|
1318
|
+
block/deactivation. Do not expose trusted runtime inputs in a browser route. `runNewUserHook(ontology, sql,
|
|
1319
|
+
principal)` is the same lifecycle step, returning a boolean (`true` = pending). It always strips named
|
|
1320
|
+
roles and workload authority and atomically skips completed or non-self-registered users.
|
|
1321
|
+
|
|
1322
|
+
`setMemberBlocked(ontology, {id, blocked})` is the SDK member-management operation for A3. Runtime-only
|
|
1323
|
+
`POST /api/ontology/agent/members/:id/block` and `/unblock` return 204, or 404 `not_found` for unknown
|
|
1324
|
+
members. Member summaries retain legacy statuses, exposing blocked members as inactive with `blocked: true`.
|
|
1325
|
+
Block and deactivation revoke all existing OAuth grants and remove OIDC sessions in the same transaction; `revokeMemberSessions(sql,
|
|
1326
|
+
userId)` is B's session revocation integration point. OAuth and OIDC session issuers lock the same
|
|
1327
|
+
User row and recheck active/unblocked membership inside the insertion transaction, so issuance
|
|
1328
|
+
cannot escape concurrent revocation. Unblock never restores revoked sessions. Every
|
|
1329
|
+
session authentication must re-check the active, unblocked member. A blocked account cannot be paired
|
|
1330
|
+
elsewhere, even after unpairing; pairing conflicts remain 409 `identity_in_use`.
|
|
1331
|
+
|
|
1332
|
+
### Channel app sessions
|
|
1333
|
+
|
|
1334
|
+
Explicit service opt-in `channels: { telegram: { connection: "chat_bot", miniApp: {} } }` mounts
|
|
1335
|
+
`POST /api/ontology/sign-in/:provider`, `GET /api/ontology/session` and
|
|
1336
|
+
`POST /api/ontology/sign-in/logout`. A chat connection alone registers no exchange. Providers remain
|
|
1337
|
+
invited by default: existing live identities sign in; new accounts require an explicit open channel.
|
|
1338
|
+
App SDK framing/sign-in also needs its own `channels.telegram: true` opt-in.
|
|
1339
|
+
|
|
1340
|
+
The exchange accepts `{assertion: "<raw initData>"}`. Telegram's `ChannelAppProvider` verifies Ed25519
|
|
1341
|
+
against the [pinned production key](https://core.telegram.org/bots/webapps#validating-data-for-third-party-use),
|
|
1342
|
+
fetches/caches getMe through the configured connection, rejects duplicates and assertions older than
|
|
1343
|
+
one hour or over 30 seconds ahead, and never logs assertions. An explicit test deployment may set
|
|
1344
|
+
`channels.telegram.miniApp.testPublicKeyHex`; production needs no key setting. Additional adapters
|
|
1345
|
+
implement `ChannelAppProvider.verify(assertion, ctx)` and register through `channelAppProviders`.
|
|
1346
|
+
|
|
1347
|
+
Admission/onboarding uses `resolveChannelSignIn` outside the issuer transaction with the service's
|
|
1348
|
+
shared action-admission gate. Issuance locks User FOR SHARE before the exact live identity and
|
|
1349
|
+
rechecks active/unblocked membership and ownership. The provider-derived first-party client issues
|
|
1350
|
+
only a one-hour `sso_` access token for the app-session resource, isolated from MCP. Discovery returns
|
|
1351
|
+
`session.{id,expires_at}` and `user.id`; exchange returns `{signed_in:true,expires_at}`. Browser input
|
|
1352
|
+
cannot override policy or limits. Refusals return codes (`not_paired`, `access_refused`,
|
|
1353
|
+
`invalid_assertion`, `rate_limited`), never end-user text. Public exchange has an IP budget.
|
|
1354
|
+
|
|
1355
|
+
Cookie/protocol constants live in the browser-safe `@segmentstream/ontology/channel-session`
|
|
1356
|
+
export shared with app SDK. The `__Host-` cookie is HttpOnly, Secure, SameSite=None, Partitioned and
|
|
1357
|
+
Path=/ with Max-Age=3600; deletion matches those attributes. Exchange/logout and cookie writes enforce
|
|
1358
|
+
the existing Origin rule. A valid channel cookie wins over OIDC/Neon; stale channel cookies fall
|
|
1359
|
+
through. Renewal/unpair/displacement revoke only that identity's grants in the same transaction;
|
|
1360
|
+
block/deactivation use A1's all-member revocation and cannot revive tokens after restoration.
|
|
1361
|
+
|
|
1362
|
+
Handlers receive read-only `principal.identities`: live `{ provider, subject, username?, verified: true }`
|
|
1363
|
+
facts and `createdVia` (`invite`, `email`, identity provider, or `service`) plus `authenticatedVia` for the request.
|
|
1364
|
+
Legacy invitations are recognized from their creation history; synthetic principals with no stored user report `unknown`.
|
|
1365
|
+
No credentials or trust levels are exposed. Runtime action requests may set
|
|
1366
|
+
`X-SegmentStream-Identity-Provider` alongside `X-SegmentStream-Act-As`; a matching live binding is required.
|
|
1367
|
+
Older runtimes without that header report `agent`; other verified mechanisms report their provider, `oauth`,
|
|
1368
|
+
`oidc`, `api_key`, or `workload`. Direct unauthenticated `runAction` calls report `unknown`.
|
|
1369
|
+
These facts belong to the handler context; `prepare` retains the ordinary principal contract.
|
|
1370
|
+
|
|
1371
|
+
The built-in entity `agent_chat` keeps the group chats the agent is in. The agent reports each
|
|
1372
|
+
message it sees there to `chats/seen`, which creates the chat or keeps its title current,
|
|
1373
|
+
records `lastMessageAt` at most every 15 minutes, links a paired, active writer to
|
|
1374
|
+
`members` and unlinks `leftMemberId`, a paired member who left, when linked, or every member
|
|
1375
|
+
with `agentLeft: true`, when the agent itself left (the chat stays). With `migratedFrom`, the
|
|
1376
|
+
old id of a group that became a supergroup, it moves the old record to `externalId`, or, when
|
|
1377
|
+
both are recorded, moves the members and a missing `purpose` and `whenToUse` to the new one and
|
|
1378
|
+
deletes the old one. `provider`, `externalId`, `title` and `lastMessageAt` are
|
|
1379
|
+
managed. Members set `purpose` and `whenToUse` with the generated
|
|
1380
|
+
`agent_chat.update`, the chat's only generated action. Chat read/update permissions remain
|
|
1381
|
+
`agent_chat:read` and `agent_chat:update`, granted explicitly; wildcard entity grants do not
|
|
1382
|
+
cover this built-in registry.
|
|
1383
|
+
|
|
1384
|
+
Since 0.28.2, `chat_post_message` posts exact text directly through the ontology service's
|
|
1385
|
+
`ChatDelivery`. It does not start an agent turn. Configure a declared provider connection:
|
|
1386
|
+
|
|
1387
|
+
```ts
|
|
1388
|
+
const telegram = connection("telegram", { description: "Workspace Telegram bot." });
|
|
1389
|
+
defineOntology({
|
|
1390
|
+
entities,
|
|
1391
|
+
roles,
|
|
1392
|
+
connections: [telegram],
|
|
1393
|
+
chat: { telegram: { connection: telegram } },
|
|
1394
|
+
});
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
Grant `agent_chat:post_message` explicitly (`agent_chat:*` also covers it). The former
|
|
1398
|
+
`agent_chat:send_message` grant does not grant posting. The old send action, instruction relay,
|
|
1399
|
+
`AgentChannel`/`AgentMessage` exports and agent-origin configuration have been removed.
|
|
1400
|
+
Older ontology definitions still expose reads; posting requires the provider mapping.
|
|
1401
|
+
|
|
1402
|
+
`chat_list({ query?, chat?, after?, limit? })` returns eligible routing metadata and server-derived
|
|
1403
|
+
attribution. `query` filters titles by a case-insensitive literal substring before pagination.
|
|
1404
|
+
`chat` selects an exact ID/title. Choose an ID or exact title; ambiguous titles return candidates and never guess.
|
|
1405
|
+
Paired private chats have stable handles `telegram:private:<member UUID>`. Posting permission
|
|
1406
|
+
allows target discovery independently of history. `user_find` still requires `user:read`.
|
|
1407
|
+
|
|
1408
|
+
External OAuth/service clients call `chat_post_message({ chat, text, reply_to?, idempotency_key })`.
|
|
1409
|
+
Attribution comes from verified authentication and stored names. The workspace can set
|
|
1410
|
+
`chat.attribution: { template, separator }`; defaults are `{name}` and `\n\n`.
|
|
1411
|
+
The delivered plain text is `text + separator + attribution`, preserving Unicode, whitespace and
|
|
1412
|
+
newlines. Ordinary member/workload credentials cannot attribute a post. Oversize final text is
|
|
1413
|
+
refused, never truncated or split. Telegram Markdown is rendered independently from the escaped
|
|
1414
|
+
attribution; only a definite formatting refusal allows one plain-text retry. Unknown delivery
|
|
1415
|
+
outcomes are never retried. Telegram replies require the named message to exist.
|
|
1416
|
+
|
|
1417
|
+
A durable archive reservation authorizes exactly one provider attempt. Reuse the same key on
|
|
1418
|
+
replay; changed content/destination conflicts. An uncertain operation is never automatically
|
|
1419
|
+
resent. The flat MCP receipt is `{ status: "sent" | "uncertain", message_id, url, duplicate,
|
|
1420
|
+
history_recorded }`; private chats may have no URL. A receipt-write failure leaves
|
|
1421
|
+
`history_recorded: false` without sending again. An older archive lacking the claim protocol
|
|
1422
|
+
cannot authorize delivery. Local provider proxies alone cannot replace the production archive.
|
|
1423
|
+
|
|
1424
|
+
`chat_history({ chat, before?, limit? })` and `chat_search` read the archive with independent
|
|
1425
|
+
conversation authorization: a member reads the group chats they are in (the chat's `members`,
|
|
1426
|
+
like its attachments), `agent_chat:read` reads every group, and a private chat is read by its
|
|
1427
|
+
participant. A posting service cannot read member conversations. The canonical
|
|
1428
|
+
REST facade is `/api/ontology/chat/{list,history,search,actions/post_message}`; the post action
|
|
1429
|
+
uses the standard `{ input, options?: { validate_only? } }` envelope and action response.
|
|
1430
|
+
Validation checks eligibility and exact text without reserving or sending.
|
|
1431
|
+
|
|
1432
|
+
`chat_get` and `chat_update` use the registry implementations. Runtime calls use the agent SDK
|
|
1433
|
+
0.6.3 application-owned
|
|
1434
|
+
`_segmentstream` protocol; the model sees only `{ chat, text, reply_to? }`. Authenticated tools/list
|
|
1435
|
+
advertises `segmentstream/chat-post: 1` only with a configured provider. Deploy the updated agent
|
|
1436
|
+
first, then the archive migration/backend, then enable ontology delivery. HQ integration is
|
|
1437
|
+
maintained in its own repository.
|
|
1438
|
+
|
|
1439
|
+
### Member identity inspection
|
|
1440
|
+
|
|
1441
|
+
Platform member summaries include optional live `identities` with `provider`, `subject` and
|
|
1442
|
+
`username` metadata, and a `blocked` flag. Revoked bindings are omitted; identity subjects are
|
|
1443
|
+
stable and usernames are mutable display metadata. Older databases without channel tables
|
|
1444
|
+
remain readable. Use `segmentstream ontology member list --via PROVIDER` to find a member UUID.
|
|
1445
|
+
|
|
1446
|
+
### Agent attachments
|
|
1447
|
+
|
|
1448
|
+
Attachments remain separate member-authorized capabilities. Chat posting accepts text only;
|
|
1449
|
+
it has no document relay fields.
|
|
1450
|
+
|
|
1451
|
+
`GET /api/ontology/agent/files/:entity/:id[/:property]` returns `{ file, download }` for the
|
|
1452
|
+
current file property of an ontology record. Like temporary attachment routes, it requires a
|
|
1453
|
+
production runtime bearer token, `X-SegmentStream-Act-As` naming an active paired member,
|
|
1454
|
+
and the agent conversation/session headers. It checks `<entity>:read` before returning metadata
|
|
1455
|
+
and rechecks live permissions when granting a download pinned to that revision. The response is
|
|
1456
|
+
uncached; the download grant is short lived. Without a property the entity must have exactly one
|
|
1457
|
+
file property. Missing records/files return `not_found`, missing read access `forbidden`, and
|
|
1458
|
+
non-file or ambiguous properties `unsupported`.
|
|
1459
|
+
|
|
1460
|
+
Outbound Telegram uploads currently have an effective **2 MiB multipart request limit** in the
|
|
1461
|
+
connection proxy, including overhead, even though Telegram accepts larger files. The agent splits
|
|
1462
|
+
compatible albums to fit and returns per-file errors. Group purpose and document restrictions
|
|
1463
|
+
still apply; a member's read access alone does not authorize sharing a restricted document in a group.
|
|
1464
|
+
|
|
1465
|
+
A file a member sends the agent is a temporary attachment: platform metadata in the runtime
|
|
1466
|
+
table `_agent_attachments`, never an ontology record, with its bytes stored once as a revision in
|
|
1467
|
+
the ontology's file storage, beside uploads. It belongs to the member, the chat it arrived in and
|
|
1468
|
+
the agent session that first reads it, holds its SHA-256, size, type and name, and expires 24
|
|
1469
|
+
hours after receipt (`received` → `ready` → `promoted`, `discarded` or `expired`). Files are up
|
|
1470
|
+
to 20 MB, what Telegram lets a bot download; a member holds at most 50 live attachments. The
|
|
1471
|
+
table exists on deployments with file storage; the migration adds it beside the file tables, also
|
|
1472
|
+
to a database an earlier SDK migrated.
|
|
1473
|
+
|
|
1474
|
+
Only the agent calls these routes, with its runtime token, `X-SegmentStream-Act-As`, the chat in
|
|
1475
|
+
`X-SegmentStream-Agent-Conversation` (`telegram:<chat id>`) and, inside a turn, the session in
|
|
1476
|
+
`X-SegmentStream-Agent-Session`. Another member's attachment, another chat's or another
|
|
1477
|
+
session's answers 404; an expired or discarded one answers 409; a deactivated member's 403.
|
|
1478
|
+
|
|
1479
|
+
| Route | Does |
|
|
1480
|
+
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
1481
|
+
| `POST /api/ontology/agent/attachments` | `{ source, source_ref, filename, content_type, size, sha256 }` → `{ attachment, upload? }`, a PUT grant |
|
|
1482
|
+
| `POST /api/ontology/agent/attachments/{id}/complete` | Verifies the stored bytes' size and SHA-256 → `{ attachment }` in `ready` |
|
|
1483
|
+
| `GET /api/ontology/agent/attachments` | `{ attachments }`: the caller's readable attachments in this chat and session |
|
|
1484
|
+
| `GET /api/ontology/agent/attachments/{id}` | `{ attachment, download }`: a one-minute GET grant |
|
|
1485
|
+
| `POST /api/ontology/agent/attachments/{id}/promote` | `{ entity, action, input?, field?, record_id? }` → `{ attachment, deduplicated, result }` |
|
|
1486
|
+
| `POST /api/ontology/agent/attachments/{id}/discard` | Revokes it at once → `{ attachment }` |
|
|
1487
|
+
|
|
1488
|
+
Promotion keeps a file by running an action that takes an upload, such as an authored
|
|
1489
|
+
`document.upload` with `uploads.for(Document, "content")`, as the member and with their
|
|
1490
|
+
permissions. The service mints a completed upload ticket on the attachment's existing revision,
|
|
1491
|
+
fills the action's upload field (and `sha256`, when the input has one and the caller left it
|
|
1492
|
+
out), and runs the action with an idempotency key derived from the attachment, action and
|
|
1493
|
+
input, and the ticket's create intent; the action consumes the ticket as it would a browser
|
|
1494
|
+
upload, without copying the bytes. The target property's `accept` and `maxBytes` apply. A file
|
|
1495
|
+
whose SHA-256 an earlier attachment already promoted through the same action answers that
|
|
1496
|
+
promotion with `deduplicated: true` instead of a second record. Storage maintenance expires
|
|
1497
|
+
attachments past their deadline and removes the files no record adopted.
|
|
1498
|
+
|
|
1499
|
+
An MCP action tool whose input takes an upload also accepts `_options: { idempotency_key,
|
|
1500
|
+
create_intent }`, the options the REST route takes beside `input`. An MCP client stores a file
|
|
1501
|
+
in three calls and sends the bytes over HTTP, never in a tool argument. `files_upload` takes
|
|
1502
|
+
`entity`, `action`, `filename`, `content_type` and the exact `size`, plus `property` when the
|
|
1503
|
+
action takes several uploads of the entity, `record_id` when it changes an existing record and
|
|
1504
|
+
`idempotency_key` to resume an attempt. It returns `upload`, a short-lived `transfer` grant and
|
|
1505
|
+
`options`. The client sends the bytes to `transfer.url` with `transfer.method` and exactly
|
|
1506
|
+
`transfer.headers`, calls `files_upload_complete` with `upload.id` for `upload.ticket`, then
|
|
1507
|
+
calls the action tool with the ticket in its upload field and `_options` set to `options`. Both
|
|
1508
|
+
tools run the REST upload sessions above, with the same checks, limits and permissions. A
|
|
1509
|
+
failure of the storage provider answers `storage_unavailable`, and a request made while a
|
|
1510
|
+
deployment migrates the catalog answers `ontology_updating`; both are 503s with
|
|
1511
|
+
`retryable: true`, so the client repeats the same call, ideally with its own `idempotency_key`.
|
|
1512
|
+
While a failed attempt still holds an upload, a retry answers 409 `conflict` with
|
|
1513
|
+
`retryable: true` and `retry_after` seconds; a conflict without `retryable` cannot resume.
|
|
1514
|
+
|
|
1515
|
+
A caller holds at most eight open uploads. `GET /api/ontology/uploads` and the MCP tool
|
|
1516
|
+
`files_upload_list` discover the caller's sessions occupying those slots, with `pending`,
|
|
1517
|
+
`max`, IDs, filenames, target actions, expiry and `awaiting_action`. The list is bounded by
|
|
1518
|
+
the eight-slot limit and excludes expired sessions and earlier branches or manifests.
|
|
1519
|
+
It never lists another principal's sessions or returns storage credentials. `ready` means
|
|
1520
|
+
the transfer finished but a saving action is still required. `files_upload_get` (or
|
|
1521
|
+
`GET /api/ontology/uploads/:id`) reads that session and its ready ticket. Keep the original
|
|
1522
|
+
action input and idempotency options for retry; listing does not recreate them.
|
|
1523
|
+
|
|
1524
|
+
An action that commits settles each ticket it was
|
|
1525
|
+
given: one it stored is consumed, and one it did not keep, for example because it found the same
|
|
1526
|
+
file already stored, is released at once and storage maintenance removes its bytes. A
|
|
1527
|
+
rejected action leaves the ticket ready for a retry, and a retry of the committed action replays
|
|
1528
|
+
its saved outcome. `files_upload_cancel` with `id` cancels an open upload no action will take.
|
|
1529
|
+
When the limit refuses a new upload, its `pending_uploads` error lists the open `uploads` (`id`,
|
|
1530
|
+
`filename`, `expires_at`), so the client stores or cancels them; an untaken ticket otherwise
|
|
1531
|
+
expires after 24 hours.
|
|
1532
|
+
|
|
1533
|
+
## The `segmentstream-ontology` binary
|
|
1534
|
+
|
|
1535
|
+
Run the binary in the `ontology/` folder. The entry defaults to `./index.ts`. The binary
|
|
1536
|
+
loads it with tsx 4.21.0, as the deploy pipeline does.
|
|
1537
|
+
|
|
1538
|
+
```sh
|
|
1539
|
+
npx segmentstream-ontology check [entry] # diagnostics and a catalog summary
|
|
1540
|
+
npx segmentstream-ontology check [entry] --manifest # the manifest JSON
|
|
1541
|
+
npx segmentstream-ontology check [entry] --catalog # the catalog as prose
|
|
1542
|
+
npx segmentstream-ontology migrate [entry] --plan # the migration plan and its SQL
|
|
1543
|
+
npx segmentstream-ontology migrate [entry] # plan and apply the migration
|
|
1544
|
+
```
|
|
1545
|
+
|
|
1546
|
+
- `check` prints errors and warnings on stderr with their paths. It fails on any error.
|
|
1547
|
+
- `migrate` checks the ontology, reads the catalog recorded in the database, plans the
|
|
1548
|
+
migration and applies it with the planner and applier the deploy pipeline uses. Use
|
|
1549
|
+
the owner connection string (`ontology_owner`, direct connection) of a Neon development
|
|
1550
|
+
branch. Set `SEGMENTSTREAM_ONTOLOGY_MIGRATE_URL`, or pass `--database-url <url>`. The
|
|
1551
|
+
binary does not read `SEGMENTSTREAM_ONTOLOGY_DATABASE_URL` and never prints the
|
|
1552
|
+
connection string.
|
|
1553
|
+
With `SEGMENTSTREAM_WORKSPACE_ID` set, it also reconciles the workflow users, as deploy
|
|
1554
|
+
does, including when the plan has nothing to apply.
|
|
1555
|
+
- Removing entities, properties or relationships updates the ontology while retaining their
|
|
1556
|
+
database tables, columns, enum types, records and history. Removed fields become nullable
|
|
1557
|
+
and lose their defaults and indexes; removed relationships lose their foreign keys, so they
|
|
1558
|
+
cannot constrain active writes or cascade-delete retained data. The catalog records the kept
|
|
1559
|
+
storage: declaring the entity again with its `id`, or the field again under its name,
|
|
1560
|
+
restores it with its records, constraints and indexes. Another `id` under a kept name, a kept
|
|
1561
|
+
field with another type, or a name the database holds without a record is refused.
|
|
1562
|
+
`planMigration` takes the record from `readRetainedStorage` and the schema's names from
|
|
1563
|
+
`readStorageNames`; `migrate` and the binary read both.
|
|
1564
|
+
- Removing a select option validates every retained record in the migration transaction. If any
|
|
1565
|
+
record still holds a removed value, the migration fails and rolls back schema and catalog changes.
|
|
1566
|
+
Other stored values and history are preserved; destructive storage drops stay disabled by default.
|
|
1567
|
+
- The planner refuses changes it cannot make safely, for example a property type change.
|
|
1568
|
+
A refusal prints its code and path, and nothing is applied.
|
|
1569
|
+
|
|
1570
|
+
Exit codes: `0` success, `1` errors or a failed migration, `2` refused migration, `64`
|
|
1571
|
+
usage error.
|
|
1572
|
+
|
|
1573
|
+
## Deploy
|
|
1574
|
+
|
|
1575
|
+
`segmentstream deploy` uploads `ontology/` with the app. The deploy pipeline evaluates
|
|
1576
|
+
`ontology/index.ts`, migrates the workspace database and deploys the service under
|
|
1577
|
+
`/api/ontology`. A failure stops the deployment and keeps the running version.
|
|
1578
|
+
|
|
1579
|
+
## Development tests
|
|
1580
|
+
|
|
1581
|
+
`pnpm --filter @segmentstream/ontology test` runs the ordinary suite with
|
|
1582
|
+
embedded PGlite. Authentication contention tests require separate PostgreSQL
|
|
1583
|
+
backends and run against a local disposable PostgreSQL server:
|
|
1584
|
+
|
|
1585
|
+
```sh
|
|
1586
|
+
ONTOLOGY_TEST_ADMIN_URL=postgresql://postgres@127.0.0.1:5432/postgres \
|
|
1587
|
+
pnpm --filter @segmentstream/ontology run test:postgres
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
The test fixture accepts only a localhost `postgres` admin connection to the
|
|
1591
|
+
`postgres` database. It creates a randomly named test database, applies the example
|
|
1592
|
+
ontology, and drops that database on completion. Repository CI and the SDK release
|
|
1593
|
+
gate run this suite with PostgreSQL 17.
|
|
1594
|
+
|
|
1595
|
+
## Request timing
|
|
1596
|
+
|
|
1597
|
+
Every service response includes `Server-Timing: ontology;dur=…` and `x-request-id`.
|
|
1598
|
+
Structured `ontology.request` runtime logs carry that ID, a safe entity route label,
|
|
1599
|
+
HTTP method/status and duration in milliseconds. They exclude request URLs,
|
|
1600
|
+
parameters, bodies, credentials and member IDs. Duration includes authentication
|
|
1601
|
+
and query execution up to response headers, not streaming bodies or browser rendering.
|
|
1602
|
+
Read hosted logs with `segmentstream logs --service ontology` (deployment permission required).
|
|
1603
|
+
|
|
1604
|
+
Status is the HTTP status, so an MCP tool error can still have status 200. This
|
|
1605
|
+
does not collect browser web vitals, page rendering times, database plans or
|
|
1606
|
+
SQL-level timings. There is no per-instance sample store or MCP diagnostics
|
|
1607
|
+
reader. Retained request diagnostics are tracked in issue #520.
|
|
1608
|
+
|
|
1609
|
+
## License
|
|
1610
|
+
|
|
1611
|
+
Apache-2.0
|
|
1612
|
+
|
|
1613
|
+
## Entity sources and metric queries
|
|
1614
|
+
|
|
1615
|
+
Entities with a warehouse source declare mapped properties, metrics and relationships without ontology storage tables or actions. `find`, `query` and `get` read observations through the workspace's warehouse API, with opaque source-key record IDs and bounded, permission-checked relationship reads. See [warehouse record reads](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-record-reads.md). Stored entities may declare metrics too; they are computed over the entity's replication view, whose columns are the properties' snake_case names. See [ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md) for declaration rules, metric query paths, source verification and workspace authorization.
|
|
1616
|
+
|
|
1617
|
+
See [release order and rollback](../../docs/agent-chat-delivery-migration.md).
|
|
1618
|
+
|
|
1619
|
+
### Telegram link previews
|
|
1620
|
+
|
|
1621
|
+
Telegram delivery defaults to `external`: preview the first external HTTP(S) destination
|
|
1622
|
+
and suppress previews when all links belong to the workspace. Hosted deployments use
|
|
1623
|
+
`agent/segmentstream.json` channel settings. Standalone services accept
|
|
1624
|
+
`channels: { telegram: { connection: "bot", linkPreviews: "external", additionalOwnOrigins: [] } }`
|
|
1625
|
+
and `appOrigins: ["https://app.example"]` in their service configuration. Opt into `none`
|
|
1626
|
+
to suppress every preview or `all` for Telegram's native first-link preview. Without known
|
|
1627
|
+
workspace origins, `external` retains native behavior. Settings apply only to the named
|
|
1628
|
+
destination connection and do not enable a provider. A different destination uses the
|
|
1629
|
+
platform `external` default, independently of source overrides. Precedence is explicit modern
|
|
1630
|
+
provider options, then legacy options, then channel settings, then the `external` default.
|
|
1631
|
+
The public `@segmentstream/ontology/telegram` entry exports the pure preview
|
|
1632
|
+
policy, existing-parser link extraction and runtime metadata helpers for other senders.
|
|
1633
|
+
|
|
1634
|
+
### Opt-in after-commit actions
|
|
1635
|
+
|
|
1636
|
+
Declare `defineOntology({ hooks: { role: "hook" }, roles: [hook], ... })` to expose
|
|
1637
|
+
`event.run({ entity, action, input, as: "service" })` to configured after-commit hooks. The role
|
|
1638
|
+
must exist. Each call runs through the normal action pipeline as a stable keyless service with
|
|
1639
|
+
only that role's current permissions, without member baseline grants or the originating member's
|
|
1640
|
+
rights. Deactivated service actors and revoked role assignments stay revoked. Without the
|
|
1641
|
+
`hooks` declaration the event has no `run`. Hooks execute after commit; failures are logged and
|
|
1642
|
+
cannot undo the originating action. Workspaces own recursion prevention, delivery attempts and
|
|
1643
|
+
any retry policy.
|
|
1644
|
+
|
|
1645
|
+
Authenticated runtime actions can read `context.runtime?.source`. It is populated only from
|
|
1646
|
+
verified runtime metadata, including Telegram source and staged attachment bindings, and is
|
|
1647
|
+
absent for ordinary OAuth/API-key input. Do not trust an action input property named `source`.
|
|
1648
|
+
|
|
1649
|
+
### Replication lifecycle and freshness
|
|
1650
|
+
|
|
1651
|
+
Cloud publishes a generation's views after its baseline, backfill and replay preparation finish
|
|
1652
|
+
and Pub/Sub accepts its fixed initialization prefix. Later writes do not postpone that boundary
|
|
1653
|
+
or make published views unavailable. Pub/Sub acceptance does not prove BigQuery visibility;
|
|
1654
|
+
the native BigQuery subscription handles delivery and retries. Replication status reports
|
|
1655
|
+
`captured` and `accepted`, and metric query responses carry no destination freshness watermark.
|
|
1656
|
+
Initial copies and replay remain unavailable until acceptance and view publication finish.
|