@segmentstream/ontology 0.0.0-stage → 0.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +1655 -2
- package/dist/action.d.ts +193 -0
- package/dist/action.js +41 -0
- package/dist/agent-chat.d.ts +44 -0
- package/dist/agent-chat.js +91 -0
- package/dist/agent-task-contract.d.ts +64 -0
- package/dist/agent-task-contract.js +15 -0
- package/dist/agent-task.d.ts +39 -0
- package/dist/agent-task.js +112 -0
- package/dist/analyze.d.ts +215 -0
- package/dist/analyze.js +2438 -0
- package/dist/app-feedback.d.ts +167 -0
- package/dist/app-feedback.js +176 -0
- package/dist/authoring-input.d.ts +3 -0
- package/dist/authoring-input.js +107 -0
- package/dist/authoring-names.d.ts +4 -0
- package/dist/authoring-names.js +13 -0
- package/dist/authoring-records.d.ts +35 -0
- package/dist/authoring-records.js +264 -0
- package/dist/authoring.d.ts +15 -0
- package/dist/authoring.js +331 -0
- package/dist/bin/check.d.ts +30 -0
- package/dist/bin/check.js +99 -0
- package/dist/bin/load.d.ts +48 -0
- package/dist/bin/load.js +139 -0
- package/dist/bin/main.d.ts +31 -0
- package/dist/bin/main.js +121 -0
- package/dist/bin/migrate.d.ts +57 -0
- package/dist/bin/migrate.js +170 -0
- package/dist/bin/output.d.ts +31 -0
- package/dist/bin/output.js +84 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +31 -0
- package/dist/blob.d.ts +52 -0
- package/dist/blob.js +87 -0
- package/dist/channel-session.d.ts +16 -0
- package/dist/channel-session.js +16 -0
- package/dist/chat.d.ts +43 -0
- package/dist/chat.js +34 -0
- package/dist/confirmation.d.ts +46 -0
- package/dist/confirmation.js +41 -0
- package/dist/connection.d.ts +213 -0
- package/dist/connection.js +289 -0
- package/dist/deployment.d.ts +35 -0
- package/dist/deployment.js +76 -0
- package/dist/diagnostics.d.ts +27 -0
- package/dist/diagnostics.js +106 -0
- package/dist/emails.d.ts +70 -0
- package/dist/emails.js +221 -0
- package/dist/entity.d.ts +331 -0
- package/dist/entity.js +153 -0
- package/dist/errors.d.ts +154 -0
- package/dist/errors.js +109 -0
- package/dist/feedback-config.d.ts +24 -0
- package/dist/feedback-config.js +126 -0
- package/dist/files.d.ts +39 -0
- package/dist/files.js +4 -0
- package/dist/groups.d.ts +95 -0
- package/dist/groups.js +280 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +70 -0
- package/dist/internal/diagnostics.js +594 -0
- package/dist/legacy.d.ts +160 -0
- package/dist/legacy.js +270 -0
- package/dist/manifest-compatibility.d.ts +8 -0
- package/dist/manifest-compatibility.js +87 -0
- package/dist/manifest.d.ts +3329 -0
- package/dist/manifest.js +757 -0
- package/dist/me.d.ts +46 -0
- package/dist/me.js +109 -0
- package/dist/metric-filters.d.ts +48 -0
- package/dist/metric-filters.js +252 -0
- package/dist/metric-routes.d.ts +132 -0
- package/dist/metric-routes.js +498 -0
- package/dist/metric.d.ts +172 -0
- package/dist/metric.js +132 -0
- package/dist/migrate/apply.d.ts +94 -0
- package/dist/migrate/apply.js +526 -0
- package/dist/migrate/errors.d.ts +46 -0
- package/dist/migrate/errors.js +38 -0
- package/dist/migrate/index.d.ts +10 -0
- package/dist/migrate/index.js +7 -0
- package/dist/migrate/model.d.ts +2311 -0
- package/dist/migrate/model.js +148 -0
- package/dist/migrate/plan.d.ts +51 -0
- package/dist/migrate/plan.js +1856 -0
- package/dist/migrate/preview.d.ts +13 -0
- package/dist/migrate/preview.js +65 -0
- package/dist/migrate/storage.d.ts +226 -0
- package/dist/migrate/storage.js +774 -0
- package/dist/model-metrics.d.ts +22 -0
- package/dist/model-metrics.js +545 -0
- package/dist/names.d.ts +49 -0
- package/dist/names.js +110 -0
- package/dist/navigation-artifact.d.ts +13 -0
- package/dist/navigation-artifact.js +11 -0
- package/dist/navigation-catalog.d.ts +55 -0
- package/dist/navigation-catalog.js +297 -0
- package/dist/nitro-navigation.d.ts +3 -0
- package/dist/nitro-navigation.js +64 -0
- package/dist/nitro-workflows.d.ts +108 -0
- package/dist/nitro-workflows.js +286 -0
- package/dist/nitro.d.ts +40 -0
- package/dist/nitro.js +50 -0
- package/dist/ontology.d.ts +41 -0
- package/dist/ontology.js +46 -0
- package/dist/permissions.d.ts +198 -0
- package/dist/permissions.js +272 -0
- package/dist/property.d.ts +188 -0
- package/dist/property.js +301 -0
- package/dist/query.d.ts +245 -0
- package/dist/query.js +615 -0
- package/dist/relationship.d.ts +99 -0
- package/dist/relationship.js +33 -0
- package/dist/render.d.ts +81 -0
- package/dist/render.js +512 -0
- package/dist/replication/baseline.d.ts +51 -0
- package/dist/replication/baseline.js +215 -0
- package/dist/replication/bigquery.d.ts +37 -0
- package/dist/replication/bigquery.js +211 -0
- package/dist/replication/coverage.d.ts +14 -0
- package/dist/replication/coverage.js +53 -0
- package/dist/replication/evolution.d.ts +25 -0
- package/dist/replication/evolution.js +184 -0
- package/dist/replication/identities.d.ts +6 -0
- package/dist/replication/identities.js +47 -0
- package/dist/replication/legacy-baseline.d.ts +27 -0
- package/dist/replication/legacy-baseline.js +109 -0
- package/dist/replication/legacy-store.d.ts +19 -0
- package/dist/replication/legacy-store.js +63 -0
- package/dist/replication/lineage.d.ts +36 -0
- package/dist/replication/lineage.js +72 -0
- package/dist/replication/migrate.d.ts +21 -0
- package/dist/replication/migrate.js +84 -0
- package/dist/replication/model.d.ts +52 -0
- package/dist/replication/model.js +99 -0
- package/dist/replication/scans.d.ts +34 -0
- package/dist/replication/scans.js +158 -0
- package/dist/replication/schema.d.ts +11 -0
- package/dist/replication/schema.js +226 -0
- package/dist/replication/shadow-schema.d.ts +4 -0
- package/dist/replication/shadow-schema.js +61 -0
- package/dist/replication/shadow.d.ts +127 -0
- package/dist/replication/shadow.js +614 -0
- package/dist/replication/source-replacement.d.ts +33 -0
- package/dist/replication/source-replacement.js +76 -0
- package/dist/replication/store.d.ts +36 -0
- package/dist/replication/store.js +382 -0
- package/dist/replication/transaction.d.ts +23 -0
- package/dist/replication/transaction.js +88 -0
- package/dist/role.d.ts +61 -0
- package/dist/role.js +76 -0
- package/dist/runtime/action-deadlocks.d.ts +21 -0
- package/dist/runtime/action-deadlocks.js +130 -0
- package/dist/runtime/action-transactions.d.ts +35 -0
- package/dist/runtime/action-transactions.js +54 -0
- package/dist/runtime/agent-counters.d.ts +26 -0
- package/dist/runtime/agent-counters.js +98 -0
- package/dist/runtime/agent-tasks.d.ts +63 -0
- package/dist/runtime/agent-tasks.js +120 -0
- package/dist/runtime/agents.d.ts +15 -0
- package/dist/runtime/agents.js +457 -0
- package/dist/runtime/batch-diagnostics.d.ts +22 -0
- package/dist/runtime/batch-diagnostics.js +89 -0
- package/dist/runtime/batch.d.ts +52 -0
- package/dist/runtime/batch.js +534 -0
- package/dist/runtime/channel-session-revocation.d.ts +3 -0
- package/dist/runtime/channel-session-revocation.js +5 -0
- package/dist/runtime/channel-sessions.d.ts +4 -0
- package/dist/runtime/channel-sessions.js +61 -0
- package/dist/runtime/confirmations.d.ts +29 -0
- package/dist/runtime/confirmations.js +57 -0
- package/dist/runtime/cursor.d.ts +23 -0
- package/dist/runtime/cursor.js +100 -0
- package/dist/runtime/deployment.d.ts +7 -0
- package/dist/runtime/deployment.js +4 -0
- package/dist/runtime/errors.d.ts +118 -0
- package/dist/runtime/errors.js +109 -0
- package/dist/runtime/federation-tables.d.ts +2 -0
- package/dist/runtime/federation-tables.js +20 -0
- package/dist/runtime/federation.d.ts +42 -0
- package/dist/runtime/federation.js +222 -0
- package/dist/runtime/files.d.ts +39 -0
- package/dist/runtime/files.js +44 -0
- package/dist/runtime/generated-actions.d.ts +37 -0
- package/dist/runtime/generated-actions.js +307 -0
- package/dist/runtime/history.d.ts +91 -0
- package/dist/runtime/history.js +77 -0
- package/dist/runtime/ids.d.ts +6 -0
- package/dist/runtime/ids.js +39 -0
- package/dist/runtime/member-tables.d.ts +8 -0
- package/dist/runtime/member-tables.js +33 -0
- package/dist/runtime/member-writes.d.ts +78 -0
- package/dist/runtime/member-writes.js +202 -0
- package/dist/runtime/metric-query.d.ts +19 -0
- package/dist/runtime/metric-query.js +411 -0
- package/dist/runtime/model.d.ts +67 -0
- package/dist/runtime/model.js +150 -0
- package/dist/runtime/new-user-hook.d.ts +6 -0
- package/dist/runtime/new-user-hook.js +37 -0
- package/dist/runtime/oauth.d.ts +9 -0
- package/dist/runtime/oauth.js +299 -0
- package/dist/runtime/pipeline.d.ts +155 -0
- package/dist/runtime/pipeline.js +597 -0
- package/dist/runtime/postgres-tls.d.ts +10 -0
- package/dist/runtime/postgres-tls.js +14 -0
- package/dist/runtime/principals.d.ts +302 -0
- package/dist/runtime/principals.js +1307 -0
- package/dist/runtime/reads.d.ts +55 -0
- package/dist/runtime/reads.js +313 -0
- package/dist/runtime/records.d.ts +42 -0
- package/dist/runtime/records.js +245 -0
- package/dist/runtime/resolve-agent-identity.d.ts +6 -0
- package/dist/runtime/resolve-agent-identity.js +181 -0
- package/dist/runtime/service-binding.d.ts +18 -0
- package/dist/runtime/service-binding.js +266 -0
- package/dist/runtime/sign-in-links.d.ts +68 -0
- package/dist/runtime/sign-in-links.js +147 -0
- package/dist/runtime/sql-runtime.d.ts +131 -0
- package/dist/runtime/sql-runtime.js +362 -0
- package/dist/runtime/statement.d.ts +18 -0
- package/dist/runtime/statement.js +31 -0
- package/dist/runtime/user-identities.d.ts +66 -0
- package/dist/runtime/user-identities.js +106 -0
- package/dist/runtime/user-identity-sql.d.ts +86 -0
- package/dist/runtime/user-identity-sql.js +297 -0
- package/dist/runtime/user-roles.d.ts +16 -0
- package/dist/runtime/user-roles.js +32 -0
- package/dist/runtime/validation.d.ts +16 -0
- package/dist/runtime/validation.js +26 -0
- package/dist/runtime/where.d.ts +57 -0
- package/dist/runtime/where.js +404 -0
- package/dist/runtime/workflow-admission.d.ts +49 -0
- package/dist/runtime/workflow-admission.js +92 -0
- package/dist/runtime/workflow-argument-codec.d.ts +10 -0
- package/dist/runtime/workflow-argument-codec.js +120 -0
- package/dist/runtime/workflow-arguments.d.ts +17 -0
- package/dist/runtime/workflow-arguments.js +99 -0
- package/dist/runtime/workflow-starts.d.ts +203 -0
- package/dist/runtime/workflow-starts.js +493 -0
- package/dist/runtime/workflow-users.d.ts +9 -0
- package/dist/runtime/workflow-users.js +17 -0
- package/dist/runtime/workload.d.ts +25 -0
- package/dist/runtime/workload.js +24 -0
- package/dist/runtime/writes.d.ts +108 -0
- package/dist/runtime/writes.js +491 -0
- package/dist/runtime-source.d.ts +22 -0
- package/dist/runtime-source.js +28 -0
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.js +31 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +107 -0
- package/dist/service/agent/attachments.d.ts +33 -0
- package/dist/service/agent/attachments.js +228 -0
- package/dist/service/agent/authentication.d.ts +22 -0
- package/dist/service/agent/authentication.js +58 -0
- package/dist/service/agent/confirmations.d.ts +5 -0
- package/dist/service/agent/confirmations.js +73 -0
- package/dist/service/agent/conversation-routes.d.ts +14 -0
- package/dist/service/agent/conversation-routes.js +45 -0
- package/dist/service/agent/conversations.d.ts +40 -0
- package/dist/service/agent/conversations.js +69 -0
- package/dist/service/agent/files.d.ts +12 -0
- package/dist/service/agent/files.js +69 -0
- package/dist/service/agent/protocol.d.ts +90 -0
- package/dist/service/agent/protocol.js +114 -0
- package/dist/service/agent/server.d.ts +28 -0
- package/dist/service/agent/server.js +374 -0
- package/dist/service/agent/task-caller.d.ts +21 -0
- package/dist/service/agent/task-caller.js +35 -0
- package/dist/service/agent/task-start.d.ts +53 -0
- package/dist/service/agent/task-start.js +161 -0
- package/dist/service/agent/tasks.d.ts +40 -0
- package/dist/service/agent/tasks.js +155 -0
- package/dist/service/api.d.ts +50 -0
- package/dist/service/api.js +288 -0
- package/dist/service/authentication.d.ts +227 -0
- package/dist/service/authentication.js +767 -0
- package/dist/service/caller-context.d.ts +24 -0
- package/dist/service/caller-context.js +6 -0
- package/dist/service/catalog.d.ts +42 -0
- package/dist/service/catalog.js +66 -0
- package/dist/service/channel/provider.d.ts +10 -0
- package/dist/service/channel/provider.js +1 -0
- package/dist/service/channel/providers/telegram.d.ts +18 -0
- package/dist/service/channel/providers/telegram.js +108 -0
- package/dist/service/channel/sessions.d.ts +5 -0
- package/dist/service/channel/sessions.js +53 -0
- package/dist/service/channel/sign-in.d.ts +7 -0
- package/dist/service/channel/sign-in.js +78 -0
- package/dist/service/chat/archive.d.ts +64 -0
- package/dist/service/chat/archive.js +62 -0
- package/dist/service/chat/attribution.d.ts +26 -0
- package/dist/service/chat/attribution.js +28 -0
- package/dist/service/chat/delivery.d.ts +18 -0
- package/dist/service/chat/delivery.js +251 -0
- package/dist/service/chat/diagnostics.d.ts +2 -0
- package/dist/service/chat/diagnostics.js +35 -0
- package/dist/service/chat/envelope.d.ts +96 -0
- package/dist/service/chat/envelope.js +39 -0
- package/dist/service/chat/idempotency.d.ts +19 -0
- package/dist/service/chat/idempotency.js +37 -0
- package/dist/service/chat/providers/telegram.d.ts +36 -0
- package/dist/service/chat/providers/telegram.js +133 -0
- package/dist/service/chat/reads.d.ts +25 -0
- package/dist/service/chat/reads.js +75 -0
- package/dist/service/chat/resolve.d.ts +34 -0
- package/dist/service/chat/resolve.js +150 -0
- package/dist/service/chat/routes.d.ts +23 -0
- package/dist/service/chat/routes.js +75 -0
- package/dist/service/chat/schemas.d.ts +43 -0
- package/dist/service/chat/schemas.js +31 -0
- package/dist/service/config.d.ts +218 -0
- package/dist/service/config.js +265 -0
- package/dist/service/connection-stream.d.ts +10 -0
- package/dist/service/connection-stream.js +191 -0
- package/dist/service/connections.d.ts +60 -0
- package/dist/service/connections.js +271 -0
- package/dist/service/decisions.d.ts +35 -0
- package/dist/service/decisions.js +63 -0
- package/dist/service/descriptions.d.ts +8 -0
- package/dist/service/descriptions.js +20 -0
- package/dist/service/discovery-schemas.d.ts +92 -0
- package/dist/service/discovery-schemas.js +59 -0
- package/dist/service/discovery-search.d.ts +30 -0
- package/dist/service/discovery-search.js +95 -0
- package/dist/service/discovery.d.ts +37 -0
- package/dist/service/discovery.js +436 -0
- package/dist/service/docs.d.ts +9 -0
- package/dist/service/docs.js +197 -0
- package/dist/service/errors.d.ts +145 -0
- package/dist/service/errors.js +704 -0
- package/dist/service/federation-provider.d.ts +36 -0
- package/dist/service/federation-provider.js +110 -0
- package/dist/service/federation-server.d.ts +20 -0
- package/dist/service/federation-server.js +249 -0
- package/dist/service/federation-status.d.ts +40 -0
- package/dist/service/federation-status.js +151 -0
- package/dist/service/handlers.d.ts +18 -0
- package/dist/service/handlers.js +287 -0
- package/dist/service/mcp/confirmation.d.ts +68 -0
- package/dist/service/mcp/confirmation.js +178 -0
- package/dist/service/mcp/find-input.d.ts +12 -0
- package/dist/service/mcp/find-input.js +122 -0
- package/dist/service/mcp/input-schema.d.ts +6 -0
- package/dist/service/mcp/input-schema.js +126 -0
- package/dist/service/mcp/listing.d.ts +26 -0
- package/dist/service/mcp/listing.js +92 -0
- package/dist/service/mcp/metric-tools.d.ts +85 -0
- package/dist/service/mcp/metric-tools.js +258 -0
- package/dist/service/mcp/request-log.d.ts +5 -0
- package/dist/service/mcp/request-log.js +53 -0
- package/dist/service/mcp/runtime-policy.d.ts +27 -0
- package/dist/service/mcp/runtime-policy.js +62 -0
- package/dist/service/mcp/server.d.ts +52 -0
- package/dist/service/mcp/server.js +861 -0
- package/dist/service/mcp/tools.d.ts +99 -0
- package/dist/service/mcp/tools.js +938 -0
- package/dist/service/member-file-routes.d.ts +22 -0
- package/dist/service/member-file-routes.js +52 -0
- package/dist/service/member-files.d.ts +245 -0
- package/dist/service/member-files.js +258 -0
- package/dist/service/metric-query-platform.d.ts +77 -0
- package/dist/service/metric-query-platform.js +198 -0
- package/dist/service/metric-query-scopes.d.ts +43 -0
- package/dist/service/metric-query-scopes.js +147 -0
- package/dist/service/metric-query.d.ts +48 -0
- package/dist/service/metric-query.js +200 -0
- package/dist/service/name-suggestions.d.ts +3 -0
- package/dist/service/name-suggestions.js +38 -0
- package/dist/service/navigation-schemas.d.ts +80 -0
- package/dist/service/navigation-schemas.js +49 -0
- package/dist/service/navigation.d.ts +20 -0
- package/dist/service/navigation.js +165 -0
- package/dist/service/oauth/protocol.d.ts +70 -0
- package/dist/service/oauth/protocol.js +157 -0
- package/dist/service/oauth/refresh-replay.d.ts +2 -0
- package/dist/service/oauth/refresh-replay.js +20 -0
- package/dist/service/oauth/server.d.ts +28 -0
- package/dist/service/oauth/server.js +769 -0
- package/dist/service/operations.d.ts +170 -0
- package/dist/service/operations.js +392 -0
- package/dist/service/plan.d.ts +57 -0
- package/dist/service/plan.js +96 -0
- package/dist/service/rate-limit.d.ts +22 -0
- package/dist/service/rate-limit.js +52 -0
- package/dist/service/record-reads.d.ts +21 -0
- package/dist/service/record-reads.js +155 -0
- package/dist/service/replication.d.ts +5 -0
- package/dist/service/replication.js +39 -0
- package/dist/service/runtime-contract.d.ts +508 -0
- package/dist/service/runtime-contract.js +18 -0
- package/dist/service/runtime-identity.d.ts +71 -0
- package/dist/service/runtime-identity.js +122 -0
- package/dist/service/schemas.d.ts +961 -0
- package/dist/service/schemas.js +639 -0
- package/dist/service/service.d.ts +21 -0
- package/dist/service/service.js +526 -0
- package/dist/service/warehouse-platform.d.ts +17 -0
- package/dist/service/warehouse-platform.js +122 -0
- package/dist/service/warehouse-query.d.ts +24 -0
- package/dist/service/warehouse-query.js +280 -0
- package/dist/service/webhooks.d.ts +106 -0
- package/dist/service/webhooks.js +233 -0
- package/dist/service/workflow-admission-provider.d.ts +31 -0
- package/dist/service/workflow-admission-provider.js +205 -0
- package/dist/service/workflow-argument-serialization.d.ts +2 -0
- package/dist/service/workflow-argument-serialization.js +14 -0
- package/dist/service/workflow-callback.d.ts +22 -0
- package/dist/service/workflow-callback.js +75 -0
- package/dist/service/workflow-deployment.d.ts +8 -0
- package/dist/service/workflow-deployment.js +131 -0
- package/dist/service/workflow-diagnostics.d.ts +23 -0
- package/dist/service/workflow-diagnostics.js +263 -0
- package/dist/service/workflow-errors.d.ts +26 -0
- package/dist/service/workflow-errors.js +181 -0
- package/dist/service/workflow-logging.d.ts +2 -0
- package/dist/service/workflow-logging.js +50 -0
- package/dist/service/workflow-queue-fence.d.ts +6 -0
- package/dist/service/workflow-queue-fence.js +66 -0
- package/dist/service/workflows.d.ts +200 -0
- package/dist/service/workflows.js +1549 -0
- package/dist/sign-in.d.ts +58 -0
- package/dist/sign-in.js +57 -0
- package/dist/source.d.ts +30 -0
- package/dist/source.js +107 -0
- package/dist/start-recovery-contract.d.ts +49 -0
- package/dist/start-recovery-contract.js +26 -0
- package/dist/start-recovery.d.ts +27 -0
- package/dist/start-recovery.js +81 -0
- package/dist/storage/api.d.ts +4 -0
- package/dist/storage/api.js +40 -0
- package/dist/storage/attachments.d.ts +146 -0
- package/dist/storage/attachments.js +733 -0
- package/dist/storage/authorization.d.ts +38 -0
- package/dist/storage/authorization.js +83 -0
- package/dist/storage/file-access.d.ts +17 -0
- package/dist/storage/file-access.js +8 -0
- package/dist/storage/limits.d.ts +12 -0
- package/dist/storage/limits.js +17 -0
- package/dist/storage/maintenance-auth.d.ts +20 -0
- package/dist/storage/maintenance-auth.js +24 -0
- package/dist/storage/maintenance.d.ts +11 -0
- package/dist/storage/maintenance.js +67 -0
- package/dist/storage/neon.d.ts +4 -0
- package/dist/storage/neon.js +318 -0
- package/dist/storage/operator.d.ts +11 -0
- package/dist/storage/operator.js +30 -0
- package/dist/storage/provider.d.ts +93 -0
- package/dist/storage/provider.js +36 -0
- package/dist/storage/sessions.d.ts +184 -0
- package/dist/storage/sessions.js +688 -0
- package/dist/storage/tables.d.ts +54 -0
- package/dist/storage/tables.js +205 -0
- package/dist/storage/transfers.d.ts +16 -0
- package/dist/storage/transfers.js +153 -0
- package/dist/storage/writes.d.ts +7 -0
- package/dist/storage/writes.js +88 -0
- package/dist/telegram.d.ts +35 -0
- package/dist/telegram.js +173 -0
- package/dist/user-identity.d.ts +28 -0
- package/dist/user-identity.js +106 -0
- package/dist/user.d.ts +75 -0
- package/dist/user.js +111 -0
- package/dist/warehouse-backed.d.ts +6 -0
- package/dist/warehouse-backed.js +495 -0
- package/dist/webhook-token.d.ts +11 -0
- package/dist/webhook-token.js +18 -0
- package/dist/webhooks.d.ts +40 -0
- package/dist/webhooks.js +55 -0
- package/dist/wire.d.ts +10 -0
- package/dist/wire.js +35 -0
- package/dist/workflow-runtime.d.ts +116 -0
- package/dist/workflow-runtime.js +109 -0
- package/dist/workflow-topics.d.ts +28 -0
- package/dist/workflow-topics.js +28 -0
- package/dist/workflow.d.ts +284 -0
- package/dist/workflow.js +259 -0
- package/dist/workflows.d.ts +24 -0
- package/dist/workflows.js +18 -0
- package/package.json +127 -4
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { replicationIdentities } from "./identities.js";
|
|
2
|
+
import { queryFields } from "../query.js";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { canonicalJson } from "../manifest.js";
|
|
6
|
+
import { ReplicationBaselineFailed } from "./baseline.js";
|
|
7
|
+
import { initialReplicationLineage, renamedReplicationField } from "./lineage.js";
|
|
8
|
+
import { replicationScanScopes, scheduleReplicationScans, } from "./scans.js";
|
|
9
|
+
import { lockReplicationLifecycle } from "./transaction.js";
|
|
10
|
+
export const SHADOW_HANDOFF_MS = 30_000;
|
|
11
|
+
export const SHADOW_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
12
|
+
export const SHADOW_QUERY_LIFETIME_MS = 60 * 60 * 1000;
|
|
13
|
+
const shadowJson = (value) => JSON.stringify(value, (_key, item) => (typeof item === "bigint" ? item.toString() : item));
|
|
14
|
+
const epochFields = `id::text, source_id::text, desired_hash, target, routes, state,
|
|
15
|
+
predecessor::text, boundary::text, catchup_boundary::text, token::text, fence::text,
|
|
16
|
+
handoff_until::text, operation_id::text, installed, cancel_requested`;
|
|
17
|
+
const segmentFields = `id::text, epoch::text, generation::text, source_id::text,
|
|
18
|
+
through_revision::text, events, event_ids, digest, object_uri, job_id, attempt, state, retry_after::text`;
|
|
19
|
+
/** Names the target keys a stored route omits because its epoch target holds the same value. */
|
|
20
|
+
const SHARED_FROM_EPOCH = "shared_from_epoch";
|
|
21
|
+
/** SQL reads these keys from stored routes, for example retention checks on route generations. */
|
|
22
|
+
const STORED_ROUTE_KEYS = new Set(["generation", "source_id"]);
|
|
23
|
+
const sameSharedValue = (key, route, epoch) => {
|
|
24
|
+
if (route === undefined || epoch === undefined) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (route === epoch) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (key === "manifest") {
|
|
31
|
+
const routeHash = route?.hash;
|
|
32
|
+
return typeof routeHash === "string" && routeHash === epoch?.hash;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return canonicalJson(route) === canonicalJson(epoch);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Every route carries a full target, and most routes repeat the epoch target's manifest and
|
|
43
|
+
* projection. One copy per scope grows with entities times manifest size, past what a single
|
|
44
|
+
* PostgreSQL message may carry, so a stored route names the keys it takes from its epoch target.
|
|
45
|
+
*/
|
|
46
|
+
export function compactShadowRoutes(routes, target) {
|
|
47
|
+
const epoch = target;
|
|
48
|
+
return routes.map((route) => {
|
|
49
|
+
const record = route.target;
|
|
50
|
+
const shared = Object.keys(record).filter((key) => !STORED_ROUTE_KEYS.has(key) && sameSharedValue(key, record[key], epoch[key]));
|
|
51
|
+
if (shared.length === 0) {
|
|
52
|
+
return route;
|
|
53
|
+
}
|
|
54
|
+
const stored = { [SHARED_FROM_EPOCH]: shared };
|
|
55
|
+
for (const [key, value] of Object.entries(record)) {
|
|
56
|
+
if (!shared.includes(key)) {
|
|
57
|
+
stored[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ...route, target: stored };
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Restores the keys a stored route took from its epoch target. Full stored routes pass through. */
|
|
64
|
+
export function expandShadowRoutes(routes, target) {
|
|
65
|
+
const epoch = target;
|
|
66
|
+
return routes.map((stored) => {
|
|
67
|
+
const route = stored;
|
|
68
|
+
const record = route.target;
|
|
69
|
+
const shared = record[SHARED_FROM_EPOCH];
|
|
70
|
+
if (!Array.isArray(shared)) {
|
|
71
|
+
return route;
|
|
72
|
+
}
|
|
73
|
+
const expanded = {};
|
|
74
|
+
for (const key of shared) {
|
|
75
|
+
expanded[key] = epoch[key];
|
|
76
|
+
}
|
|
77
|
+
for (const [key, value] of Object.entries(record)) {
|
|
78
|
+
if (key !== SHARED_FROM_EPOCH) {
|
|
79
|
+
expanded[key] = value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { ...route, target: expanded };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const expandEpoch = (row) => row && { ...row, routes: expandShadowRoutes(row.routes, row.target) };
|
|
86
|
+
export const shadowEpoch = (sql, id) => sql
|
|
87
|
+
.unsafe(`SELECT ${epochFields} FROM _ontology_replication_epochs WHERE id=$1::uuid`, [id])
|
|
88
|
+
.pipe(Effect.map((rows) => expandEpoch(rows[0])));
|
|
89
|
+
export const servingShadowEpoch = (sql) => sql
|
|
90
|
+
.unsafe(`SELECT ${epochFields} FROM _ontology_replication_epochs
|
|
91
|
+
WHERE id=(SELECT serving_epoch FROM _ontology_replication WHERE id)`)
|
|
92
|
+
.pipe(Effect.map((rows) => expandEpoch(rows[0])));
|
|
93
|
+
export const candidateShadowEpoch = (sql) => sql
|
|
94
|
+
.unsafe(`SELECT ${epochFields} FROM _ontology_replication_epochs WHERE state='candidate'`)
|
|
95
|
+
.pipe(Effect.map((rows) => expandEpoch(rows[0])));
|
|
96
|
+
export const shadowProjectionHash = (target) => createHash("sha256")
|
|
97
|
+
.update(canonicalJson({
|
|
98
|
+
manifest: target.manifest.hash,
|
|
99
|
+
selection: target.selection,
|
|
100
|
+
fills: target.fills ?? {},
|
|
101
|
+
identities: replicationIdentities(target),
|
|
102
|
+
}))
|
|
103
|
+
.digest("hex");
|
|
104
|
+
/** Called under exclusive lifecycle admission; no provider I/O is permitted here. */
|
|
105
|
+
export const adoptShadowServing = (sql, target) => Effect.gen(function* () {
|
|
106
|
+
const existing = yield* servingShadowEpoch(sql);
|
|
107
|
+
if (existing) {
|
|
108
|
+
return existing;
|
|
109
|
+
}
|
|
110
|
+
target = { ...target, identities: replicationIdentities(target) };
|
|
111
|
+
const routes = replicationScanScopes(target).map((scope) => ({ ...scope, target }));
|
|
112
|
+
yield* sql.unsafe(`INSERT INTO _ontology_replication_epochs
|
|
113
|
+
(id,source_id,desired_hash,target,routes,state) VALUES ($1::uuid,$2::uuid,$3,$4::jsonb,$5::jsonb,'serving')
|
|
114
|
+
ON CONFLICT (id) DO UPDATE SET state='serving'`, [
|
|
115
|
+
target.generation,
|
|
116
|
+
target.source_id,
|
|
117
|
+
shadowProjectionHash(target),
|
|
118
|
+
shadowJson(target),
|
|
119
|
+
shadowJson(compactShadowRoutes(routes, target)),
|
|
120
|
+
]);
|
|
121
|
+
yield* sql.unsafe(`UPDATE _ontology_replication SET serving_epoch=$1::uuid WHERE id`, [
|
|
122
|
+
target.generation,
|
|
123
|
+
]);
|
|
124
|
+
yield* enableShadowCapture(sql, target, replicationScanScopes(target));
|
|
125
|
+
return (yield* shadowEpoch(sql, target.generation));
|
|
126
|
+
});
|
|
127
|
+
/** Capture bindings name wire fields, not public columns. Renames never rewrite historical JSON. */
|
|
128
|
+
export const enableShadowCapture = (sql, target, scopes) => sql
|
|
129
|
+
.unsafe(`UPDATE _ontology_replication_targets SET capturing=true,
|
|
130
|
+
identities=COALESCE(identities,$4::jsonb),
|
|
131
|
+
capture_scopes=$2::jsonb, capture_bindings=COALESCE(capture_bindings,$3::jsonb),
|
|
132
|
+
capture_versions=COALESCE(capture_versions,jsonb_build_array(jsonb_build_object('bindings',$3::jsonb)))
|
|
133
|
+
WHERE generation=$1::uuid`, [
|
|
134
|
+
target.generation,
|
|
135
|
+
JSON.stringify(scopes),
|
|
136
|
+
JSON.stringify(Object.fromEntries(target.selection.map((item) => [
|
|
137
|
+
item.entityId,
|
|
138
|
+
Object.fromEntries([...new Set(["id", ...item.fields])].map((field) => [field, field])),
|
|
139
|
+
]))),
|
|
140
|
+
JSON.stringify(replicationIdentities(target)),
|
|
141
|
+
])
|
|
142
|
+
.pipe(Effect.andThen(refreshShadowCapturePolicy(sql)));
|
|
143
|
+
/**
|
|
144
|
+
* Immutable epochs may share unaffected generations. Only complete affected entity scopes get a
|
|
145
|
+
* fresh incarnation. Generation-zero snapshots cannot overwrite mutations in another incarnation.
|
|
146
|
+
*/
|
|
147
|
+
export const prepareShadowEpoch = (sql, input) => Effect.gen(function* () {
|
|
148
|
+
if (input.predecessor) {
|
|
149
|
+
const previous = input.predecessor;
|
|
150
|
+
yield* sql.unsafe(`INSERT INTO _ontology_replication_epochs
|
|
151
|
+
(id,source_id,desired_hash,target,routes,state)
|
|
152
|
+
VALUES ($1::uuid,$2::uuid,$3,$4::jsonb,$5::jsonb,'retired') ON CONFLICT DO NOTHING`, [
|
|
153
|
+
previous.id,
|
|
154
|
+
previous.source_id,
|
|
155
|
+
previous.desired_hash,
|
|
156
|
+
shadowJson(previous.target),
|
|
157
|
+
shadowJson(compactShadowRoutes(previous.routes, previous.target)),
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
const desiredHash = shadowProjectionHash(input.target);
|
|
161
|
+
const pending = yield* candidateShadowEpoch(sql);
|
|
162
|
+
if (!pending?.cancel_requested &&
|
|
163
|
+
pending?.desired_hash === desiredHash &&
|
|
164
|
+
pending.predecessor === (input.predecessor?.id ?? null) &&
|
|
165
|
+
(!input.force || pending.routes.every((route) => route.target.generation === pending.id))) {
|
|
166
|
+
const [capture] = yield* sql.unsafe("SELECT enabled FROM _ontology_replication WHERE id");
|
|
167
|
+
if (capture?.enabled) {
|
|
168
|
+
return pending;
|
|
169
|
+
}
|
|
170
|
+
// Pause interrupted capture admission. A fresh incarnation scans authoritative rows and
|
|
171
|
+
// fences late old loads; reusing this candidate would leave its source disabled forever.
|
|
172
|
+
}
|
|
173
|
+
// Supersession with an installed handoff must first reconcile its destination fence. A migration
|
|
174
|
+
// can commit desired metadata, but cannot make another candidate publication-eligible yet.
|
|
175
|
+
if (pending?.token) {
|
|
176
|
+
return yield* new ReplicationBaselineFailed({
|
|
177
|
+
message: "replication_publication_reconciliation_pending",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (pending) {
|
|
181
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_scans SET complete=true,required=false
|
|
182
|
+
WHERE generation=$1::uuid`, [pending.id]);
|
|
183
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET state='cancelled',owner=NULL,owner_until=NULL
|
|
184
|
+
WHERE id=$1::uuid`, [pending.id]);
|
|
185
|
+
}
|
|
186
|
+
const id = input.target.generation === input.predecessor?.id || input.target.generation === pending?.id
|
|
187
|
+
? randomUUID()
|
|
188
|
+
: input.target.generation;
|
|
189
|
+
const target = {
|
|
190
|
+
...input.target,
|
|
191
|
+
generation: id,
|
|
192
|
+
schema_version: 6,
|
|
193
|
+
identities: replicationIdentities(input.target),
|
|
194
|
+
protocol: 4,
|
|
195
|
+
baseline_complete: input.affected.length === 0,
|
|
196
|
+
scans_complete: input.affected.length === 0,
|
|
197
|
+
lineage: initialReplicationLineage(input.target),
|
|
198
|
+
};
|
|
199
|
+
yield* sql.unsafe(`INSERT INTO _ontology_replication_targets
|
|
200
|
+
(generation,source_id,from_revision,manifest,selection,protocol,baseline_complete,
|
|
201
|
+
scans_complete,lineage,fills,identities)
|
|
202
|
+
VALUES ($1::uuid,$2::uuid,0,$3::jsonb,$4::jsonb,4,$5,$5,$6::jsonb,$7::jsonb,$8::jsonb) ON CONFLICT DO NOTHING`, [
|
|
203
|
+
id,
|
|
204
|
+
target.source_id,
|
|
205
|
+
JSON.stringify(target.manifest),
|
|
206
|
+
JSON.stringify(target.selection),
|
|
207
|
+
input.affected.length === 0,
|
|
208
|
+
JSON.stringify(target.lineage),
|
|
209
|
+
JSON.stringify(target.fills ?? {}),
|
|
210
|
+
JSON.stringify(target.identities),
|
|
211
|
+
]);
|
|
212
|
+
const affectedEntities = new Set(input.affected.map((scope) => scope.entityId));
|
|
213
|
+
const scopes = replicationScanScopes(target).filter((scope) => affectedEntities.has(scope.entityId));
|
|
214
|
+
const captures = yield* sql.unsafe("SELECT generation::text,capture_bindings FROM _ontology_replication_targets WHERE capturing");
|
|
215
|
+
const routes = replicationScanScopes(target).map((scope) => {
|
|
216
|
+
const old = input.predecessor?.routes.find((route) => route.entityId === scope.entityId && route.kind === scope.kind);
|
|
217
|
+
if (!old || affectedEntities.has(scope.entityId)) {
|
|
218
|
+
return { ...scope, target };
|
|
219
|
+
}
|
|
220
|
+
// Reuse retained events through a schema-aware projection, without changing old epoch metadata.
|
|
221
|
+
const projected = {
|
|
222
|
+
...old.target,
|
|
223
|
+
manifest: target.manifest,
|
|
224
|
+
identities: target.identities,
|
|
225
|
+
selection: target.selection,
|
|
226
|
+
fills: target.fills,
|
|
227
|
+
};
|
|
228
|
+
const bindings = captures.find((capture) => capture.generation === old.target.generation)
|
|
229
|
+
?.capture_bindings ?? {};
|
|
230
|
+
const previousLineage = old.target.lineage ?? initialReplicationLineage(old.target);
|
|
231
|
+
const last = previousLineage.epochs.at(-1);
|
|
232
|
+
const lineage = {
|
|
233
|
+
...previousLineage,
|
|
234
|
+
epochs: previousLineage.epochs.map((epoch) => ({
|
|
235
|
+
...epoch,
|
|
236
|
+
fields: Object.fromEntries(target.selection.map((selected) => [
|
|
237
|
+
selected.entityId,
|
|
238
|
+
Object.fromEntries([...new Set(["id", ...selected.fields])].map((field) => {
|
|
239
|
+
const slot = Object.entries(bindings[selected.entityId] ?? {}).find(([, current]) => current === field)?.[0];
|
|
240
|
+
const previousField = Object.entries(last.fields[selected.entityId] ?? {}).find(([, binding]) => binding.key === slot)?.[0];
|
|
241
|
+
const binding = previousField
|
|
242
|
+
? epoch.fields[selected.entityId]?.[previousField]
|
|
243
|
+
: undefined;
|
|
244
|
+
const fills = target.fills?.[selected.entityId] ?? {};
|
|
245
|
+
return [
|
|
246
|
+
field,
|
|
247
|
+
binding ?? {
|
|
248
|
+
key: slot ?? null,
|
|
249
|
+
...(Object.hasOwn(fills, field) ? { fill: fills[field] } : {}),
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
})),
|
|
253
|
+
])),
|
|
254
|
+
})),
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
...scope,
|
|
258
|
+
target: {
|
|
259
|
+
...projected,
|
|
260
|
+
lineage: {
|
|
261
|
+
...lineage,
|
|
262
|
+
epochs: lineage.epochs.map((epoch, index, epochs) => index === epochs.length - 1 ? { ...epoch, throughRevision: undefined } : epoch),
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
yield* sql.unsafe(`INSERT INTO _ontology_replication_epochs
|
|
268
|
+
(id,source_id,desired_hash,target,routes,state,predecessor)
|
|
269
|
+
VALUES ($1::uuid,$2::uuid,$3,$4::jsonb,$5::jsonb,'candidate',$6::uuid)`, [
|
|
270
|
+
id,
|
|
271
|
+
target.source_id,
|
|
272
|
+
desiredHash,
|
|
273
|
+
shadowJson(target),
|
|
274
|
+
shadowJson(compactShadowRoutes(routes, target)),
|
|
275
|
+
input.predecessor?.id ?? null,
|
|
276
|
+
]);
|
|
277
|
+
yield* enableShadowCapture(sql, target, scopes);
|
|
278
|
+
yield* scheduleReplicationScans(sql, id, scopes, { initial: true });
|
|
279
|
+
yield* sql.unsafe(`UPDATE _ontology_replication SET active_generation=$1::uuid, enabled=true WHERE id`, [id]);
|
|
280
|
+
return (yield* shadowEpoch(sql, id));
|
|
281
|
+
});
|
|
282
|
+
/** Remap destination wire bindings before installing the next source snapshot trigger. */
|
|
283
|
+
export const migrateShadowCapture = (sql, current, next, selection, identities) => Effect.gen(function* () {
|
|
284
|
+
const targets = yield* sql.unsafe(`SELECT generation::text,capture_bindings FROM _ontology_replication_targets WHERE capturing`);
|
|
285
|
+
const [boundary] = yield* sql.unsafe("SELECT (CASE WHEN is_called THEN last_value ELSE 0 END)::text AS revision FROM _ontology_replication_revision");
|
|
286
|
+
for (const target of targets) {
|
|
287
|
+
const bindings = Object.fromEntries(Object.entries(target.capture_bindings ?? {}).map(([entity, fields]) => [
|
|
288
|
+
entity,
|
|
289
|
+
Object.fromEntries(Object.entries(fields).map(([wire, key]) => {
|
|
290
|
+
const declaration = next.entities.find((item) => item.id === entity);
|
|
291
|
+
const renamed = declaration?.properties.find((property) => renamedReplicationField(current, next, entity, property.name) === key)?.name;
|
|
292
|
+
return [wire, renamed ?? key];
|
|
293
|
+
})),
|
|
294
|
+
]));
|
|
295
|
+
for (const selected of selection) {
|
|
296
|
+
const fields = bindings[selected.entityId];
|
|
297
|
+
if (!fields) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const entity = next.entities.find((item) => item.id === selected.entityId);
|
|
301
|
+
for (const field of queryFields(entity).filter((field) => selected.fields.includes(field.name))) {
|
|
302
|
+
if (!Object.values(fields).includes(field.name)) {
|
|
303
|
+
fields[`__ss_${createHash("sha256")
|
|
304
|
+
.update(next.hash + entity.id + field.name + identities[entity.id]?.[field.name])
|
|
305
|
+
.digest("hex")
|
|
306
|
+
.slice(0, 24)}`] = field.name;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_targets SET capture_bindings=$2::jsonb,
|
|
311
|
+
capture_versions=(SELECT jsonb_agg(CASE WHEN NOT item ? 'through'
|
|
312
|
+
THEN item || jsonb_build_object('through',$3::text) ELSE item END ORDER BY n)
|
|
313
|
+
FROM jsonb_array_elements(COALESCE(capture_versions,
|
|
314
|
+
jsonb_build_array(jsonb_build_object('bindings',capture_bindings)))) WITH ORDINALITY AS versions(item,n))
|
|
315
|
+
|| jsonb_build_array(jsonb_build_object('bindings',$2::jsonb))
|
|
316
|
+
WHERE generation=$1::uuid`, [target.generation, JSON.stringify(bindings), boundary.revision]);
|
|
317
|
+
}
|
|
318
|
+
yield* refreshShadowCapturePolicy(sql);
|
|
319
|
+
});
|
|
320
|
+
/** Every pass has a source-side lease as well as the product lease; stale completions are rejected. */
|
|
321
|
+
export const claimShadowEpoch = (sql, id, owner) => sql
|
|
322
|
+
.unsafe(`UPDATE _ontology_replication_epochs SET owner=$2::uuid,owner_until=now()+interval '2 minutes'
|
|
323
|
+
WHERE id=$1::uuid AND state='candidate' AND NOT cancel_requested
|
|
324
|
+
AND EXISTS(SELECT 1 FROM _ontology_replication WHERE id AND enabled AND NOT destination_repair_pending)
|
|
325
|
+
AND (owner=$2::uuid OR owner_until IS NULL OR owner_until<now())
|
|
326
|
+
RETURNING id`, [id, owner])
|
|
327
|
+
.pipe(Effect.map((rows) => rows.length === 1));
|
|
328
|
+
export const releaseShadowEpoch = (sql, id, owner) => sql.unsafe(`UPDATE _ontology_replication_epochs SET owner=NULL,owner_until=NULL WHERE id=$1::uuid AND owner=$2::uuid`, [id, owner]);
|
|
329
|
+
/** A closed source boundary is selected only after all admitted writers and scans have drained. */
|
|
330
|
+
export const beginShadowHandoff = (sql, id, owner, minimumFence = "0") => sql.withTransaction(Effect.gen(function* () {
|
|
331
|
+
yield* sql.unsafe("SET LOCAL lock_timeout='5s'");
|
|
332
|
+
yield* lockReplicationLifecycle(sql);
|
|
333
|
+
yield* sql.unsafe(`SELECT setval('_ontology_replication_cutover_fence',GREATEST(
|
|
334
|
+
(SELECT last_value FROM _ontology_replication_cutover_fence),$1::bigint),true)`, [minimumFence]);
|
|
335
|
+
const [epoch] = yield* sql.unsafe(`UPDATE _ontology_replication_epochs
|
|
336
|
+
SET boundary=(SELECT CASE WHEN is_called THEN last_value ELSE 0 END FROM _ontology_replication_revision),
|
|
337
|
+
token=gen_random_uuid(),fence=nextval('_ontology_replication_cutover_fence'),
|
|
338
|
+
operation_id=gen_random_uuid(),handoff_until=now()+interval '30 seconds'
|
|
339
|
+
WHERE id=$1::uuid AND owner=$2::uuid AND owner_until>now() AND state='candidate'
|
|
340
|
+
AND NOT cancel_requested AND token IS NULL AND installed
|
|
341
|
+
AND id=(SELECT active_generation FROM _ontology_replication WHERE id AND enabled)
|
|
342
|
+
AND NOT EXISTS(SELECT 1 FROM _ontology_replication_scans WHERE generation=$1::uuid AND NOT complete)
|
|
343
|
+
RETURNING ${epochFields}`, [id, owner]);
|
|
344
|
+
return expandEpoch(epoch);
|
|
345
|
+
}));
|
|
346
|
+
/** Destination invalidation must have succeeded (or switched) before this transaction runs. */
|
|
347
|
+
export const abandonShadowHandoff = (sql, epoch) => sql.withTransaction(Effect.gen(function* () {
|
|
348
|
+
yield* lockReplicationLifecycle(sql);
|
|
349
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET catchup_boundary=GREATEST(COALESCE(catchup_boundary,0),boundary),
|
|
350
|
+
token=NULL,boundary=NULL,
|
|
351
|
+
handoff_until=NULL,operation_id=NULL WHERE id=$1::uuid AND token=$2::uuid AND state='candidate'`, [epoch.id, epoch.token]);
|
|
352
|
+
}));
|
|
353
|
+
export const completeShadowEpoch = (sql, epoch) => sql.withTransaction(Effect.gen(function* () {
|
|
354
|
+
yield* lockReplicationLifecycle(sql);
|
|
355
|
+
const [current] = yield* sql.unsafe(`SELECT e.state,e.operation_id::text,r.active_generation::text,r.destination_repair_pending
|
|
356
|
+
FROM _ontology_replication_epochs e
|
|
357
|
+
CROSS JOIN _ontology_replication r WHERE e.id=$1::uuid AND r.id FOR UPDATE OF e`, [epoch.id]);
|
|
358
|
+
if (current?.state === "serving") {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (!current ||
|
|
362
|
+
current.destination_repair_pending ||
|
|
363
|
+
current.state !== "candidate" ||
|
|
364
|
+
current.operation_id !== epoch.operation_id ||
|
|
365
|
+
current.active_generation !== epoch.id) {
|
|
366
|
+
return yield* new ReplicationBaselineFailed({
|
|
367
|
+
message: "replication_publication_superseded",
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
// An external switch has already committed. Adopt its immutable epoch even after a lost local
|
|
371
|
+
// completion, before considering any newer desired state. Never revert the warehouse here.
|
|
372
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET state='retired',retired_at=now()
|
|
373
|
+
WHERE state='serving' AND id<>$1::uuid`, [epoch.id]);
|
|
374
|
+
yield* sql.unsafe("UPDATE _ontology_replication_epochs SET retired_at=now() WHERE state='retired' AND retired_at IS NULL");
|
|
375
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET state='serving',owner=NULL,owner_until=NULL
|
|
376
|
+
WHERE id=$1::uuid AND operation_id=$2::uuid`, [epoch.id, epoch.operation_id]);
|
|
377
|
+
yield* sql.unsafe(`UPDATE _ontology_replication SET serving_epoch=$1::uuid,destination_rebuilding=false,
|
|
378
|
+
destination_repair_pending=false
|
|
379
|
+
WHERE id AND source_id=$2::uuid`, [epoch.id, epoch.source_id]);
|
|
380
|
+
const generations = [...new Set(epoch.routes.map((route) => route.target.generation))];
|
|
381
|
+
for (const generation of generations) {
|
|
382
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_targets SET capture_scopes=$2::jsonb
|
|
383
|
+
WHERE generation=$1::uuid`, [
|
|
384
|
+
generation,
|
|
385
|
+
JSON.stringify(epoch.routes
|
|
386
|
+
.filter((route) => route.target.generation === generation)
|
|
387
|
+
.map(({ entityId, kind }) => ({ entityId, kind }))),
|
|
388
|
+
]);
|
|
389
|
+
}
|
|
390
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_targets SET capturing=false
|
|
391
|
+
WHERE NOT (generation IN (SELECT value::uuid FROM jsonb_array_elements_text($1::jsonb)))`, [JSON.stringify(generations)]);
|
|
392
|
+
}));
|
|
393
|
+
export const pendingShadowSegment = (sql, epoch) => sql
|
|
394
|
+
.unsafe(`SELECT ${segmentFields} FROM _ontology_replication_segments
|
|
395
|
+
WHERE epoch=$1::uuid AND state<>'committed' ORDER BY created_at,id LIMIT 1`, [epoch])
|
|
396
|
+
.pipe(Effect.map((rows) => rows[0]));
|
|
397
|
+
/** Source events, immutable object names and job identities commit before the first upload. */
|
|
398
|
+
export const journalShadowSegment = (sql, input) => sql.withTransaction(Effect.gen(function* () {
|
|
399
|
+
const id = randomUUID();
|
|
400
|
+
const events = input.events.map((item) => item.event);
|
|
401
|
+
if (!events.length) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
const digest = createHash("sha256").update(JSON.stringify(events)).digest("hex");
|
|
405
|
+
const through = input.events
|
|
406
|
+
.reduce((maximum, item) => BigInt(item.event.revision) > maximum ? BigInt(item.event.revision) : maximum, 0n)
|
|
407
|
+
.toString();
|
|
408
|
+
const [segment] = yield* sql.unsafe(`INSERT INTO _ontology_replication_segments
|
|
409
|
+
(id,epoch,generation,source_id,through_revision,events,event_ids,digest,object_uri,job_id)
|
|
410
|
+
SELECT $1::uuid,$2::uuid,$3::uuid,$4::uuid,$5::bigint,$6::jsonb,$7::jsonb,$8,$9,$10
|
|
411
|
+
WHERE EXISTS(SELECT 1 FROM _ontology_replication_epochs WHERE id=$2::uuid
|
|
412
|
+
AND owner=$11::uuid AND owner_until>now() AND state='candidate') RETURNING ${segmentFields}`, [
|
|
413
|
+
id,
|
|
414
|
+
input.epoch.id,
|
|
415
|
+
events[0].generation,
|
|
416
|
+
input.epoch.source_id,
|
|
417
|
+
through,
|
|
418
|
+
JSON.stringify(events),
|
|
419
|
+
JSON.stringify(input.events.map((item) => item.historyId)),
|
|
420
|
+
digest,
|
|
421
|
+
`gs://${input.projectId}-staging/ontology-replication/${input.epoch.source_id}/${id}-${digest}.ndjson`,
|
|
422
|
+
`ontology_load_${id.replaceAll("-", "")}_0`,
|
|
423
|
+
input.owner,
|
|
424
|
+
]);
|
|
425
|
+
if (!segment) {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_delivery SET segment_id=$3::uuid
|
|
429
|
+
WHERE generation=$1::uuid AND event_id IN (SELECT value::uuid FROM jsonb_array_elements_text($2::jsonb))
|
|
430
|
+
AND committed_at IS NULL`, [segment.generation, JSON.stringify(segment.event_ids), id]);
|
|
431
|
+
return segment;
|
|
432
|
+
}));
|
|
433
|
+
export const finishShadowSegment = (sql, segment, owner) => sql.withTransaction(Effect.gen(function* () {
|
|
434
|
+
const [completed] = yield* sql.unsafe(`UPDATE _ontology_replication_segments SET state='committed',committed_at=now()
|
|
435
|
+
WHERE id=$1::uuid AND job_id=$2 AND EXISTS(SELECT 1 FROM _ontology_replication_epochs
|
|
436
|
+
WHERE id=$3::uuid AND owner=$4::uuid AND owner_until>now() AND state='candidate') RETURNING id`, [segment.id, segment.job_id, segment.epoch, owner]);
|
|
437
|
+
if (!completed) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_delivery SET committed_at=now()
|
|
441
|
+
WHERE segment_id=$1::uuid`, [segment.id]);
|
|
442
|
+
return true;
|
|
443
|
+
}));
|
|
444
|
+
export const retryShadowSegment = (sql, segment, owner, failure) => sql.unsafe(`UPDATE _ontology_replication_segments SET attempt=attempt+1,
|
|
445
|
+
retry_after=now()+make_interval(secs=>least(3600,30*power(2,least(attempt,7)))::integer),
|
|
446
|
+
job_id='ontology_load_' || replace(id::text,'-','') || '_' || (attempt+1)::text,failure=$5::jsonb
|
|
447
|
+
WHERE id=$1::uuid AND job_id=$2 AND EXISTS(SELECT 1 FROM _ontology_replication_epochs
|
|
448
|
+
WHERE id=$3::uuid AND owner=$4::uuid AND owner_until>now() AND state='candidate')`, [segment.id, segment.job_id, segment.epoch, owner, JSON.stringify(failure)]);
|
|
449
|
+
/** Recheck references under the lifecycle fence. Audit journals and immutable epoch routes survive. */
|
|
450
|
+
export const retireShadowCapture = (sql) => sql.withTransaction(Effect.gen(function* () {
|
|
451
|
+
yield* lockReplicationLifecycle(sql);
|
|
452
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_targets t SET capturing=false
|
|
453
|
+
WHERE NOT EXISTS(SELECT 1 FROM _ontology_replication_epochs e,
|
|
454
|
+
jsonb_array_elements(e.routes) r WHERE e.state IN ('serving','candidate')
|
|
455
|
+
AND r->'target'->>'generation'=t.generation::text)`);
|
|
456
|
+
}));
|
|
457
|
+
/** Filter by the destination's immutable wire schema, including missing keys and explicit nulls. */
|
|
458
|
+
export function shadowWireEvent(event, bindings, selection) {
|
|
459
|
+
const allowed = selection.find((item) => item.entityId === event.entity_id);
|
|
460
|
+
if (!allowed || event.deleted || event.record_kind !== "entity") {
|
|
461
|
+
return event;
|
|
462
|
+
}
|
|
463
|
+
const source = JSON.parse(event.payload);
|
|
464
|
+
const fields = bindings[event.entity_id] ??
|
|
465
|
+
Object.fromEntries(["id", ...allowed.fields].map((field) => [field, field]));
|
|
466
|
+
return {
|
|
467
|
+
...event,
|
|
468
|
+
payload: JSON.stringify(Object.fromEntries(Object.entries(fields)
|
|
469
|
+
.filter(([, key]) => Object.hasOwn(source, key))
|
|
470
|
+
.map(([wire, key]) => [wire, source[key]]))),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/** Capture is the union needed by live destinations; export shaping remains destination-specific. */
|
|
474
|
+
export const refreshShadowCapturePolicy = (sql) => sql.unsafe(`UPDATE _ontology_replication SET selection=COALESCE((
|
|
475
|
+
SELECT jsonb_object_agg(entity_id,
|
|
476
|
+
jsonb_build_object('entityId',entity_id,'fields',fields,'relationships',relationships))
|
|
477
|
+
FROM (
|
|
478
|
+
SELECT entity_id,
|
|
479
|
+
(SELECT COALESCE(jsonb_agg(DISTINCT value),'[]'::jsonb) FROM jsonb_array_elements(all_fields) value) AS fields,
|
|
480
|
+
(SELECT COALESCE(jsonb_agg(DISTINCT value),'[]'::jsonb)
|
|
481
|
+
FROM jsonb_array_elements(all_relationships) value) AS relationships
|
|
482
|
+
FROM (
|
|
483
|
+
SELECT binding.key AS entity_id,jsonb_agg(DISTINCT field.value) AS all_fields,
|
|
484
|
+
COALESCE((SELECT jsonb_agg(DISTINCT substring(scope->>'kind' FROM 6))
|
|
485
|
+
FILTER (WHERE scope->>'kind' LIKE 'edge:%')
|
|
486
|
+
FROM _ontology_replication_targets other,jsonb_array_elements(other.capture_scopes) scope
|
|
487
|
+
WHERE other.capturing AND scope->>'entityId'=binding.key),'[]'::jsonb) AS all_relationships
|
|
488
|
+
FROM _ontology_replication_targets t,jsonb_each(t.capture_bindings) binding,
|
|
489
|
+
jsonb_each_text(binding.value) field WHERE t.capturing
|
|
490
|
+
AND t.source_id=(SELECT source_id FROM _ontology_replication WHERE id)
|
|
491
|
+
AND NOT EXISTS (
|
|
492
|
+
SELECT 1 FROM _ontology_replication state,jsonb_array_elements(state.exclusions) exclusion
|
|
493
|
+
WHERE exclusion->>'entityId'=binding.key AND
|
|
494
|
+
(COALESCE((exclusion->>'disabled')::boolean,false) OR exclusion->'fields' ? field.value))
|
|
495
|
+
GROUP BY binding.key
|
|
496
|
+
) union_fields
|
|
497
|
+
) selections),'{}'::jsonb) WHERE id`);
|
|
498
|
+
/** Only generations retired for seven days qualify; creation time is never a query-safety fence. */
|
|
499
|
+
export const retiredShadowGenerations = (sql) => sql.withTransaction(Effect.gen(function* () {
|
|
500
|
+
yield* lockReplicationLifecycle(sql);
|
|
501
|
+
return yield* sql.unsafe(`SELECT t.generation::text,t.source_id::text
|
|
502
|
+
FROM _ontology_replication_targets t WHERE NOT t.capturing
|
|
503
|
+
AND t.source_id=(SELECT source_id FROM _ontology_replication WHERE id)
|
|
504
|
+
AND t.created_at<now()-interval '31 days'
|
|
505
|
+
AND NOT EXISTS (SELECT 1 FROM _ontology_replication_epochs e,jsonb_array_elements(e.routes) route
|
|
506
|
+
WHERE route->'target'->>'generation'=t.generation::text AND
|
|
507
|
+
(e.state IN ('serving','candidate') OR e.retired_at>=now()-interval '7 days'))
|
|
508
|
+
AND NOT EXISTS(SELECT 1 FROM _ontology_replication_segments segment
|
|
509
|
+
JOIN _ontology_replication_epochs epoch ON epoch.id=segment.epoch
|
|
510
|
+
WHERE segment.generation=t.generation AND (epoch.state='candidate' OR segment.state='pending'))
|
|
511
|
+
ORDER BY t.generation LIMIT 20`);
|
|
512
|
+
}));
|
|
513
|
+
/** The destination has already fenced and deselected an observably destroyed event table. */
|
|
514
|
+
export const replaceDestroyedShadowDestination = (sql, input) => sql.withTransaction(Effect.gen(function* () {
|
|
515
|
+
yield* lockReplicationLifecycle(sql);
|
|
516
|
+
const [current] = yield* sql.unsafe("SELECT active_generation::text,source_id::text FROM _ontology_replication WHERE id AND enabled");
|
|
517
|
+
if (current?.active_generation !== input.target.generation ||
|
|
518
|
+
current.source_id !== input.target.source_id) {
|
|
519
|
+
return yield* new ReplicationBaselineFailed({
|
|
520
|
+
message: "replication_destination_repair_superseded",
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
yield* sql.unsafe(`SELECT setval('_ontology_replication_cutover_fence',GREATEST(
|
|
524
|
+
(SELECT last_value FROM _ontology_replication_cutover_fence),$1::bigint),true)`, [input.fence]);
|
|
525
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET state=CASE WHEN state='serving'
|
|
526
|
+
THEN 'retired' ELSE 'cancelled' END,retired_at=now(),token=NULL,owner=NULL,owner_until=NULL
|
|
527
|
+
WHERE state IN ('serving','candidate')`);
|
|
528
|
+
yield* sql.unsafe(`UPDATE _ontology_replication SET serving_epoch=NULL,destination_rebuilding=true,
|
|
529
|
+
destination_repair_pending=false WHERE id`);
|
|
530
|
+
yield* sql.unsafe("UPDATE _ontology_replication_targets SET capturing=false");
|
|
531
|
+
return yield* prepareShadowEpoch(sql, {
|
|
532
|
+
target: { ...input.target, generation: randomUUID() },
|
|
533
|
+
affected: replicationScanScopes(input.target),
|
|
534
|
+
});
|
|
535
|
+
}));
|
|
536
|
+
export const installShadowEpoch = (sql, id, owner) => sql
|
|
537
|
+
.unsafe(`UPDATE _ontology_replication_epochs SET installed=true
|
|
538
|
+
WHERE id=$1::uuid AND owner=$2::uuid AND owner_until>now() AND state='candidate'
|
|
539
|
+
AND id=(SELECT active_generation FROM _ontology_replication WHERE id AND enabled) RETURNING id`, [id, owner])
|
|
540
|
+
.pipe(Effect.map((rows) => rows.length === 1));
|
|
541
|
+
/** Strict warehouse job completion, not Pub/Sub acceptance, is the publication prerequisite. */
|
|
542
|
+
export const shadowPublicationReady = (sql, epoch, owner) => sql
|
|
543
|
+
.unsafe(`SELECT EXISTS(SELECT 1 FROM _ontology_replication_epochs e
|
|
544
|
+
WHERE id=$1::uuid AND owner=$2::uuid AND owner_until>now() AND state='candidate' AND installed
|
|
545
|
+
AND NOT cancel_requested
|
|
546
|
+
AND token=$3::uuid AND boundary=$4::bigint AND handoff_until>now()
|
|
547
|
+
AND id=(SELECT active_generation FROM _ontology_replication
|
|
548
|
+
WHERE id AND enabled AND NOT destination_repair_pending)
|
|
549
|
+
AND NOT EXISTS(SELECT 1 FROM _ontology_replication_scans WHERE generation=e.id AND NOT complete)
|
|
550
|
+
AND NOT EXISTS(SELECT 1 FROM _ontology_replication_delivery WHERE generation=e.id
|
|
551
|
+
AND revision<=e.boundary AND committed_at IS NULL AND suppressed_at IS NULL)) AS ready`, [epoch.id, owner, epoch.token, epoch.boundary])
|
|
552
|
+
.pipe(Effect.map((rows) => rows[0].ready));
|
|
553
|
+
export const unfinishedRetiredSegments = (sql) => sql.unsafe(`SELECT ${segmentFields} FROM _ontology_replication_segments
|
|
554
|
+
WHERE state='pending' AND created_at<now()-interval '7 days' AND epoch IN (
|
|
555
|
+
SELECT id FROM _ontology_replication_epochs WHERE state IN ('retired','cancelled'))
|
|
556
|
+
ORDER BY created_at,id LIMIT 10`);
|
|
557
|
+
export const settleRetiredSegment = (sql, segment, committed) => sql.unsafe(`UPDATE _ontology_replication_segments SET state=$3,
|
|
558
|
+
committed_at=CASE WHEN $3='committed' THEN now() ELSE committed_at END
|
|
559
|
+
WHERE id=$1::uuid AND job_id=$2 AND state='pending' AND epoch IN (
|
|
560
|
+
SELECT id FROM _ontology_replication_epochs WHERE state IN ('retired','cancelled'))`, [segment.id, segment.job_id, committed ? "committed" : "failed"]);
|
|
561
|
+
/** Restriction-only refresh is independent of build leases and cannot confer publication eligibility. */
|
|
562
|
+
export const shadowPolicyViewOwnership = (sql, generation, source, exclusions) => sql
|
|
563
|
+
.unsafe(`SELECT EXISTS(SELECT 1 FROM _ontology_replication
|
|
564
|
+
WHERE id AND enabled AND NOT destination_repair_pending AND active_generation=$1::uuid
|
|
565
|
+
AND source_id=$2::uuid AND exclusions=$3::jsonb) AS valid`, [generation, source, JSON.stringify(exclusions)])
|
|
566
|
+
.pipe(Effect.map((rows) => rows[0].valid));
|
|
567
|
+
export const recordShadowPolicyViews = (sql, generation, source, exclusions) => sql.unsafe(`UPDATE _ontology_replication_epochs SET policy_installed=true WHERE id=$1::uuid
|
|
568
|
+
AND EXISTS(SELECT 1 FROM _ontology_replication WHERE id AND enabled AND NOT destination_repair_pending
|
|
569
|
+
AND active_generation=$1::uuid AND source_id=$2::uuid AND exclusions=$3::jsonb)`, [generation, source, JSON.stringify(exclusions)]);
|
|
570
|
+
/** Used after reading a destination view's etag, before its conditional DDL update. */
|
|
571
|
+
export const shadowViewOwnership = (sql, id) => sql
|
|
572
|
+
.unsafe(`SELECT EXISTS(SELECT 1 FROM _ontology_replication_epochs e
|
|
573
|
+
WHERE e.id=$1::uuid
|
|
574
|
+
AND EXISTS(SELECT 1 FROM _ontology_replication WHERE id AND NOT destination_repair_pending)
|
|
575
|
+
AND (
|
|
576
|
+
(e.state='candidate' AND NOT e.cancel_requested
|
|
577
|
+
AND e.id=(SELECT active_generation FROM _ontology_replication WHERE id AND enabled))
|
|
578
|
+
OR (e.state='serving' AND e.id=(SELECT serving_epoch FROM _ontology_replication WHERE id AND enabled)
|
|
579
|
+
AND NOT EXISTS(SELECT 1 FROM _ontology_replication_epochs WHERE state='candidate')))) AS valid`, [id])
|
|
580
|
+
.pipe(Effect.map((rows) => rows[0].valid));
|
|
581
|
+
/** A finite background goal prevents continuous writes from moving initialization's finish line. */
|
|
582
|
+
export const closeShadowCatchup = (sql, id, owner) => sql.withTransaction(Effect.gen(function* () {
|
|
583
|
+
yield* sql.unsafe("SET LOCAL lock_timeout='5s'");
|
|
584
|
+
yield* lockReplicationLifecycle(sql);
|
|
585
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET catchup_boundary=(
|
|
586
|
+
SELECT CASE WHEN is_called THEN last_value ELSE 0 END FROM _ontology_replication_revision)
|
|
587
|
+
WHERE id=$1::uuid AND owner=$2::uuid AND owner_until>now() AND state='candidate'
|
|
588
|
+
AND catchup_boundary IS NULL AND installed=false
|
|
589
|
+
AND id=(SELECT active_generation FROM _ontology_replication WHERE id AND enabled)
|
|
590
|
+
AND NOT EXISTS(SELECT 1 FROM _ontology_replication_scans WHERE generation=$1::uuid AND NOT complete)`, [id, owner]);
|
|
591
|
+
}));
|
|
592
|
+
/** Persist cancellation before returning a retryable migration decision; provider fencing follows. */
|
|
593
|
+
export const requestShadowSupersession = (sql, manifestHash) => sql.withTransaction(Effect.gen(function* () {
|
|
594
|
+
yield* sql.unsafe("SET LOCAL lock_timeout='5s'");
|
|
595
|
+
yield* sql.unsafe("SET LOCAL statement_timeout='30s'");
|
|
596
|
+
yield* lockReplicationLifecycle(sql);
|
|
597
|
+
const rows = yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET cancel_requested=true
|
|
598
|
+
WHERE state='candidate' AND (token IS NOT NULL OR cancel_requested)
|
|
599
|
+
AND target->'manifest'->>'hash'<>$1 RETURNING id`, [manifestHash]);
|
|
600
|
+
if (rows.length) {
|
|
601
|
+
yield* sql.unsafe(`UPDATE _ontology_replication SET requested_manifest_hash=$1,
|
|
602
|
+
requested_manifest_until=now()+interval '5 minutes' WHERE id`, [manifestHash]);
|
|
603
|
+
}
|
|
604
|
+
return rows.length > 0;
|
|
605
|
+
}));
|
|
606
|
+
/** The destination token has been invalidated, or no token ever existed. */
|
|
607
|
+
export const cancelShadowEpoch = (sql, epoch) => sql.withTransaction(Effect.gen(function* () {
|
|
608
|
+
yield* lockReplicationLifecycle(sql);
|
|
609
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_epochs SET state='cancelled',owner=NULL,
|
|
610
|
+
owner_until=NULL,token=NULL,boundary=NULL,handoff_until=NULL
|
|
611
|
+
WHERE id=$1::uuid AND state='candidate' AND cancel_requested`, [epoch.id]);
|
|
612
|
+
yield* sql.unsafe(`UPDATE _ontology_replication_scans SET complete=true,required=false
|
|
613
|
+
WHERE generation=$1::uuid`, [epoch.id]);
|
|
614
|
+
}));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SourcePublicationProof } from "./model.js";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import type { SqlClient } from "effect/unstable/sql";
|
|
4
|
+
import { ReplicationBaselineFailed } from "./baseline.js";
|
|
5
|
+
export interface SourceReplacementSelector {
|
|
6
|
+
readonly sourceId: string;
|
|
7
|
+
readonly operationId: string | null;
|
|
8
|
+
readonly epoch: string | null;
|
|
9
|
+
readonly token: string | null;
|
|
10
|
+
readonly fence: string;
|
|
11
|
+
readonly incarnation: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ShadowSourceReplacement {
|
|
14
|
+
readonly source_id: string;
|
|
15
|
+
readonly operation_id: string;
|
|
16
|
+
readonly expected: SourceReplacementSelector;
|
|
17
|
+
readonly proof: SourcePublicationProof | null;
|
|
18
|
+
readonly published: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const pendingSourceReplacement: (sql: SqlClient.SqlClient) => Effect.Effect<ShadowSourceReplacement, import("effect/unstable/sql/SqlError").SqlError, never>;
|
|
21
|
+
/** Only the trusted product publication can justify replacing an otherwise healthy old source. */
|
|
22
|
+
export declare const prepareSourceReplacement: (sql: SqlClient.SqlClient, input: {
|
|
23
|
+
readonly sourceId: string;
|
|
24
|
+
readonly published: string;
|
|
25
|
+
readonly observed: SourceReplacementSelector;
|
|
26
|
+
readonly proof: SourcePublicationProof;
|
|
27
|
+
readonly projectId: string;
|
|
28
|
+
readonly workspaceId: string;
|
|
29
|
+
}) => Effect.Effect<ShadowSourceReplacement, import("effect/unstable/sql/SqlError").SqlError | ReplicationBaselineFailed, never>;
|
|
30
|
+
/** A confirmed no-op CAS may race an old-source handoff; retain its receipt before a new attempt. */
|
|
31
|
+
export declare const retrySourceReplacement: (sql: SqlClient.SqlClient, intent: ShadowSourceReplacement, observed: SourceReplacementSelector) => Effect.Effect<undefined, import("effect/unstable/sql/SqlError").SqlError | ReplicationBaselineFailed, never>;
|
|
32
|
+
/** Upgrade only an already admitted old journal; the provider evidence is read before this transaction. */
|
|
33
|
+
export declare const recordSourcePublicationProof: (sql: SqlClient.SqlClient, intent: ShadowSourceReplacement, proof: SourcePublicationProof) => Effect.Effect<undefined, import("effect/unstable/sql/SqlError").SqlError | ReplicationBaselineFailed, never>;
|