pipework 0.251.0 → 0.252.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/CHANGELOG.md +11 -0
- package/dist/core/config/load.d.ts +2 -0
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +1 -0
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +2 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/surface/types.d.ts +1 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/data/db/namespace.d.ts +3 -0
- package/dist/data/db/namespace.d.ts.map +1 -1
- package/dist/data/db/namespace.js +3 -0
- package/dist/data/db/namespace.js.map +1 -1
- package/dist/data/db/pool.d.ts.map +1 -1
- package/dist/data/db/pool.js +6 -1
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/query-label.d.ts +5 -0
- package/dist/data/db/query-label.d.ts.map +1 -0
- package/dist/data/db/query-label.js +11 -0
- package/dist/data/db/query-label.js.map +1 -0
- package/dist/data/db/query-watch.d.ts +21 -0
- package/dist/data/db/query-watch.d.ts.map +1 -0
- package/dist/data/db/query-watch.js +90 -0
- package/dist/data/db/query-watch.js.map +1 -0
- package/dist/data/query/postgres-js/driver.js +1 -1
- package/dist/data/query/postgres-js/driver.js.map +1 -1
- package/dist/data/query/postgres-js/session.d.ts +5 -1
- package/dist/data/query/postgres-js/session.d.ts.map +1 -1
- package/dist/data/query/postgres-js/session.js +16 -11
- package/dist/data/query/postgres-js/session.js.map +1 -1
- package/dist/data/query/utils.d.ts +2 -0
- package/dist/data/query/utils.d.ts.map +1 -1
- package/dist/data/query/utils.js.map +1 -1
- package/dist/infra/credits/schema.d.ts +1 -1
- package/dist/test/plugin.d.ts.map +1 -1
- package/dist/test/plugin.js +10 -4
- package/dist/test/plugin.js.map +1 -1
- package/package.json +4 -3
- package/src/async/jobs/abortable-delay.ts +18 -0
- package/src/async/jobs/claims.ts +52 -0
- package/src/async/jobs/cron.ts +179 -0
- package/src/async/jobs/errors.ts +56 -0
- package/src/async/jobs/exclusion.ts +117 -0
- package/src/async/jobs/execute.ts +219 -0
- package/src/async/jobs/index.ts +10 -0
- package/src/async/jobs/listener.ts +135 -0
- package/src/async/jobs/namespace.ts +20 -0
- package/src/async/jobs/queue-door.ts +44 -0
- package/src/async/jobs/queue.ts +1012 -0
- package/src/async/jobs/run-script.ts +67 -0
- package/src/async/jobs/scheduler.ts +218 -0
- package/src/async/jobs/schedules-table.ts +37 -0
- package/src/async/jobs/serialize-error.ts +48 -0
- package/src/async/jobs/table.ts +191 -0
- package/src/async/pipeline/define.ts +53 -0
- package/src/async/pipeline/errors.ts +35 -0
- package/src/async/pipeline/execute.ts +53 -0
- package/src/async/pipeline/index.ts +13 -0
- package/src/async/pipeline/namespace.ts +12 -0
- package/src/async/pipeline/resume.ts +67 -0
- package/src/async/pipeline/run-steps.ts +98 -0
- package/src/async/pipeline/types.ts +57 -0
- package/src/async/state-machine/define.ts +64 -0
- package/src/async/state-machine/errors.ts +29 -0
- package/src/async/state-machine/index.ts +3 -0
- package/src/async/state-machine/namespace.ts +12 -0
- package/src/async/state-machine/transition.ts +90 -0
- package/src/async/state-machine/types.ts +37 -0
- package/src/async/steps/errors.ts +31 -0
- package/src/async/steps/graph.ts +1214 -0
- package/src/async/steps/hash.ts +40 -0
- package/src/async/steps/index.ts +7 -0
- package/src/async/steps/namespace.ts +7 -0
- package/src/async/steps/table.ts +97 -0
- package/src/async/steps/types.ts +318 -0
- package/src/audit/chain/index.ts +9 -0
- package/src/audit/chain/install.ts +113 -0
- package/src/audit/chain/retire.ts +52 -0
- package/src/audit/chain/verify.ts +257 -0
- package/src/audit/query/cursor.ts +37 -0
- package/src/audit/query/index.ts +9 -0
- package/src/audit/query/query.ts +223 -0
- package/src/audit/schema/audit-record-fields.ts +68 -0
- package/src/audit/schema/audit-record.ts +23 -0
- package/src/auth/auth/audit.ts +31 -0
- package/src/auth/auth/chain.ts +84 -0
- package/src/auth/auth/cookie.ts +136 -0
- package/src/auth/auth/email-links.ts +311 -0
- package/src/auth/auth/guard.ts +128 -0
- package/src/auth/auth/index.ts +18 -0
- package/src/auth/auth/multi-org.ts +337 -0
- package/src/auth/auth/namespace.ts +34 -0
- package/src/auth/auth/parse-expiry.ts +13 -0
- package/src/auth/auth/passwords.ts +110 -0
- package/src/auth/auth/schema.ts +110 -0
- package/src/auth/auth/sessions.ts +565 -0
- package/src/auth/auth/sign-in.ts +156 -0
- package/src/auth/auth/signing-keys.ts +69 -0
- package/src/auth/auth/types.ts +26 -0
- package/src/auth/auth/uuid.ts +10 -0
- package/src/auth/rbac/cache.ts +55 -0
- package/src/auth/rbac/check.ts +30 -0
- package/src/auth/rbac/create.ts +35 -0
- package/src/auth/rbac/index.ts +5 -0
- package/src/auth/rbac/middleware.ts +20 -0
- package/src/auth/rbac/namespace.ts +15 -0
- package/src/auth/rbac/resolve.ts +31 -0
- package/src/auth/rbac/schema.sql +31 -0
- package/src/auth/rbac/seed.ts +67 -0
- package/src/auth/rbac/tables.ts +32 -0
- package/src/auth/rbac/types.ts +65 -0
- package/src/auth/tenant/extract.ts +33 -0
- package/src/auth/tenant/in-tenant.ts +49 -0
- package/src/auth/tenant/index.ts +7 -0
- package/src/auth/tenant/namespace.ts +23 -0
- package/src/auth/tenant/propagate.ts +20 -0
- package/src/auth/tenant/rls.ts +120 -0
- package/src/auth/tenant/scope.ts +33 -0
- package/src/auth/tenant/scoped-db.ts +581 -0
- package/src/auth/tenant/types.ts +14 -0
- package/src/auth/tenant/validate.ts +27 -0
- package/src/cli/commands/add.ts +158 -0
- package/src/cli/commands/alerts.ts +98 -0
- package/src/cli/commands/check.ts +1418 -0
- package/src/cli/commands/codemod.ts +78 -0
- package/src/cli/commands/db.ts +356 -0
- package/src/cli/commands/dev.ts +101 -0
- package/src/cli/commands/generate.ts +75 -0
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/install.ts +28 -0
- package/src/cli/commands/lint.ts +62 -0
- package/src/cli/commands/migrate.ts +132 -0
- package/src/cli/commands/openapi.ts +63 -0
- package/src/cli/commands/remove.ts +72 -0
- package/src/cli/commands/run.ts +50 -0
- package/src/cli/commands/scope.ts +54 -0
- package/src/cli/commands/serve.ts +68 -0
- package/src/cli/commands/shard.ts +31 -0
- package/src/cli/commands/shared/config-pipework-version.ts +28 -0
- package/src/cli/commands/shared/find-config.ts +22 -0
- package/src/cli/commands/shared/load-config.ts +35 -0
- package/src/cli/commands/shared/pnpm-install.ts +95 -0
- package/src/cli/commands/shared/report-removed.ts +8 -0
- package/src/cli/commands/sync.ts +32 -0
- package/src/cli/commands/test.ts +307 -0
- package/src/cli/commands/upgrade.ts +48 -0
- package/src/cli/dev-bootstrap.ts +24 -0
- package/src/cli/errors.ts +10 -0
- package/src/cli/index.ts +303 -0
- package/src/cli/output.ts +17 -0
- package/src/cli/port.ts +15 -0
- package/src/cli/surface-kinds.ts +20 -0
- package/src/client/index.ts +4 -0
- package/src/client/poll.ts +130 -0
- package/src/client/report-fault.ts +119 -0
- package/src/core/config/app-pool.ts +19 -0
- package/src/core/config/aws-env.ts +29 -0
- package/src/core/config/base-dir.ts +24 -0
- package/src/core/config/discover.ts +64 -0
- package/src/core/config/env-resolve.ts +87 -0
- package/src/core/config/env-types.ts +116 -0
- package/src/core/config/env.ts +55 -0
- package/src/core/config/errors.ts +9 -0
- package/src/core/config/import-config.ts +40 -0
- package/src/core/config/index.ts +8 -0
- package/src/core/config/load.ts +417 -0
- package/src/core/config/namespace.ts +14 -0
- package/src/core/config/resolve.ts +195 -0
- package/src/core/config/schema.ts +488 -0
- package/src/core/config/statement-observer.ts +48 -0
- package/src/core/config/ts-inline-hooks.ts +75 -0
- package/src/core/config/ts-register.ts +4 -0
- package/src/core/config/ts-resolve-hooks.ts +101 -0
- package/src/core/errors/base.ts +49 -0
- package/src/core/errors/http.ts +146 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/namespace.ts +31 -0
- package/src/core/lifecycle/drain.ts +33 -0
- package/src/core/lifecycle/health.ts +72 -0
- package/src/core/lifecycle/index.ts +4 -0
- package/src/core/lifecycle/namespace.ts +7 -0
- package/src/core/lifecycle/startup.ts +102 -0
- package/src/core/lifecycle/types.ts +36 -0
- package/src/core/logging/context.ts +60 -0
- package/src/core/logging/error-chain.ts +68 -0
- package/src/core/logging/index.ts +5 -0
- package/src/core/logging/logger.ts +65 -0
- package/src/core/logging/namespace.ts +16 -0
- package/src/core/logging/proxy.ts +22 -0
- package/src/core/logging/redact-match.ts +24 -0
- package/src/core/logging/types.ts +47 -0
- package/src/core/pipework.ts +566 -0
- package/src/core/runtime/broker.ts +69 -0
- package/src/core/surface/http.ts +133 -0
- package/src/core/surface/index.ts +11 -0
- package/src/core/surface/job-timeout.ts +40 -0
- package/src/core/surface/namespace.ts +13 -0
- package/src/core/surface/script.ts +33 -0
- package/src/core/surface/start.ts +142 -0
- package/src/core/surface/types.ts +225 -0
- package/src/core/surface/validate.ts +88 -0
- package/src/core/surface/worker.ts +528 -0
- package/src/core/validation/connectivity.ts +49 -0
- package/src/core/validation/domain.ts +99 -0
- package/src/core/validation/index.ts +5 -0
- package/src/core/validation/queue-tables.ts +312 -0
- package/src/core/validation/rls-role.ts +138 -0
- package/src/core/validation/startup.ts +51 -0
- package/src/core/validation/surfaces.ts +65 -0
- package/src/core/validation/types.ts +25 -0
- package/src/data/db/advisory-lock.ts +55 -0
- package/src/data/db/aggregates.ts +172 -0
- package/src/data/db/array-elements.ts +111 -0
- package/src/data/db/bulk-set.ts +53 -0
- package/src/data/db/col/index.ts +14 -0
- package/src/data/db/composed.ts +53 -0
- package/src/data/db/db.ts +276 -0
- package/src/data/db/excluded.ts +16 -0
- package/src/data/db/expressions.ts +473 -0
- package/src/data/db/filter.ts +90 -0
- package/src/data/db/function-source.ts +114 -0
- package/src/data/db/identifiers.ts +16 -0
- package/src/data/db/idx/index.ts +9 -0
- package/src/data/db/in-list.ts +49 -0
- package/src/data/db/index.ts +9 -0
- package/src/data/db/insert-split.ts +137 -0
- package/src/data/db/instrumentation.ts +82 -0
- package/src/data/db/maintenance.ts +28 -0
- package/src/data/db/namespace.ts +107 -0
- package/src/data/db/owner-labels.ts +9 -0
- package/src/data/db/pool.ts +187 -0
- package/src/data/db/query-label.ts +13 -0
- package/src/data/db/query-watch.ts +116 -0
- package/src/data/db/raw-client-warning.ts +37 -0
- package/src/data/db/readonly.ts +23 -0
- package/src/data/db/reservation.ts +151 -0
- package/src/data/db/schema-features.ts +14 -0
- package/src/data/db/serializable.ts +83 -0
- package/src/data/db/sql.ts +1 -0
- package/src/data/db/timeouts.ts +76 -0
- package/src/data/db/types.ts +18 -0
- package/src/data/db/unbound.ts +33 -0
- package/src/data/db/values.ts +29 -0
- package/src/data/domain/brand.ts +15 -0
- package/src/data/domain/build-factory.ts +193 -0
- package/src/data/domain/build-schema.ts +161 -0
- package/src/data/domain/build-table.ts +355 -0
- package/src/data/domain/define.ts +239 -0
- package/src/data/domain/external-registry.ts +34 -0
- package/src/data/domain/external.ts +77 -0
- package/src/data/domain/field.ts +279 -0
- package/src/data/domain/index.ts +13 -0
- package/src/data/domain/naming.ts +6 -0
- package/src/data/domain/projections.ts +57 -0
- package/src/data/domain/trace-safe-projection.ts +31 -0
- package/src/data/domain/traits.ts +108 -0
- package/src/data/domain/types.ts +289 -0
- package/src/data/jsonb/composed.ts +18 -0
- package/src/data/jsonb/index.ts +1 -0
- package/src/data/jsonb/namespace.ts +30 -0
- package/src/data/jsonb/query.ts +86 -0
- package/src/data/migrate/append-only-generate.ts +47 -0
- package/src/data/migrate/applied.ts +47 -0
- package/src/data/migrate/apply.ts +309 -0
- package/src/data/migrate/check.ts +119 -0
- package/src/data/migrate/diff.ts +357 -0
- package/src/data/migrate/door-generate.ts +130 -0
- package/src/data/migrate/framework-rls.ts +58 -0
- package/src/data/migrate/generate.ts +201 -0
- package/src/data/migrate/history.ts +215 -0
- package/src/data/migrate/index.ts +9 -0
- package/src/data/migrate/internal-definitions.ts +175 -0
- package/src/data/migrate/post-process.ts +73 -0
- package/src/data/migrate/pragma.ts +73 -0
- package/src/data/migrate/provision-framework.ts +147 -0
- package/src/data/migrate/rls-generate.ts +61 -0
- package/src/data/migrate/run.ts +124 -0
- package/src/data/migrate/safety.ts +167 -0
- package/src/data/migrate/snapshot.ts +368 -0
- package/src/data/migrate/sql-emit.ts +392 -0
- package/src/data/migrate/statements.ts +312 -0
- package/src/data/query/alias.ts +126 -0
- package/src/data/query/batch.ts +8 -0
- package/src/data/query/cache/core/cache.ts +78 -0
- package/src/data/query/cache/core/index.ts +1 -0
- package/src/data/query/cache/core/types.ts +29 -0
- package/src/data/query/cache/readme.md +230 -0
- package/src/data/query/casing.ts +76 -0
- package/src/data/query/collation.ts +11 -0
- package/src/data/query/column-builder.ts +370 -0
- package/src/data/query/column.ts +150 -0
- package/src/data/query/entity.ts +42 -0
- package/src/data/query/errors.ts +33 -0
- package/src/data/query/index.ts +14 -0
- package/src/data/query/logger.ts +47 -0
- package/src/data/query/operations.ts +45 -0
- package/src/data/query/pg-core/alias.ts +12 -0
- package/src/data/query/pg-core/checks.ts +32 -0
- package/src/data/query/pg-core/columns/all.ts +71 -0
- package/src/data/query/pg-core/columns/bigint.ts +110 -0
- package/src/data/query/pg-core/columns/bigserial.ts +128 -0
- package/src/data/query/pg-core/columns/boolean.ts +43 -0
- package/src/data/query/pg-core/columns/char.ts +85 -0
- package/src/data/query/pg-core/columns/cidr.ts +43 -0
- package/src/data/query/pg-core/columns/common.ts +366 -0
- package/src/data/query/pg-core/columns/custom.ts +232 -0
- package/src/data/query/pg-core/columns/date.common.ts +15 -0
- package/src/data/query/pg-core/columns/date.ts +112 -0
- package/src/data/query/pg-core/columns/double-precision.ts +55 -0
- package/src/data/query/pg-core/columns/enum.ts +201 -0
- package/src/data/query/pg-core/columns/index.ts +35 -0
- package/src/data/query/pg-core/columns/inet.ts +43 -0
- package/src/data/query/pg-core/columns/int.common.ts +57 -0
- package/src/data/query/pg-core/columns/integer.ts +53 -0
- package/src/data/query/pg-core/columns/interval.ts +83 -0
- package/src/data/query/pg-core/columns/json.ts +58 -0
- package/src/data/query/pg-core/columns/jsonb.ts +56 -0
- package/src/data/query/pg-core/columns/line.ts +117 -0
- package/src/data/query/pg-core/columns/macaddr.ts +43 -0
- package/src/data/query/pg-core/columns/macaddr8.ts +43 -0
- package/src/data/query/pg-core/columns/numeric.ts +227 -0
- package/src/data/query/pg-core/columns/point.ts +127 -0
- package/src/data/query/pg-core/columns/postgis_extension/geometry.ts +119 -0
- package/src/data/query/pg-core/columns/postgis_extension/utils.ts +47 -0
- package/src/data/query/pg-core/columns/real.ts +58 -0
- package/src/data/query/pg-core/columns/serial.ts +55 -0
- package/src/data/query/pg-core/columns/smallint.ts +53 -0
- package/src/data/query/pg-core/columns/smallserial.ts +60 -0
- package/src/data/query/pg-core/columns/text.ts +67 -0
- package/src/data/query/pg-core/columns/time.ts +72 -0
- package/src/data/query/pg-core/columns/timestamp.ts +163 -0
- package/src/data/query/pg-core/columns/uuid.ts +51 -0
- package/src/data/query/pg-core/columns/varchar.ts +89 -0
- package/src/data/query/pg-core/columns/vector_extension/bit.ts +68 -0
- package/src/data/query/pg-core/columns/vector_extension/halfvec.ts +80 -0
- package/src/data/query/pg-core/columns/vector_extension/sparsevec.ts +67 -0
- package/src/data/query/pg-core/columns/vector_extension/vector.ts +80 -0
- package/src/data/query/pg-core/db.ts +674 -0
- package/src/data/query/pg-core/dialect.ts +1404 -0
- package/src/data/query/pg-core/expressions.ts +25 -0
- package/src/data/query/pg-core/foreign-keys.ts +130 -0
- package/src/data/query/pg-core/index.ts +21 -0
- package/src/data/query/pg-core/indexes.ts +276 -0
- package/src/data/query/pg-core/policies.ts +56 -0
- package/src/data/query/pg-core/primary-keys.ts +62 -0
- package/src/data/query/pg-core/query-builders/count.ts +86 -0
- package/src/data/query/pg-core/query-builders/delete.ts +293 -0
- package/src/data/query/pg-core/query-builders/index.ts +7 -0
- package/src/data/query/pg-core/query-builders/insert.ts +452 -0
- package/src/data/query/pg-core/query-builders/query-builder.ts +123 -0
- package/src/data/query/pg-core/query-builders/query.ts +157 -0
- package/src/data/query/pg-core/query-builders/raw.ts +49 -0
- package/src/data/query/pg-core/query-builders/refresh-materialized-view.ts +108 -0
- package/src/data/query/pg-core/query-builders/select.ts +1346 -0
- package/src/data/query/pg-core/query-builders/select.types.ts +437 -0
- package/src/data/query/pg-core/query-builders/update.ts +626 -0
- package/src/data/query/pg-core/roles.ts +41 -0
- package/src/data/query/pg-core/schema.ts +72 -0
- package/src/data/query/pg-core/sequence.ts +41 -0
- package/src/data/query/pg-core/session.ts +292 -0
- package/src/data/query/pg-core/subquery.ts +46 -0
- package/src/data/query/pg-core/table.ts +252 -0
- package/src/data/query/pg-core/unique-constraint.ts +73 -0
- package/src/data/query/pg-core/utils/array.ts +95 -0
- package/src/data/query/pg-core/utils/index.ts +1 -0
- package/src/data/query/pg-core/utils.ts +104 -0
- package/src/data/query/pg-core/view-base.ts +14 -0
- package/src/data/query/pg-core/view-common.ts +1 -0
- package/src/data/query/pg-core/view.ts +435 -0
- package/src/data/query/pg-core/with-builder.ts +41 -0
- package/src/data/query/postgres-js/README.md +46 -0
- package/src/data/query/postgres-js/array-parser.ts +45 -0
- package/src/data/query/postgres-js/driver-error-boundary.ts +149 -0
- package/src/data/query/postgres-js/driver.ts +163 -0
- package/src/data/query/postgres-js/index.ts +2 -0
- package/src/data/query/postgres-js/session.ts +234 -0
- package/src/data/query/primary-key.ts +11 -0
- package/src/data/query/query-builders/query-builder.ts +19 -0
- package/src/data/query/query-builders/select.types.ts +175 -0
- package/src/data/query/query-promise.ts +35 -0
- package/src/data/query/relations.ts +725 -0
- package/src/data/query/runnable-query.ts +12 -0
- package/src/data/query/selection-proxy.ts +121 -0
- package/src/data/query/session.ts +8 -0
- package/src/data/query/sql/expressions/conditions.ts +741 -0
- package/src/data/query/sql/expressions/index.ts +2 -0
- package/src/data/query/sql/expressions/select.ts +45 -0
- package/src/data/query/sql/functions/aggregate.ts +129 -0
- package/src/data/query/sql/functions/index.ts +2 -0
- package/src/data/query/sql/functions/vector.ts +178 -0
- package/src/data/query/sql/index.ts +3 -0
- package/src/data/query/sql/sql.ts +794 -0
- package/src/data/query/subquery.ts +53 -0
- package/src/data/query/table.ts +208 -0
- package/src/data/query/table.utils.ts +2 -0
- package/src/data/query/tracing-utils.ts +3 -0
- package/src/data/query/tracing.ts +11 -0
- package/src/data/query/utils.ts +357 -0
- package/src/data/query/version.ts +4 -0
- package/src/data/query/view-common.ts +1 -0
- package/src/data/schema/branded.ts +13 -0
- package/src/data/schema/generated/coerce.ts +5 -0
- package/src/data/schema/generated/combinator.ts +6 -0
- package/src/data/schema/generated/encoding.ts +5 -0
- package/src/data/schema/generated/error.ts +6 -0
- package/src/data/schema/generated/format.ts +5 -0
- package/src/data/schema/generated/type-constructor.ts +10 -0
- package/src/data/schema/generated/type.ts +6 -0
- package/src/data/schema/index.ts +6 -0
- package/src/data/schema/json-schema.ts +33 -0
- package/src/data/schema/namespace.ts +55 -0
- package/src/data/schema/parse.ts +39 -0
- package/src/data/schema/types.ts +39 -0
- package/src/data/temporal/append-only.ts +134 -0
- package/src/data/temporal/close.ts +27 -0
- package/src/data/temporal/columns.ts +10 -0
- package/src/data/temporal/definition-queries.ts +70 -0
- package/src/data/temporal/errors.ts +10 -0
- package/src/data/temporal/get-current.ts +22 -0
- package/src/data/temporal/index.ts +9 -0
- package/src/data/temporal/namespace.ts +36 -0
- package/src/data/temporal/query.ts +25 -0
- package/src/data/temporal/resolve.ts +17 -0
- package/src/data/temporal/revise.ts +67 -0
- package/src/data/temporal/types.ts +22 -0
- package/src/data/valve/artifact.ts +33 -0
- package/src/data/valve/contract-diff.ts +21 -0
- package/src/data/valve/cross.ts +57 -0
- package/src/data/valve/crossing-ledger.ts +50 -0
- package/src/data/valve/decision.ts +44 -0
- package/src/data/valve/durable-crossing.ts +52 -0
- package/src/data/valve/execute-crossing.ts +77 -0
- package/src/data/valve/faucet.ts +56 -0
- package/src/data/valve/gate.ts +114 -0
- package/src/data/valve/graph.ts +77 -0
- package/src/data/valve/index.ts +53 -0
- package/src/data/valve/namespace.ts +57 -0
- package/src/data/valve/registry.ts +50 -0
- package/src/data/valve/schema/valve-crossing-fields.ts +23 -0
- package/src/data/valve/schema/valve-crossing.ts +9 -0
- package/src/data/valve/supply.ts +154 -0
- package/src/data/valve/types.ts +143 -0
- package/src/data/vector/columns.ts +27 -0
- package/src/data/vector/index.ts +4 -0
- package/src/data/vector/namespace.ts +33 -0
- package/src/data/vector/query.ts +85 -0
- package/src/data/vector/validate.ts +16 -0
- package/src/data/write/audit/actor.ts +15 -0
- package/src/data/write/audit/audit-columns.ts +18 -0
- package/src/data/write/audit/audit-select.ts +111 -0
- package/src/data/write/audit/bound-transaction.ts +14 -0
- package/src/data/write/audit/cte-compile.ts +32 -0
- package/src/data/write/audit/decode-returning.ts +47 -0
- package/src/data/write/audit/final-select.ts +43 -0
- package/src/data/write/audit/infra-audit.ts +36 -0
- package/src/data/write/audit/primary-key.ts +65 -0
- package/src/data/write/audit/projection.ts +41 -0
- package/src/data/write/audit/read-touches.ts +86 -0
- package/src/data/write/audit/touch-match.ts +78 -0
- package/src/data/write/audit/verb-delete.ts +62 -0
- package/src/data/write/audit/verb-insert.ts +36 -0
- package/src/data/write/audit/verb-select.ts +47 -0
- package/src/data/write/audit/verb-update.ts +60 -0
- package/src/data/write/audit/verb-upsert.ts +104 -0
- package/src/data/write/conflict.ts +43 -0
- package/src/data/write/delete.ts +38 -0
- package/src/data/write/execute.ts +243 -0
- package/src/data/write/index.ts +11 -0
- package/src/data/write/insert.ts +63 -0
- package/src/data/write/state.ts +43 -0
- package/src/data/write/update.ts +54 -0
- package/src/data/write/upsert.ts +58 -0
- package/src/data/write/values.ts +19 -0
- package/src/index.ts +230 -0
- package/src/infra/access-links/code.ts +70 -0
- package/src/infra/access-links/handle.ts +892 -0
- package/src/infra/access-links/index.ts +9 -0
- package/src/infra/access-links/namespace.ts +30 -0
- package/src/infra/access-links/person-check.ts +177 -0
- package/src/infra/access-links/schema.ts +71 -0
- package/src/infra/alerts/async-sources.ts +231 -0
- package/src/infra/alerts/audit.ts +36 -0
- package/src/infra/alerts/beat.ts +123 -0
- package/src/infra/alerts/client-faults.ts +111 -0
- package/src/infra/alerts/faults.ts +156 -0
- package/src/infra/alerts/handle.ts +778 -0
- package/src/infra/alerts/http.ts +192 -0
- package/src/infra/alerts/index.ts +12 -0
- package/src/infra/alerts/infra-sources.ts +180 -0
- package/src/infra/alerts/namespace.ts +81 -0
- package/src/infra/alerts/redact.ts +21 -0
- package/src/infra/alerts/request-faults.ts +41 -0
- package/src/infra/alerts/schema.ts +124 -0
- package/src/infra/alerts/sinks.ts +125 -0
- package/src/infra/alerts/source-options.ts +26 -0
- package/src/infra/alerts/sources.ts +503 -0
- package/src/infra/alerts/subsystem-sources.ts +137 -0
- package/src/infra/audit/emit.ts +87 -0
- package/src/infra/audit/index.ts +2 -0
- package/src/infra/audit/namespace.ts +22 -0
- package/src/infra/audit/schema.sql +35 -0
- package/src/infra/audit/types.ts +18 -0
- package/src/infra/aws/credentials.ts +147 -0
- package/src/infra/aws/sigv4.ts +140 -0
- package/src/infra/cache/create.ts +58 -0
- package/src/infra/cache/entry.ts +81 -0
- package/src/infra/cache/index.ts +4 -0
- package/src/infra/cache/namespace.ts +10 -0
- package/src/infra/cache/tenant.ts +93 -0
- package/src/infra/cache/types.ts +24 -0
- package/src/infra/crawl/audit.ts +35 -0
- package/src/infra/crawl/errors.ts +25 -0
- package/src/infra/crawl/handle.ts +693 -0
- package/src/infra/crawl/index.ts +9 -0
- package/src/infra/crawl/links.ts +160 -0
- package/src/infra/crawl/namespace.ts +36 -0
- package/src/infra/crawl/robots.ts +100 -0
- package/src/infra/crawl/schema.ts +89 -0
- package/src/infra/credit-line/handle.ts +883 -0
- package/src/infra/credit-line/index.ts +6 -0
- package/src/infra/credit-line/namespace.ts +7 -0
- package/src/infra/credit-line/schema.ts +96 -0
- package/src/infra/credits/handle.ts +706 -0
- package/src/infra/credits/index.ts +5 -0
- package/src/infra/credits/namespace.ts +7 -0
- package/src/infra/credits/schema.ts +83 -0
- package/src/infra/email/audit.ts +36 -0
- package/src/infra/email/inbound.ts +622 -0
- package/src/infra/email/index.ts +11 -0
- package/src/infra/email/mail.ts +517 -0
- package/src/infra/email/mime.ts +447 -0
- package/src/infra/email/namespace.ts +22 -0
- package/src/infra/email/providers.ts +111 -0
- package/src/infra/email/schema.ts +96 -0
- package/src/infra/email/sns.ts +200 -0
- package/src/infra/email/unsubscribe.ts +44 -0
- package/src/infra/fetch/address.ts +88 -0
- package/src/infra/fetch/connect-retry.ts +87 -0
- package/src/infra/fetch/handle.ts +329 -0
- package/src/infra/fetch/index.ts +7 -0
- package/src/infra/fetch/namespace.ts +70 -0
- package/src/infra/idempotency/index.ts +3 -0
- package/src/infra/idempotency/middleware.ts +87 -0
- package/src/infra/idempotency/store.ts +92 -0
- package/src/infra/idempotency/types.ts +30 -0
- package/src/infra/join-requests/handle.ts +312 -0
- package/src/infra/join-requests/index.ts +2 -0
- package/src/infra/join-requests/namespace.ts +7 -0
- package/src/infra/join-requests/schema.ts +33 -0
- package/src/infra/outbox/core.ts +165 -0
- package/src/infra/quota/errors.ts +32 -0
- package/src/infra/quota/handle.ts +215 -0
- package/src/infra/quota/index.ts +5 -0
- package/src/infra/quota/namespace.ts +7 -0
- package/src/infra/quota/schema.ts +15 -0
- package/src/infra/storage/backend.ts +34 -0
- package/src/infra/storage/handle.ts +513 -0
- package/src/infra/storage/index.ts +9 -0
- package/src/infra/storage/local.ts +104 -0
- package/src/infra/storage/namespace.ts +13 -0
- package/src/infra/storage/s3.ts +257 -0
- package/src/infra/storage/schema.ts +24 -0
- package/src/infra/support/handle.ts +854 -0
- package/src/infra/support/http.ts +229 -0
- package/src/infra/support/index.ts +7 -0
- package/src/infra/support/namespace.ts +12 -0
- package/src/infra/support/schema.ts +96 -0
- package/src/infra/webhook/inbound.ts +45 -0
- package/src/infra/webhook/index.ts +5 -0
- package/src/infra/webhook/namespace.ts +24 -0
- package/src/infra/webhook/outbound.ts +315 -0
- package/src/infra/webhook/sign.ts +32 -0
- package/src/infra/webhook/verify.ts +82 -0
- package/src/invariants/assert.ts +26 -0
- package/src/invariants/index.ts +1 -0
- package/src/lint/config.ts +56 -0
- package/src/lint/doctrine.ts +163 -0
- package/src/lint/include.ts +97 -0
- package/src/lint/index.ts +3 -0
- package/src/lint/preset.ts +10 -0
- package/src/lint/resolver.ts +218 -0
- package/src/lint/rules/no-reexports.ts +46 -0
- package/src/request/behavior/audited.ts +85 -0
- package/src/request/behavior/cached.ts +62 -0
- package/src/request/behavior/compose.ts +28 -0
- package/src/request/behavior/index.ts +5 -0
- package/src/request/behavior/namespace.ts +16 -0
- package/src/request/behavior/types.ts +26 -0
- package/src/request/behavior/versioned.ts +54 -0
- package/src/request/context/create.ts +103 -0
- package/src/request/context/guard.ts +49 -0
- package/src/request/context/index.ts +4 -0
- package/src/request/context/run-in-transaction.ts +200 -0
- package/src/request/context/store.ts +28 -0
- package/src/request/context/types.ts +50 -0
- package/src/request/di/builder.ts +535 -0
- package/src/request/di/index.ts +8 -0
- package/src/request/di/resolve.ts +271 -0
- package/src/request/di/types.ts +262 -0
- package/src/request/fitting/namespace.ts +7 -0
- package/src/request/fixture/batch.ts +71 -0
- package/src/request/fixture/builder.ts +26 -0
- package/src/request/fixture/errors.ts +10 -0
- package/src/request/fixture/index.ts +8 -0
- package/src/request/fixture/namespace.ts +23 -0
- package/src/request/fixture/paginate.ts +44 -0
- package/src/request/fixture/to-handlers.ts +260 -0
- package/src/request/fixture/types.ts +79 -0
- package/src/request/flow/act-as.ts +78 -0
- package/src/request/flow/namespace.ts +17 -0
- package/src/request/http/error-step.ts +42 -0
- package/src/request/http/file.ts +141 -0
- package/src/request/http/index.ts +4 -0
- package/src/request/http/middleware.ts +141 -0
- package/src/request/http/namespace.ts +5 -0
- package/src/request/http/rate-limit-classes.ts +170 -0
- package/src/request/http/response.ts +82 -0
- package/src/request/http/server.ts +916 -0
- package/src/request/http/sniff.ts +261 -0
- package/src/request/http/types.ts +250 -0
- package/src/request/http/upload.ts +284 -0
- package/src/request/internal/text.ts +6 -0
- package/src/request/openapi/generate.ts +205 -0
- package/src/request/openapi/index.ts +2 -0
- package/src/request/openapi/namespace.ts +7 -0
- package/src/test/auth.ts +13 -0
- package/src/test/auto-setup.ts +45 -0
- package/src/test/context.ts +91 -0
- package/src/test/crawl-fixture.ts +157 -0
- package/src/test/discover-fork.ts +17 -0
- package/src/test/fingerprint.ts +135 -0
- package/src/test/globals.d.ts +7 -0
- package/src/test/index.ts +19 -0
- package/src/test/namespace.ts +87 -0
- package/src/test/plugin.ts +189 -0
- package/src/test/progress.ts +185 -0
- package/src/test/reap-after-run.ts +28 -0
- package/src/test/reap.ts +286 -0
- package/src/test/reset.ts +253 -0
- package/src/test/savepoints.ts +172 -0
- package/src/test/setup.ts +1422 -0
- package/src/test/system-channel.ts +50 -0
- package/src/test/vitest-plugin.ts +142 -0
- package/src/test/vitest-workspace.ts +507 -0
- package/src/test/vitest.ts +333 -0
- package/src/test/watch.ts +105 -0
- package/src/trace/capture/bounded-serializer.ts +146 -0
- package/src/trace/cardinality/analyze.ts +173 -0
- package/src/trace/cardinality/branch-context.ts +214 -0
- package/src/trace/cardinality/check.ts +33 -0
- package/src/trace/cardinality/classify-edge.ts +64 -0
- package/src/trace/cardinality/structural-max.ts +107 -0
- package/src/trace/check/coverage-check.ts +188 -0
- package/src/trace/check/flow-check.ts +103 -0
- package/src/trace/check/flow-step-site.ts +234 -0
- package/src/trace/check/io-pattern.ts +116 -0
- package/src/trace/codemod/add-cardinality.ts +152 -0
- package/src/trace/codemod/run.ts +80 -0
- package/src/trace/entry/child-trace.ts +21 -0
- package/src/trace/entry/close-trace.ts +87 -0
- package/src/trace/entry/finalize-trace.ts +28 -0
- package/src/trace/entry/open-trace.ts +138 -0
- package/src/trace/entry/parent-trace.ts +20 -0
- package/src/trace/entry/parse-traceparent.ts +39 -0
- package/src/trace/entry/process-start.ts +38 -0
- package/src/trace/entry/trace-context.ts +26 -0
- package/src/trace/entry/tracing-enabled.ts +10 -0
- package/src/trace/external/record.ts +195 -0
- package/src/trace/external/report.ts +66 -0
- package/src/trace/namespace.ts +19 -0
- package/src/trace/partition/day.ts +47 -0
- package/src/trace/partition/index.ts +14 -0
- package/src/trace/partition/partition-name.ts +23 -0
- package/src/trace/partition/plan.ts +82 -0
- package/src/trace/partition/schedule.ts +26 -0
- package/src/trace/partition/startup-hook.ts +39 -0
- package/src/trace/partition/sync.ts +154 -0
- package/src/trace/partition/tables.ts +53 -0
- package/src/trace/read/tree.ts +83 -0
- package/src/trace/retain/copy-on-error.ts +76 -0
- package/src/trace/runtime/expire-stale-traces.ts +54 -0
- package/src/trace/runtime/flow-step.ts +147 -0
- package/src/trace/runtime/flush-steps.ts +31 -0
- package/src/trace/runtime/pending-writes.ts +54 -0
- package/src/trace/runtime/step-buffer.ts +298 -0
- package/src/trace/runtime/step-error.ts +21 -0
- package/src/trace/runtime/step-meta.ts +55 -0
- package/src/trace/runtime/thenable.ts +12 -0
- package/src/trace/runtime/truncate-trace.ts +62 -0
- package/src/trace/schema/trace-fields.ts +52 -0
- package/src/trace/schema/trace-retained.ts +22 -0
- package/src/trace/schema/trace-step-fields.ts +44 -0
- package/src/trace/schema/trace-step-retained.ts +25 -0
- package/src/trace/schema/trace-step.ts +18 -0
- package/src/trace/schema/trace.ts +12 -0
- package/src/trace/transformer/apply.ts +79 -0
- package/src/trace/transformer/build-meta-literal.ts +30 -0
- package/src/trace/transformer/find-flow-step-sites.ts +85 -0
- package/src/trace/transformer/inject-flow-step-import.ts +39 -0
- package/src/trace/transformer/transform-error.ts +14 -0
- package/src/trace/transformer/transform-flow-steps.ts +123 -0
- package/src/trace/transformer/wrap-function-declaration.ts +62 -0
- package/src/trace/transformer/wrap-variable-declaration.ts +67 -0
- package/src/workspace/affected.ts +110 -0
- package/src/workspace/changed-files.ts +163 -0
- package/src/workspace/check-deps.ts +213 -0
- package/src/workspace/checksum.ts +20 -0
- package/src/workspace/dependency-graph.ts +67 -0
- package/src/workspace/detect-pipework-copies.ts +93 -0
- package/src/workspace/discover.ts +206 -0
- package/src/workspace/generate.ts +401 -0
- package/src/workspace/index.ts +7 -0
- package/src/workspace/module-imports-pipework.ts +39 -0
- package/src/workspace/module-package-name.ts +12 -0
- package/src/workspace/scrub-git-env.ts +23 -0
- package/src/workspace/source-paths.ts +36 -0
- package/src/workspace/sync.ts +182 -0
- package/dist/REFERENCE.md +0 -1477
- package/dist/infra/access-links/rate-limit.d.ts +0 -10
- package/dist/infra/access-links/rate-limit.d.ts.map +0 -1
- package/dist/infra/access-links/rate-limit.js +0 -28
- package/dist/infra/access-links/rate-limit.js.map +0 -1
- package/dist/infra/email/sigv4.d.ts +0 -24
- package/dist/infra/email/sigv4.d.ts.map +0 -1
- package/dist/infra/email/sigv4.js +0 -55
- package/dist/infra/email/sigv4.js.map +0 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createAccessLinks } from './handle.js'
|
|
2
|
+
export type {
|
|
3
|
+
AccessLinks, AccessLinksConfig, AccessLink, PublicLink, AccessLinkUse, AccessLinkTarget, AccessLinkListed, AccessLinkPage,
|
|
4
|
+
AccessLinkCheck, AccessLinkCheckArgs, LinkOptions, ResolveResult, BeginUseResult,
|
|
5
|
+
CompleteUseResult, AbandonUseResult,
|
|
6
|
+
} from './handle.js'
|
|
7
|
+
export type { CodeConfig } from './code.js'
|
|
8
|
+
export type { RateLimitConfig } from './handle.js'
|
|
9
|
+
export type { PersonCheck, PersonCheckConfig, PersonChallenge, PersonPass } from './person-check.js'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createAccessLinks } from './handle.js'
|
|
2
|
+
import { personCheck } from './person-check.js'
|
|
3
|
+
|
|
4
|
+
/** Access links namespace — short revocable bearer links to a tenant's resource. */
|
|
5
|
+
export const accessLinks = {
|
|
6
|
+
/** Creates an access-link handle: tenant-scoped management plus the public resolve / beginUse / completeUse / abandonUse. */
|
|
7
|
+
create: createAccessLinks,
|
|
8
|
+
/**
|
|
9
|
+
* A self-hosted person check for `requireChecks`: ALTCHA-format proof-of-work challenges bound to a link code
|
|
10
|
+
* and an expiry, HMAC-signed with `secret` (32 bytes or more, the same in every process). A solution is accepted
|
|
11
|
+
* once across all processes (`access_link_check_spent`, in the `accessLinks` tables) and yields a short-lived
|
|
12
|
+
* signed pass that is the proof for the link's check at `beginUse`. `personCheck.solve(challenge)` solves a
|
|
13
|
+
* challenge in code, for tests.
|
|
14
|
+
*
|
|
15
|
+
* The app writes two public routes: GET returns `check.challenge(code)` for the widget (`challengeurl`, or
|
|
16
|
+
* `challengejson`); POST gives `check.verify(code, payload)` the widget's payload and returns the pass, or refuses
|
|
17
|
+
* when it is `null`. The page carries the ALTCHA widget (MIT; serve its script yourself, no third party sees the
|
|
18
|
+
* visitor), which solves in the background and falls back to a keyboard and screen-reader checkbox. pipework
|
|
19
|
+
* does not ship the widget.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* const person = accessLinks.personCheck({ secret: config.linkSecret })
|
|
23
|
+
* const links = accessLinks.create({ permissions: ['view'], checks: { person: person.check } })
|
|
24
|
+
* await links.link({ target, permissions: ['view'], requireChecks: ['person'] })
|
|
25
|
+
* // public route: begin with the pass the visitor got from POST
|
|
26
|
+
* await links.beginUse(code, { kind: 'view', proof: { person: pass } })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
personCheck,
|
|
30
|
+
} as const
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto'
|
|
2
|
+
import { sql } from '../../data/db/sql.js'
|
|
3
|
+
import { pipeSystem } from '../../data/db/db.js'
|
|
4
|
+
import type { AccessLinkCheck } from './handle.js'
|
|
5
|
+
|
|
6
|
+
const ALGORITHM = 'SHA-256'
|
|
7
|
+
const MIN_SECRET_BYTES = 32
|
|
8
|
+
const MAX_NUMBER_LIMIT = 10_000_000
|
|
9
|
+
const PRUNE_BATCH = 100
|
|
10
|
+
|
|
11
|
+
/** A proof-of-work challenge in the shape the ALTCHA widget takes. */
|
|
12
|
+
export interface PersonChallenge {
|
|
13
|
+
readonly algorithm: 'SHA-256'
|
|
14
|
+
readonly challenge: string
|
|
15
|
+
readonly maxnumber: number
|
|
16
|
+
readonly salt: string
|
|
17
|
+
readonly signature: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PersonCheckConfig {
|
|
21
|
+
/** Signs challenges and passes; 32 bytes or more, the same in every process. */
|
|
22
|
+
readonly secret: string | Buffer
|
|
23
|
+
/** The largest number the browser may have to try; the work is half this on average. Default 100_000; 1 to 10_000_000. */
|
|
24
|
+
readonly maxNumber?: number
|
|
25
|
+
/** How long a challenge can be solved for. Default 10 minutes. */
|
|
26
|
+
readonly challengeMs?: number
|
|
27
|
+
/** How long a pass is good for. Default 30 minutes. */
|
|
28
|
+
readonly passMs?: number
|
|
29
|
+
/** Database holding `access_link_check_spent`; default the default database. */
|
|
30
|
+
readonly database?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PersonPass {
|
|
34
|
+
readonly pass: string
|
|
35
|
+
readonly expiresAt: Date
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PersonCheck {
|
|
39
|
+
/** A challenge for one link's code, for the widget. */
|
|
40
|
+
challenge(code: string): PersonChallenge
|
|
41
|
+
/**
|
|
42
|
+
* Accepts a widget solution (its base64 payload) for `code` once, across processes, and returns a pass;
|
|
43
|
+
* `null` for anything else: malformed, tampered, expired, for another link, or already spent. Never throws on bad input.
|
|
44
|
+
* Needs a Flow (it records the spent solution through `pipe.system()`).
|
|
45
|
+
*/
|
|
46
|
+
verify(code: string, solution: unknown): Promise<PersonPass | null>
|
|
47
|
+
/**
|
|
48
|
+
* True when `pass` is a pass issued for `code`, unexpired and correctly signed: the same answer `check` gives at
|
|
49
|
+
* `beginUse`. Use it on a public route right after `resolve(code)` to refuse a bad pass before answering anything
|
|
50
|
+
* about the link. Synchronous; no database, no Flow; never throws.
|
|
51
|
+
*/
|
|
52
|
+
checkPass(code: string, pass: unknown): boolean
|
|
53
|
+
/** The `AccessLinkCheck` to register under a name in `accessLinks.create({ checks })`: the proof is a pass issued for this link. */
|
|
54
|
+
readonly check: AccessLinkCheck
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sha256Hex(data: string): string {
|
|
58
|
+
return createHash('sha256').update(data).digest('hex')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function sameBytes(a: Buffer, b: Buffer): boolean {
|
|
62
|
+
return a.length === b.length && a.length > 0 && timingSafeEqual(a, b)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function sameHex(a: string, b: string): boolean {
|
|
66
|
+
return sameBytes(Buffer.from(a, 'hex'), Buffer.from(b, 'hex'))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function positive(name: string, value: number): number {
|
|
70
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`personCheck: ${name} must be a positive number`)
|
|
71
|
+
return value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createPersonCheck(config: PersonCheckConfig): PersonCheck {
|
|
75
|
+
const secret = typeof config.secret === 'string' ? Buffer.from(config.secret, 'utf8') : config.secret
|
|
76
|
+
if (!Buffer.isBuffer(secret) || secret.length < MIN_SECRET_BYTES) {
|
|
77
|
+
throw new Error(`personCheck: secret must be ${String(MIN_SECRET_BYTES)} bytes or more, the same in every process (no random fallback: a per-process secret breaks passes across processes)`)
|
|
78
|
+
}
|
|
79
|
+
const maxNumber = config.maxNumber ?? 100_000
|
|
80
|
+
if (!Number.isInteger(maxNumber) || maxNumber < 1 || maxNumber > MAX_NUMBER_LIMIT) {
|
|
81
|
+
throw new Error(`personCheck: maxNumber must be an integer from 1 to ${String(MAX_NUMBER_LIMIT)}`)
|
|
82
|
+
}
|
|
83
|
+
const challengeMs = positive('challengeMs', config.challengeMs ?? 600_000)
|
|
84
|
+
const passMs = positive('passMs', config.passMs ?? 1_800_000)
|
|
85
|
+
const database = config.database
|
|
86
|
+
|
|
87
|
+
const key = (purpose: 'challenge' | 'pass'): Buffer =>
|
|
88
|
+
createHmac('sha256', secret).update(`pipework access links person check: ${purpose}`).digest()
|
|
89
|
+
const challengeKey = key('challenge')
|
|
90
|
+
const passKey = key('pass')
|
|
91
|
+
|
|
92
|
+
function challenge(code: string): PersonChallenge {
|
|
93
|
+
const expires = Math.floor((Date.now() + challengeMs) / 1000)
|
|
94
|
+
const salt = `${randomBytes(12).toString('hex')}?expires=${String(expires)}&link=${encodeURIComponent(code)}`
|
|
95
|
+
const secretNumber = randomBytes(4).readUInt32BE() % (maxNumber + 1)
|
|
96
|
+
const digest = sha256Hex(salt + String(secretNumber))
|
|
97
|
+
const signature = createHmac('sha256', challengeKey).update(digest).digest('hex')
|
|
98
|
+
return { algorithm: ALGORITHM, challenge: digest, maxnumber: maxNumber, salt, signature }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function issuePass(code: string): PersonPass {
|
|
102
|
+
const exp = Date.now() + passMs
|
|
103
|
+
const body = Buffer.from(JSON.stringify({ code, exp })).toString('base64url')
|
|
104
|
+
const mac = createHmac('sha256', passKey).update(body).digest('base64url')
|
|
105
|
+
return { pass: `${body}.${mac}`, expiresAt: new Date(exp) }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The challenge's expiry in ms when `solution` solves a live challenge made for `code`; otherwise null.
|
|
109
|
+
function solvedUntil(code: string, solution: unknown): { id: string; untilMs: number } | null {
|
|
110
|
+
if (typeof solution !== 'string') return null
|
|
111
|
+
let payload: unknown
|
|
112
|
+
try {
|
|
113
|
+
payload = JSON.parse(Buffer.from(solution, 'base64').toString('utf8'))
|
|
114
|
+
} catch {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) return null
|
|
118
|
+
const { algorithm, challenge: id, number, salt, signature } = payload as Record<string, unknown>
|
|
119
|
+
if (algorithm !== ALGORITHM || typeof id !== 'string' || typeof salt !== 'string' || typeof signature !== 'string') return null
|
|
120
|
+
if (typeof number !== 'number' || !Number.isSafeInteger(number) || number < 0) return null
|
|
121
|
+
const params = new URLSearchParams(salt.split('?')[1] ?? '')
|
|
122
|
+
const expires = Number(params.get('expires'))
|
|
123
|
+
if (!Number.isFinite(expires) || expires * 1000 <= Date.now() || params.get('link') !== code) return null
|
|
124
|
+
const expected = createHmac('sha256', challengeKey).update(id).digest('hex')
|
|
125
|
+
if (!sameHex(expected, signature)) return null
|
|
126
|
+
if (!sameHex(sha256Hex(salt + String(number)), id)) return null
|
|
127
|
+
return { id, untilMs: expires * 1000 }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function verify(code: string, solution: unknown): Promise<PersonPass | null> {
|
|
131
|
+
const solved = solvedUntil(code, solution)
|
|
132
|
+
if (solved === null) return null
|
|
133
|
+
const db = pipeSystem(database)
|
|
134
|
+
const spent = await db.execute(sql`
|
|
135
|
+
INSERT INTO access_link_check_spent (challenge, expires_at)
|
|
136
|
+
VALUES (${solved.id}, ${new Date(solved.untilMs).toISOString()})
|
|
137
|
+
ON CONFLICT DO NOTHING RETURNING 1 AS spent`)
|
|
138
|
+
if (spent.length === 0) return null
|
|
139
|
+
// Kept an hour past expiry so clock skew between processes and the database never reopens a solution.
|
|
140
|
+
await db.execute(sql`
|
|
141
|
+
DELETE FROM access_link_check_spent WHERE ctid IN (
|
|
142
|
+
SELECT ctid FROM access_link_check_spent WHERE expires_at < now() - interval '1 hour' LIMIT ${PRUNE_BATCH})`)
|
|
143
|
+
return issuePass(code)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function checkPass(code: string, pass: unknown): boolean {
|
|
147
|
+
if (typeof pass !== 'string') return false
|
|
148
|
+
const parts = pass.split('.')
|
|
149
|
+
const [body, mac] = parts
|
|
150
|
+
if (parts.length !== 2 || body === undefined || mac === undefined) return false
|
|
151
|
+
const expected = createHmac('sha256', passKey).update(body).digest()
|
|
152
|
+
if (!sameBytes(Buffer.from(mac, 'base64url'), expected)) return false
|
|
153
|
+
try {
|
|
154
|
+
const claims = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as { code?: unknown; exp?: unknown }
|
|
155
|
+
return typeof claims.exp === 'number' && claims.exp > Date.now() && claims.code === code
|
|
156
|
+
} catch {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const check: AccessLinkCheck = ({ link, proof }) => Promise.resolve(checkPass(link.code, proof))
|
|
162
|
+
|
|
163
|
+
return { challenge, verify, checkPass, check }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Solves a challenge the way the widget does and returns its base64 payload. For tests: it does the work the browser would. */
|
|
167
|
+
function solvePersonChallenge(c: PersonChallenge): string {
|
|
168
|
+
for (let n = 0; n <= c.maxnumber; n++) {
|
|
169
|
+
if (sha256Hex(c.salt + String(n)) === c.challenge) {
|
|
170
|
+
const payload = { algorithm: c.algorithm, challenge: c.challenge, number: n, salt: c.salt, signature: c.signature, took: 0 }
|
|
171
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64')
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
throw new Error('personCheck.solve: challenge has no solution')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const personCheck = Object.assign(createPersonCheck, { solve: solvePersonChallenge })
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// The four tables behind access links. `access_link_code` is deliberately NOT
|
|
2
|
+
// tenant-scoped: it maps a code to its tenant so a request with no tenant can
|
|
3
|
+
// find where to look, and holds nothing else. `access_link` and
|
|
4
|
+
// `access_link_use` carry a `.tenant()` key, so generated migrations put them
|
|
5
|
+
// under forced row-level security.
|
|
6
|
+
|
|
7
|
+
import { define } from '../../data/domain/index.js'
|
|
8
|
+
import { field } from '../../data/domain/field.js'
|
|
9
|
+
import { schema } from '../../data/schema/namespace.js'
|
|
10
|
+
import { index } from '../../data/db/idx/index.js'
|
|
11
|
+
|
|
12
|
+
export const ACCESS_LINK_USE_STATUSES = ['pending', 'completed', 'abandoned'] as const
|
|
13
|
+
|
|
14
|
+
export const accessLinkCode = define('access_link_code', {
|
|
15
|
+
code: field.text().primaryKey(),
|
|
16
|
+
tenantId: field.uuid(),
|
|
17
|
+
linkId: field.uuid(),
|
|
18
|
+
createdAt: field.timestamp().default('now'),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const accessLink = define('access_link', {
|
|
22
|
+
id: field.uuid().defaultRandom().primaryKey(),
|
|
23
|
+
tenantId: field.uuid().tenant(),
|
|
24
|
+
code: field.text().unique(),
|
|
25
|
+
targetType: field.text(),
|
|
26
|
+
targetId: field.text(),
|
|
27
|
+
permissions: field.jsonb(schema.check.json()),
|
|
28
|
+
note: field.text().nullable(),
|
|
29
|
+
createdBy: field.text().nullable(),
|
|
30
|
+
createdAt: field.timestamp().default('now'),
|
|
31
|
+
expiresAt: field.timestamp().nullable(),
|
|
32
|
+
maxUses: field.integer().nullable(),
|
|
33
|
+
requireChecks: field.jsonb(schema.check.json()).default([]),
|
|
34
|
+
checkData: field.jsonb(schema.check.json()).nullable(),
|
|
35
|
+
revokedAt: field.timestamp().nullable(),
|
|
36
|
+
revokedBy: field.text().nullable(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const accessLinkUse = define('access_link_use', {
|
|
40
|
+
id: field.uuid().defaultRandom().primaryKey(),
|
|
41
|
+
tenantId: field.uuid().tenant(),
|
|
42
|
+
linkId: field.uuid(),
|
|
43
|
+
kind: field.text(),
|
|
44
|
+
status: field.enum(ACCESS_LINK_USE_STATUSES),
|
|
45
|
+
startedAt: field.timestamp().default('now'),
|
|
46
|
+
reservedUntil: field.timestamp(),
|
|
47
|
+
completedAt: field.timestamp().nullable(),
|
|
48
|
+
client: field.text().nullable(),
|
|
49
|
+
location: field.text().nullable(),
|
|
50
|
+
detail: field.jsonb(schema.check.json()).nullable(),
|
|
51
|
+
claimedAt: field.timestamp().nullable(),
|
|
52
|
+
}, {
|
|
53
|
+
indexes: (t) => ({
|
|
54
|
+
byLink: index('idx_access_link_use_link').on(t['linkId'], t['status']),
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Person-check solutions already accepted, kept until the challenge has expired:
|
|
59
|
+
// one solved challenge makes one pass, across processes. Not tenant-scoped.
|
|
60
|
+
export const accessLinkCheckSpent = define('access_link_check_spent', {
|
|
61
|
+
challenge: field.text().primaryKey(),
|
|
62
|
+
expiresAt: field.timestamp(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Codes that matched no link, counted per client in a fixed window: the guessing
|
|
66
|
+
// limit, shared by every process. Not tenant-scoped.
|
|
67
|
+
export const accessLinkMiss = define('access_link_miss', {
|
|
68
|
+
client: field.text().primaryKey(),
|
|
69
|
+
count: field.integer().default(0),
|
|
70
|
+
windowStart: field.timestamp().default('now'),
|
|
71
|
+
})
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Sources over the failures the async subsystems already record: cron schedules, step graphs, pipelines and
|
|
2
|
+
// trace expiry. Each is a composition of `querySource` and `newRowsSource`.
|
|
3
|
+
|
|
4
|
+
import { sql } from '../../data/db/sql.js'
|
|
5
|
+
import { composeSources, newRowsSource, querySource, type AlertSource } from './sources.js'
|
|
6
|
+
import { common, positive, text, FIFTEEN_MIN, HOUR, type Basics } from './source-options.js'
|
|
7
|
+
|
|
8
|
+
const FIVE_MIN = 5 * 60_000
|
|
9
|
+
const TRACE_SWEEP_GRACE = 26 * HOUR
|
|
10
|
+
|
|
11
|
+
/** The `error` jsonb of a recorded failure as one line: its `message`, `error` or `step`/`error` pair, else the JSON. */
|
|
12
|
+
function errorText(v: unknown): string | null {
|
|
13
|
+
if (v === null || v === undefined) return null
|
|
14
|
+
const parsed = typeof v === 'string' ? safeParse(v) : v
|
|
15
|
+
if (typeof parsed === 'string') return parsed
|
|
16
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
17
|
+
const o = parsed as Record<string, unknown>
|
|
18
|
+
const inner = o['message'] ?? o['error']
|
|
19
|
+
if (typeof inner === 'string') return typeof o['step'] === 'string' ? `${o['step']}: ${inner}` : inner
|
|
20
|
+
if (typeof inner === 'object' && inner !== null) return errorText(inner)
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify(parsed)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function safeParse(s: string): unknown {
|
|
26
|
+
try { return JSON.parse(s) } catch { return s }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Raises `schedule-overdue:<schedule name>` while a cron schedule's `next_run_at` is more than `graceMs` (default 5 min)
|
|
31
|
+
* in the past. Every scheduler pass moves `next_run_at` into the future, whether it fires the job or skips it because
|
|
32
|
+
* the previous run is still pending, so an overdue row means no scheduler is running (or it cannot reach the row).
|
|
33
|
+
* Resolves when the scheduler catches up. Needs a worker surface that declares schedules (the `pipework_schedules` table; no blueprint key).
|
|
34
|
+
*/
|
|
35
|
+
export function schedulesSource(options: Basics & { graceMs?: number } = {}): AlertSource {
|
|
36
|
+
const grace = positive(options.graceMs ?? FIVE_MIN, 'graceMs', 'alerts.source.schedules')
|
|
37
|
+
return querySource({
|
|
38
|
+
...common(options),
|
|
39
|
+
name: 'schedule-overdue',
|
|
40
|
+
query: sql`
|
|
41
|
+
SELECT name, next_run_at, last_run_at, (extract(epoch FROM clock_timestamp() - next_run_at) * 1000)::bigint AS overdue_ms
|
|
42
|
+
FROM pipework_schedules
|
|
43
|
+
WHERE next_run_at < clock_timestamp() - ${grace} * interval '1 millisecond'
|
|
44
|
+
ORDER BY name`,
|
|
45
|
+
id: r => String(r['name']),
|
|
46
|
+
message: r => `schedule ${String(r['name'])} is ${String(r['overdue_ms'])} ms past its next run; is the scheduler running?`,
|
|
47
|
+
subject: r => ({ entity: 'schedule', entityId: String(r['name']) }),
|
|
48
|
+
context: r => ({
|
|
49
|
+
schedule: String(r['name']),
|
|
50
|
+
overdueMs: Number(r['overdue_ms']),
|
|
51
|
+
nextRunAt: new Date(String(r['next_run_at'])).toISOString(),
|
|
52
|
+
lastRunAt: r['last_run_at'] === null ? null : new Date(String(r['last_run_at'])).toISOString(),
|
|
53
|
+
}),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Raises `step-run-failed:<graph>` for step runs that failed since the last tick (message with the count and the last
|
|
59
|
+
* error; context `count`, `lastError`, `lastRunId`, `lastTenantId`), `step-run-unnotified:<graph>` while runs finished
|
|
60
|
+
* more than `notifyAfterMs` (default 15 min) ago still have `notified = false` (the graph's `onDone` has not run), and
|
|
61
|
+
* `step-task-waiting:<graph>` while a task has waited for a person's decision more than `waitingAfterMs` (default 1 h);
|
|
62
|
+
* the wait is measured from its run's `created_at`, as tasks carry no timestamp, and `step-run-stalled:<graph>` while runs
|
|
63
|
+
* are still `running` more than `runningAfterMs` (default 1 h) after `created_at` with no task `waiting` (a `waiting` task is a
|
|
64
|
+
* person's decision and is covered above; `blocked` and `queued` tasks are work in flight, so a run stuck on them is stalled). A failed task always fails its run in the
|
|
65
|
+
* same transaction, so failed runs cover failed tasks. The failed condition clears by the handle's quiet period; the other two
|
|
66
|
+
* resolve when the rows drain. Needs a step graph defined in the app (the `pipework_step_run` and `pipework_step_task` tables; no blueprint key).
|
|
67
|
+
*/
|
|
68
|
+
export function stepsSource(options: Basics & { notifyAfterMs?: number; waitingAfterMs?: number; runningAfterMs?: number; settleMs?: number } = {}): AlertSource {
|
|
69
|
+
const notifyAfter = positive(options.notifyAfterMs ?? FIFTEEN_MIN, 'notifyAfterMs', 'alerts.source.steps')
|
|
70
|
+
const waitingAfter = positive(options.waitingAfterMs ?? HOUR, 'waitingAfterMs', 'alerts.source.steps')
|
|
71
|
+
const runningAfter = positive(options.runningAfterMs ?? HOUR, 'runningAfterMs', 'alerts.source.steps')
|
|
72
|
+
const failed = newRowsSource({
|
|
73
|
+
...common(options),
|
|
74
|
+
name: 'step-run-failed',
|
|
75
|
+
table: 'pipework_step_run',
|
|
76
|
+
at: 'finished_at',
|
|
77
|
+
where: sql`t.status = 'failed'`,
|
|
78
|
+
groupBy: 'graph',
|
|
79
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
80
|
+
message: g => `${String(g.count)} run(s) of step graph ${g.id} failed; last error: ${text(errorText(g.last['error']))}`,
|
|
81
|
+
context: g => ({
|
|
82
|
+
graph: g.id,
|
|
83
|
+
count: g.count,
|
|
84
|
+
lastError: errorText(g.last['error']),
|
|
85
|
+
lastRunId: String(g.last['id']),
|
|
86
|
+
lastTenantId: String(g.last['tenant_id']),
|
|
87
|
+
}),
|
|
88
|
+
})
|
|
89
|
+
const unnotified = querySource({
|
|
90
|
+
...common(options),
|
|
91
|
+
name: 'step-run-unnotified',
|
|
92
|
+
query: sql`
|
|
93
|
+
SELECT graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(finished_at)) * 1000)::bigint AS oldest_ms
|
|
94
|
+
FROM pipework_step_run
|
|
95
|
+
WHERE status = 'done' AND notified = false
|
|
96
|
+
AND finished_at < clock_timestamp() - ${notifyAfter} * interval '1 millisecond'
|
|
97
|
+
GROUP BY graph ORDER BY graph`,
|
|
98
|
+
id: r => String(r['graph']),
|
|
99
|
+
message: r => `${String(r['n'])} finished run(s) of step graph ${String(r['graph'])} not notified for more than ${String(notifyAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
100
|
+
subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
|
|
101
|
+
context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
102
|
+
})
|
|
103
|
+
const waiting = querySource({
|
|
104
|
+
...common(options),
|
|
105
|
+
name: 'step-task-waiting',
|
|
106
|
+
query: sql`
|
|
107
|
+
SELECT r.graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(r.created_at)) * 1000)::bigint AS oldest_ms
|
|
108
|
+
FROM pipework_step_task k JOIN pipework_step_run r ON r.id = k.run_id
|
|
109
|
+
WHERE k.status = 'waiting' AND r.status = 'running'
|
|
110
|
+
AND r.created_at < clock_timestamp() - ${waitingAfter} * interval '1 millisecond'
|
|
111
|
+
GROUP BY r.graph ORDER BY r.graph`,
|
|
112
|
+
id: r => String(r['graph']),
|
|
113
|
+
message: r => `${String(r['n'])} task(s) of step graph ${String(r['graph'])} waiting more than ${String(waitingAfter)} ms (oldest run ${String(r['oldest_ms'])} ms old)`,
|
|
114
|
+
subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
|
|
115
|
+
context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
116
|
+
})
|
|
117
|
+
const stalled = querySource({
|
|
118
|
+
...common(options),
|
|
119
|
+
name: 'step-run-stalled',
|
|
120
|
+
query: sql`
|
|
121
|
+
SELECT r.graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(r.created_at)) * 1000)::bigint AS oldest_ms
|
|
122
|
+
FROM pipework_step_run r
|
|
123
|
+
WHERE r.status = 'running'
|
|
124
|
+
AND r.created_at < clock_timestamp() - ${runningAfter} * interval '1 millisecond'
|
|
125
|
+
AND NOT EXISTS (SELECT 1 FROM pipework_step_task k WHERE k.run_id = r.id AND k.status = 'waiting')
|
|
126
|
+
GROUP BY r.graph ORDER BY r.graph`,
|
|
127
|
+
id: r => String(r['graph']),
|
|
128
|
+
message: r => `${String(r['n'])} run(s) of step graph ${String(r['graph'])} still running after more than ${String(runningAfter)} ms with no person step waiting (oldest ${String(r['oldest_ms'])} ms)`,
|
|
129
|
+
subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
|
|
130
|
+
context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
131
|
+
})
|
|
132
|
+
return composeSources('steps', [failed, unnotified, waiting, stalled])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Raises `pipeline-failed:<pipeline name>` for pipeline runs that failed since the last tick (context `count`,
|
|
137
|
+
* `lastError`, `lastRunId`, `lastTenantId`), and `pipeline-waiting:<pipeline name>` while runs sit in `waiting` more than
|
|
138
|
+
* `waitingAfterMs` (default 1 h) after they started (the table keeps no time for when a run began to wait, so the age is
|
|
139
|
+
* `started_at`), and `pipeline-stalled:<pipeline name>` while runs are `running` more than `runningAfterMs` (default 1 h)
|
|
140
|
+
* after `started_at` (the process died mid-run). The failed condition clears by the handle's quiet period; the others
|
|
141
|
+
* resolve when the runs resume or finish. Needs the app's own `pipework_pipeline_runs` table (pipelines have no blueprint key; the app creates it).
|
|
142
|
+
*/
|
|
143
|
+
export function pipelinesSource(options: Basics & { waitingAfterMs?: number; runningAfterMs?: number; settleMs?: number } = {}): AlertSource {
|
|
144
|
+
const waitingAfter = positive(options.waitingAfterMs ?? HOUR, 'waitingAfterMs', 'alerts.source.pipelines')
|
|
145
|
+
const runningAfter = positive(options.runningAfterMs ?? HOUR, 'runningAfterMs', 'alerts.source.pipelines')
|
|
146
|
+
const failed = newRowsSource({
|
|
147
|
+
...common(options),
|
|
148
|
+
name: 'pipeline-failed',
|
|
149
|
+
table: 'pipework_pipeline_runs',
|
|
150
|
+
at: 'completed_at',
|
|
151
|
+
where: sql`t.status = 'failed'`,
|
|
152
|
+
groupBy: 'pipeline_name',
|
|
153
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
154
|
+
message: g => `${String(g.count)} run(s) of pipeline ${g.id} failed; last error: ${text(errorText(g.last['error']))}`,
|
|
155
|
+
context: g => ({
|
|
156
|
+
pipeline: g.id,
|
|
157
|
+
count: g.count,
|
|
158
|
+
lastError: errorText(g.last['error']),
|
|
159
|
+
lastRunId: String(g.last['id']),
|
|
160
|
+
lastTenantId: String(g.last['tenant_id']),
|
|
161
|
+
}),
|
|
162
|
+
})
|
|
163
|
+
const waiting = querySource({
|
|
164
|
+
...common(options),
|
|
165
|
+
name: 'pipeline-waiting',
|
|
166
|
+
query: sql`
|
|
167
|
+
SELECT pipeline_name, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(started_at)) * 1000)::bigint AS oldest_ms
|
|
168
|
+
FROM pipework_pipeline_runs
|
|
169
|
+
WHERE status = 'waiting' AND started_at < clock_timestamp() - ${waitingAfter} * interval '1 millisecond'
|
|
170
|
+
GROUP BY pipeline_name ORDER BY pipeline_name`,
|
|
171
|
+
id: r => String(r['pipeline_name']),
|
|
172
|
+
message: r => `${String(r['n'])} run(s) of pipeline ${String(r['pipeline_name'])} waiting more than ${String(waitingAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
173
|
+
subject: r => ({ entity: 'pipeline', entityId: String(r['pipeline_name']) }),
|
|
174
|
+
context: r => ({ pipeline: String(r['pipeline_name']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
175
|
+
})
|
|
176
|
+
const stalled = querySource({
|
|
177
|
+
...common(options),
|
|
178
|
+
name: 'pipeline-stalled',
|
|
179
|
+
query: sql`
|
|
180
|
+
SELECT pipeline_name, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(started_at)) * 1000)::bigint AS oldest_ms
|
|
181
|
+
FROM pipework_pipeline_runs
|
|
182
|
+
WHERE status = 'running' AND started_at < clock_timestamp() - ${runningAfter} * interval '1 millisecond'
|
|
183
|
+
GROUP BY pipeline_name ORDER BY pipeline_name`,
|
|
184
|
+
id: r => String(r['pipeline_name']),
|
|
185
|
+
message: r => `${String(r['n'])} run(s) of pipeline ${String(r['pipeline_name'])} still running after more than ${String(runningAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
186
|
+
subject: r => ({ entity: 'pipeline', entityId: String(r['pipeline_name']) }),
|
|
187
|
+
context: r => ({ pipeline: String(r['pipeline_name']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
188
|
+
})
|
|
189
|
+
return composeSources('pipelines', [failed, waiting, stalled])
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Raises `trace-expired:<entry kind>` (http, job, cron, test) for traces the expiry sweep force-closed since the last
|
|
194
|
+
* tick: the sweep stamps `status = 'expired'` and `closed_at`, so `closed_at` is the cursor. Also raises
|
|
195
|
+
* `trace-expiry-undrained:all` while traces still `open` have an `expires_at` more than `graceMs` (default 26 h: the maintenance job that
|
|
196
|
+
* runs the sweep is daily, so a trace legitimately stays open up to a day past its deadline) in the past, which means
|
|
197
|
+
* nothing is running the expiry job. The expired condition clears by the handle's quiet period; the
|
|
198
|
+
* undrained one resolves once the sweep runs. Needs the blueprint's `trace` block (the `trace` table).
|
|
199
|
+
*/
|
|
200
|
+
export function tracesSource(options: Basics & { graceMs?: number; settleMs?: number } = {}): AlertSource {
|
|
201
|
+
const grace = positive(options.graceMs ?? TRACE_SWEEP_GRACE, 'graceMs', 'alerts.source.traces')
|
|
202
|
+
const expired = newRowsSource({
|
|
203
|
+
...common(options),
|
|
204
|
+
name: 'trace-expired',
|
|
205
|
+
table: 'trace',
|
|
206
|
+
at: 'closed_at',
|
|
207
|
+
where: sql`t.status = 'expired'`,
|
|
208
|
+
groupBy: 'entry_kind',
|
|
209
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
210
|
+
message: g => `${String(g.count)} ${g.id} trace(s) force-closed after expiring; last trace ${String(g.last['id'])}`,
|
|
211
|
+
context: g => ({
|
|
212
|
+
entryKind: g.id,
|
|
213
|
+
count: g.count,
|
|
214
|
+
lastTraceId: String(g.last['id']),
|
|
215
|
+
lastTenantId: g.last['tenant_id'] === null || g.last['tenant_id'] === undefined ? null : String(g.last['tenant_id']),
|
|
216
|
+
}),
|
|
217
|
+
})
|
|
218
|
+
const undrained = querySource({
|
|
219
|
+
...common(options),
|
|
220
|
+
name: 'trace-expiry-undrained',
|
|
221
|
+
query: sql`
|
|
222
|
+
SELECT count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(expires_at)) * 1000)::bigint AS oldest_ms
|
|
223
|
+
FROM "trace"
|
|
224
|
+
WHERE status = 'open' AND expires_at <= clock_timestamp() - ${grace} * interval '1 millisecond'
|
|
225
|
+
HAVING count(*) > 0`,
|
|
226
|
+
id: () => 'all',
|
|
227
|
+
message: r => `${String(r['n'])} open trace(s) expired more than ${String(grace)} ms ago and not closed; is the trace expiry job scheduled?`,
|
|
228
|
+
context: r => ({ count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
229
|
+
})
|
|
230
|
+
return composeSources('traces', [expired, undrained])
|
|
231
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sql } from '../../data/db/sql.js'
|
|
2
|
+
import { pipeSystem } from '../../data/db/db.js'
|
|
3
|
+
import type { DB } from '../../data/db/index.js'
|
|
4
|
+
import { boundTransaction } from '../../data/write/audit/bound-transaction.js'
|
|
5
|
+
import { getContext } from '../../request/context/store.js'
|
|
6
|
+
import { resolveActorId } from '../../data/write/audit/actor.js'
|
|
7
|
+
import { getBaseLogger } from '../../core/logging/index.js'
|
|
8
|
+
import { recordFault, faultMessage } from './faults.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Writes an `Alert` audit record under the Flow's open trace step; does nothing when no step is open.
|
|
12
|
+
* In the Flow's transaction on that database when one is open (a savepoint, so a failed write cannot abort it; it
|
|
13
|
+
* commits or rolls back with it), else on the system channel.
|
|
14
|
+
*/
|
|
15
|
+
export async function auditAlert(verb: string, key: string, details: Record<string, unknown>, database?: string): Promise<void> {
|
|
16
|
+
const flow = getContext()
|
|
17
|
+
const trace = flow?.trace
|
|
18
|
+
const stepId = trace?.buffer.openStepId ?? null
|
|
19
|
+
if (flow === undefined || trace === undefined || trace === null || stepId === null) return
|
|
20
|
+
try {
|
|
21
|
+
const write = async (db: DB): Promise<void> => {
|
|
22
|
+
await db.execute(sql`
|
|
23
|
+
INSERT INTO audit_record
|
|
24
|
+
(trace_day, trace_id, trace_step_id, tenant_id, actor_id, entity, verb, entity_id, after, occurred_at)
|
|
25
|
+
VALUES (${trace.openedDay}, ${trace.id}, ${stepId}, ${flow.tenant}, ${resolveActorId(flow)},
|
|
26
|
+
'Alert', ${verb}, ${key}, ${JSON.stringify(details)}::jsonb, now())
|
|
27
|
+
`)
|
|
28
|
+
}
|
|
29
|
+
const tx = boundTransaction(flow, database)
|
|
30
|
+
if (tx === null) await write(pipeSystem(database))
|
|
31
|
+
else await tx.transaction(write)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
getBaseLogger().error({ err, traceId: trace.id }, `Failed to write the alert ${verb} audit record`)
|
|
34
|
+
void recordFault('audit-write', faultMessage(err), { entity: 'Alert', verb })
|
|
35
|
+
}
|
|
36
|
+
}
|