pipework 0.252.0 → 0.253.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/CHANGELOG.md +10 -0
- package/dist/infra/support/handle.d.ts +8 -1
- package/dist/infra/support/handle.d.ts.map +1 -1
- package/dist/infra/support/handle.js +9 -2
- package/dist/infra/support/handle.js.map +1 -1
- package/dist/infra/support/namespace.d.ts.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 +869 -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,40 @@
|
|
|
1
|
+
// Content hashing for step results. A result is identified by what it was made from,
|
|
2
|
+
// so two values hash alike exactly when their canonical JSON is the same.
|
|
3
|
+
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
|
|
6
|
+
/** JSON with object keys sorted at every depth; `undefined` members are dropped as in JSON.stringify. */
|
|
7
|
+
export function canonicalJson(value: unknown): string {
|
|
8
|
+
return JSON.stringify(canonicalize(value))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function canonicalize(value: unknown): unknown {
|
|
12
|
+
if (value === null || typeof value !== 'object') return value
|
|
13
|
+
if (Array.isArray(value)) return value.map(v => canonicalize(v === undefined ? null : v))
|
|
14
|
+
const toJson = (value as { toJSON?: () => unknown }).toJSON
|
|
15
|
+
if (typeof toJson === 'function') return canonicalize(toJson.call(value))
|
|
16
|
+
const out: Record<string, unknown> = {}
|
|
17
|
+
for (const key of Object.keys(value).sort()) {
|
|
18
|
+
const member = (value as Record<string, unknown>)[key]
|
|
19
|
+
if (member !== undefined) out[key] = canonicalize(member)
|
|
20
|
+
}
|
|
21
|
+
return out
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function sha256Hex(text: string): string {
|
|
25
|
+
return createHash('sha256').update(text).digest('hex')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The hash of a step result or an element: its canonical JSON. */
|
|
29
|
+
export function valueHash(value: unknown): string {
|
|
30
|
+
return sha256Hex(canonicalJson(value === undefined ? null : value))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The hash of a request input: a stored file (an object with a `sha256` string) hashes by that field alone. */
|
|
34
|
+
export function inputHash(value: unknown): string {
|
|
35
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
36
|
+
const sha = (value as { sha256?: unknown }).sha256
|
|
37
|
+
if (typeof sha === 'string') return sha256Hex(canonicalJson({ sha256: sha }))
|
|
38
|
+
}
|
|
39
|
+
return valueHash(value)
|
|
40
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { defineGraph } from './graph.js'
|
|
2
|
+
export { StepItemRunningError } from './errors.js'
|
|
3
|
+
export { steps } from './namespace.js'
|
|
4
|
+
export type {
|
|
5
|
+
GraphConfig, GraphStepDefinition, GraphStepContext, GraphForEachContext, GraphRunInfo, GraphFailure, GraphRunStatus,
|
|
6
|
+
RequestOptions, RequestResult, PlanOptions, PlanResult, StepPlan, StepCounts, TaskState, RunTask, RunStatus, EndedRun, EndedRunsOptions, DecideOptions, ScopeOptions, StepResultRecord, RemoveItemOptions, RemoveItemResult, StepGraph, GraphSelection, GraphHost,
|
|
7
|
+
} from './types.js'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineGraph } from './graph.js'
|
|
2
|
+
|
|
3
|
+
/** Step graphs over a job queue: results recorded by their inputs, only missing steps run. */
|
|
4
|
+
export const steps = {
|
|
5
|
+
/** Defines a graph of named steps over a jobs Queue. Pass it to `createWorkerSurface({ graphs })` to run its steps. */
|
|
6
|
+
define: defineGraph,
|
|
7
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// The three tables behind step graphs, all tenant-scoped (forced row-level security
|
|
2
|
+
// in generated migrations). A graph registers them at define() time, so a worker
|
|
3
|
+
// surface or `pipework generate` that has loaded a graph emits their DDL.
|
|
4
|
+
|
|
5
|
+
import { define } from '../../data/domain/index.js'
|
|
6
|
+
import { field } from '../../data/domain/field.js'
|
|
7
|
+
import type { DefinedTable } from '../../data/domain/types.js'
|
|
8
|
+
import { schema } from '../../data/schema/namespace.js'
|
|
9
|
+
import { index, uniqueIndex } from '../../data/db/idx/index.js'
|
|
10
|
+
|
|
11
|
+
export const STEP_RUN_STATUSES = ['running', 'done', 'failed', 'cancelled'] as const
|
|
12
|
+
export const STEP_TASK_STATUSES = ['blocked', 'queued', 'waiting', 'done', 'reused', 'failed', 'cancelled'] as const
|
|
13
|
+
|
|
14
|
+
/** Instance number of a step's single task, or of a fan-out step's expander. */
|
|
15
|
+
export const SINGLE = -1
|
|
16
|
+
|
|
17
|
+
export const stepRun = define('pipework_step_run', {
|
|
18
|
+
id: field.uuid().defaultRandom().primaryKey(),
|
|
19
|
+
tenantId: field.uuid().tenant(),
|
|
20
|
+
graph: field.text(),
|
|
21
|
+
item: field.text(),
|
|
22
|
+
targets: field.jsonb(schema.check.json()),
|
|
23
|
+
status: field.enum(STEP_RUN_STATUSES).default('running'),
|
|
24
|
+
priority: field.integer().default(0),
|
|
25
|
+
stepPriority: field.jsonb(schema.check.json()).nullable(),
|
|
26
|
+
inputs: field.jsonb(schema.check.json()),
|
|
27
|
+
traceId: field.uuid().nullable(),
|
|
28
|
+
traceRootId: field.uuid().nullable(),
|
|
29
|
+
traceStepId: field.uuid().nullable(),
|
|
30
|
+
actor: field.text().nullable(),
|
|
31
|
+
notified: field.boolean().default(false),
|
|
32
|
+
createdAt: field.timestamp().default('now'),
|
|
33
|
+
finishedAt: field.timestamp().nullable(),
|
|
34
|
+
error: field.jsonb(schema.check.json()).nullable(),
|
|
35
|
+
}, {
|
|
36
|
+
indexes: (t) => ({
|
|
37
|
+
byItem: index('idx_pipework_step_run_item').on(t['tenantId'], t['graph'], t['item']),
|
|
38
|
+
byFinished: index('idx_pipework_step_run_finished').on(t['finishedAt']),
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const stepTask = define('pipework_step_task', {
|
|
43
|
+
id: field.uuid().defaultRandom().primaryKey(),
|
|
44
|
+
tenantId: field.uuid().tenant(),
|
|
45
|
+
runId: field.uuid(),
|
|
46
|
+
step: field.text(),
|
|
47
|
+
instance: field.integer().default(SINGLE),
|
|
48
|
+
element: field.jsonb(schema.check.json()).nullable(),
|
|
49
|
+
status: field.enum(STEP_TASK_STATUSES).default('blocked'),
|
|
50
|
+
jobId: field.uuid().nullable(),
|
|
51
|
+
inputKey: field.text().nullable(),
|
|
52
|
+
resultId: field.uuid().nullable(),
|
|
53
|
+
error: field.jsonb(schema.check.json()).nullable(),
|
|
54
|
+
}, {
|
|
55
|
+
indexes: (t) => ({
|
|
56
|
+
byInstance: uniqueIndex('uq_pipework_step_task_instance').on(t['runId'], t['step'], t['instance']),
|
|
57
|
+
byJob: index('idx_pipework_step_task_job').on(t['jobId']),
|
|
58
|
+
}),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
export const stepResult = define('pipework_step_result', {
|
|
62
|
+
id: field.uuid().defaultRandom().primaryKey(),
|
|
63
|
+
tenantId: field.uuid().tenant(),
|
|
64
|
+
graph: field.text(),
|
|
65
|
+
item: field.text(),
|
|
66
|
+
step: field.text(),
|
|
67
|
+
version: field.integer(),
|
|
68
|
+
inputKey: field.text(),
|
|
69
|
+
instance: field.integer().default(SINGLE),
|
|
70
|
+
value: field.jsonb(schema.check.json()),
|
|
71
|
+
valueHash: field.text(),
|
|
72
|
+
createdAt: field.timestamp().default('now'),
|
|
73
|
+
}, {
|
|
74
|
+
indexes: (t) => ({
|
|
75
|
+
byKey: uniqueIndex('uq_pipework_step_result_key')
|
|
76
|
+
.on(t['tenantId'], t['graph'], t['item'], t['step'], t['version'], t['inputKey'], t['instance']),
|
|
77
|
+
byCreated: index('idx_pipework_step_result_created').on(t['createdAt']),
|
|
78
|
+
}),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
let registered = false
|
|
82
|
+
|
|
83
|
+
/** Marks the step tables as in use, so `pipework generate` emits them. */
|
|
84
|
+
export function registerStepTables(): void {
|
|
85
|
+
registered = true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Registered step table definitions — merged into `pipework generate` via internalDefinitions. */
|
|
89
|
+
export function stepDefinitions(): ReadonlyMap<string, DefinedTable> {
|
|
90
|
+
return registered
|
|
91
|
+
? new Map<string, DefinedTable>([
|
|
92
|
+
['pipework_step_run', stepRun as unknown as DefinedTable],
|
|
93
|
+
['pipework_step_task', stepTask as unknown as DefinedTable],
|
|
94
|
+
['pipework_step_result', stepResult as unknown as DefinedTable],
|
|
95
|
+
])
|
|
96
|
+
: new Map()
|
|
97
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import type { DB } from '../../data/db/index.js'
|
|
2
|
+
import type { Queue } from '../jobs/queue.js'
|
|
3
|
+
import type { Handler } from '../../request/di/types.js'
|
|
4
|
+
import type { Manifold } from '../../core/pipework.js'
|
|
5
|
+
import type { DeadLetterInfo } from '../jobs/queue.js'
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- a recorded result has the shape its step returned; the graph cannot know it, and callers read fields off it directly
|
|
8
|
+
export type StepValue = any
|
|
9
|
+
|
|
10
|
+
/** What a step's `run` (and `autoAccept`) is handed. */
|
|
11
|
+
export interface GraphStepContext {
|
|
12
|
+
/** Each needed request input by its value; each needed step by its result — an array in instance order for a fan-out step, or that instance's one result for a step named in `byElement`. */
|
|
13
|
+
readonly needs: Readonly<Record<string, StepValue>>
|
|
14
|
+
/** The element this instance was made for; undefined outside a fan-out step. */
|
|
15
|
+
readonly element: StepValue
|
|
16
|
+
/** The instance number; null outside a fan-out step. */
|
|
17
|
+
readonly index: number | null
|
|
18
|
+
readonly item: string
|
|
19
|
+
/** The request inputs this step declared in `needs`. */
|
|
20
|
+
readonly inputs: Readonly<Record<string, unknown>>
|
|
21
|
+
readonly runId: string
|
|
22
|
+
/** The step job's 1-based attempt, counting an attempt a lost worker used. Not available in `autoAccept`. */
|
|
23
|
+
readonly attempt: number
|
|
24
|
+
/** The step job's attempt limit: the step's `maxAttempts`, else the queue's default. Not available in `autoAccept`. */
|
|
25
|
+
readonly maxAttempts: number
|
|
26
|
+
/** Aborted when `cancel()` is called in this process while the step runs. */
|
|
27
|
+
readonly signal: AbortSignal
|
|
28
|
+
/**
|
|
29
|
+
* Runs `fn` in its own short transaction on the graph's database, as the run's tenant (RLS applies),
|
|
30
|
+
* and commits it on return. `run` itself holds no transaction: it is never open while the step waits.
|
|
31
|
+
* In `autoAccept`, `fn` runs in the transaction that advances the run.
|
|
32
|
+
*/
|
|
33
|
+
checkpoint<R>(fn: (tx: DB) => Promise<R>): Promise<R>
|
|
34
|
+
/**
|
|
35
|
+
* Registers `fn` to run, in registration order, in the transaction that records this step's result:
|
|
36
|
+
* the rows it writes and the result commit together or not at all. If `fn` throws, neither is kept,
|
|
37
|
+
* the attempt fails and the task stays queued. Not available in `autoAccept` (throws).
|
|
38
|
+
*/
|
|
39
|
+
withResult(fn: (tx: DB) => Promise<void>): void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** What a `forEach` is handed: the step's needs, before any element exists. */
|
|
43
|
+
export type GraphForEachContext = Omit<GraphStepContext, 'element' | 'index' | 'signal' | 'checkpoint' | 'withResult' | 'attempt' | 'maxAttempts'>
|
|
44
|
+
|
|
45
|
+
export interface GraphStepDefinition {
|
|
46
|
+
/** Part of the content key: bump it and the step (and what depends on it) runs again. */
|
|
47
|
+
readonly version: number
|
|
48
|
+
/** Request inputs and other steps this step reads. */
|
|
49
|
+
readonly needs?: readonly string[]
|
|
50
|
+
/** Makes this a fan-out step: one instance per element of the returned list. */
|
|
51
|
+
readonly forEach?: (ctx: GraphForEachContext) => readonly unknown[] | Promise<readonly unknown[]>
|
|
52
|
+
/**
|
|
53
|
+
* Names from `needs` that only choose the list. `forEach` sees them; they are not part of an instance's
|
|
54
|
+
* content key, and `run` and `autoAccept` do not see them. They are still needs: the step waits for them.
|
|
55
|
+
*/
|
|
56
|
+
readonly forEachOnly?: readonly string[]
|
|
57
|
+
/**
|
|
58
|
+
* Names from `needs` that are fan-out steps. Makes this step a fan-out over the same list as they: it has
|
|
59
|
+
* no `forEach`; instance i's element is their instance i's element, it waits only for their instance i,
|
|
60
|
+
* its content key hashes only their instance i's result, and `needs[name]` holds that one result (not an
|
|
61
|
+
* array). Every name must share one list: each is a step with `forEach` or itself has `byElement` leading
|
|
62
|
+
* back to the same `forEach` step. A step needing a `byElement` step without listing it here gets the
|
|
63
|
+
* whole array, as for any fan-out step.
|
|
64
|
+
*/
|
|
65
|
+
readonly byElement?: readonly string[]
|
|
66
|
+
/** The step's work; its JSON return value is the recorded result. */
|
|
67
|
+
readonly run?: (ctx: GraphStepContext) => unknown
|
|
68
|
+
/** The step waits for `decide()`, holding no worker. */
|
|
69
|
+
readonly person?: boolean
|
|
70
|
+
/** Person steps: a returned value (not undefined) records it and skips the wait. */
|
|
71
|
+
readonly autoAccept?: (ctx: Omit<GraphStepContext, 'attempt' | 'maxAttempts'>) => unknown
|
|
72
|
+
/**
|
|
73
|
+
* The priority of this step's own jobs (an integer), in place of the run's, for every run and every instance of a
|
|
74
|
+
* fan-out step. Higher is claimed first, across runs, per worker class. A measurement step sets one below every
|
|
75
|
+
* run priority in use: it is claimed after every other step, and its run still finishes. The steps it needs and
|
|
76
|
+
* the steps that need it keep the run's priority, as does the run's finish job.
|
|
77
|
+
*/
|
|
78
|
+
readonly priority?: number
|
|
79
|
+
/** Attempts the step's job gets. Default: the queue's. */
|
|
80
|
+
readonly maxAttempts?: number
|
|
81
|
+
/**
|
|
82
|
+
* The longest one try of `run` may take, in milliseconds (a positive integer). A try that exceeds it is aborted
|
|
83
|
+
* through `signal` and counted as a failed attempt, so `maxAttempts` and dead-lettering apply; a `run` that
|
|
84
|
+
* ignores its signal still ends the try on time. The class's job timeout, if any, still applies on top.
|
|
85
|
+
*/
|
|
86
|
+
readonly timeoutMs?: number
|
|
87
|
+
/**
|
|
88
|
+
* The class of worker that takes this step: letters, digits, "_" and "-", starting with a letter. Default "default".
|
|
89
|
+
* The step's job type is `pipework.step.<graph>` for the default class, `pipework.step.<graph>:<class>` otherwise.
|
|
90
|
+
*/
|
|
91
|
+
readonly workers?: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The run a callback is about. */
|
|
95
|
+
export interface GraphRunInfo {
|
|
96
|
+
readonly runId: string
|
|
97
|
+
readonly item: string
|
|
98
|
+
readonly tenant: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface GraphFailure extends GraphRunInfo {
|
|
102
|
+
readonly step: string
|
|
103
|
+
readonly error: unknown
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface GraphConfig {
|
|
107
|
+
/** The graph's name; its step jobs have type `pipework.step.<name>`. */
|
|
108
|
+
readonly name: string
|
|
109
|
+
/** The queue whose table holds the step jobs. A worker surface running them must read the same table. */
|
|
110
|
+
readonly queue: Queue
|
|
111
|
+
/** Request input names a step may list in `needs`. */
|
|
112
|
+
readonly inputs?: readonly string[]
|
|
113
|
+
readonly steps: Readonly<Record<string, GraphStepDefinition>>
|
|
114
|
+
/** Default priority of a run. Default 0. */
|
|
115
|
+
readonly priority?: number
|
|
116
|
+
/** `sweep()` deletes results older than this many days. Default: never. */
|
|
117
|
+
readonly resultRetentionDays?: number
|
|
118
|
+
/** Session variable the tenant policies read. Default: the Manifold's `rls.sessionVar`, else `pipework.tenant_id`. */
|
|
119
|
+
readonly sessionVar?: string
|
|
120
|
+
/** Called once when a run is done, in the run's tenant Flow and a transaction. A throw fails the attempt that finished the run. */
|
|
121
|
+
readonly onDone?: (run: GraphRunInfo) => void | Promise<void>
|
|
122
|
+
/** Called once when a run fails, in the run's tenant Flow, in a savepoint; a throw is logged and swallowed. With tracing on it runs in its own job trace, under the failed step job's parent, inside a `pipework/steps:on-failed` step. */
|
|
123
|
+
readonly onFailed?: (failure: GraphFailure) => void | Promise<void>
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface RequestOptions {
|
|
127
|
+
readonly item: string
|
|
128
|
+
/** The steps whose results are wanted. */
|
|
129
|
+
readonly targets: readonly string[]
|
|
130
|
+
readonly inputs?: Readonly<Record<string, unknown>>
|
|
131
|
+
readonly priority?: number
|
|
132
|
+
/** Priorities for named steps of this run only, in place of their definitions' `priority` and the run's. Keys must be steps of the graph; values integers. */
|
|
133
|
+
readonly stepPriority?: Readonly<Record<string, number>>
|
|
134
|
+
/** Default: the active Flow's tenant. */
|
|
135
|
+
readonly tenant?: string
|
|
136
|
+
/** Record the requesting Flow's actor (`auth.userId`) on the run; its step jobs, onDone and onFailed run with `auth = { userId }`. Default true. */
|
|
137
|
+
readonly carryActor?: boolean
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface RequestResult {
|
|
141
|
+
readonly runId: string
|
|
142
|
+
/** Tasks already satisfied by a recorded result at the moment of the request. */
|
|
143
|
+
readonly reused: number
|
|
144
|
+
/** Step jobs the request enqueued. */
|
|
145
|
+
readonly queued: number
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface PlanOptions {
|
|
149
|
+
readonly item: string
|
|
150
|
+
readonly targets: readonly string[]
|
|
151
|
+
readonly inputs?: Readonly<Record<string, unknown>>
|
|
152
|
+
readonly tenant?: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface StepPlan {
|
|
156
|
+
/** Instances whose result is recorded. */
|
|
157
|
+
readonly reusable: number
|
|
158
|
+
/** Instances that must run; null when a fan-out step's list is not yet known. */
|
|
159
|
+
readonly toRun: number | null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface PlanResult {
|
|
163
|
+
readonly steps: Readonly<Record<string, StepPlan>>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface StepCounts {
|
|
167
|
+
readonly total: number
|
|
168
|
+
readonly done: number
|
|
169
|
+
readonly reused: number
|
|
170
|
+
readonly running: number
|
|
171
|
+
readonly waiting: number
|
|
172
|
+
readonly failed: number
|
|
173
|
+
readonly cancelled: number
|
|
174
|
+
/** Fan-out steps only: whether the step's list is known, i.e. its instances exist. */
|
|
175
|
+
readonly listKnown?: boolean
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A task's state, classified as the step counts are. */
|
|
179
|
+
export type TaskState = 'done' | 'reused' | 'running' | 'waiting' | 'failed' | 'cancelled'
|
|
180
|
+
|
|
181
|
+
/** One task of a run. */
|
|
182
|
+
export interface RunTask {
|
|
183
|
+
readonly step: string
|
|
184
|
+
/** The fan-out instance's index; -1 for a single step and for a fan-out's list task. */
|
|
185
|
+
readonly instance: number
|
|
186
|
+
/** The fan-out list's item for this instance; null for instance -1. */
|
|
187
|
+
readonly element: unknown
|
|
188
|
+
readonly state: TaskState
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type GraphRunStatus = 'running' | 'done' | 'failed' | 'cancelled'
|
|
192
|
+
|
|
193
|
+
export interface RunStatus {
|
|
194
|
+
readonly status: GraphRunStatus
|
|
195
|
+
readonly steps: Readonly<Record<string, StepCounts>>
|
|
196
|
+
/** Every task of the run, in step order then instance order, fan-out list tasks (instance -1) included. */
|
|
197
|
+
readonly tasks: readonly RunTask[]
|
|
198
|
+
/** The inputs the run was requested with. */
|
|
199
|
+
readonly inputs: Readonly<Record<string, unknown>>
|
|
200
|
+
/** When the run ended; null while it runs. */
|
|
201
|
+
readonly finishedAt: Date | null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface EndedRunsOptions extends ScopeOptions {
|
|
205
|
+
/** Only these runs. */
|
|
206
|
+
readonly ids?: readonly string[]
|
|
207
|
+
/** Only runs that finished before this time. */
|
|
208
|
+
readonly finishedBefore?: Date
|
|
209
|
+
/** At most this many, oldest finish first. Default 1000. */
|
|
210
|
+
readonly limit?: number
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A run of the graph that is no longer running. */
|
|
214
|
+
export interface EndedRun {
|
|
215
|
+
readonly id: string
|
|
216
|
+
readonly tenant: string
|
|
217
|
+
readonly item: string
|
|
218
|
+
readonly status: Exclude<GraphRunStatus, 'running'>
|
|
219
|
+
readonly finishedAt: Date
|
|
220
|
+
/** The row's delivery flag: for a done run, true once onDone has run (or the graph has none); set at once for a failed run, so it does not show that onFailed ran. */
|
|
221
|
+
readonly notified: boolean
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The tenant a call acts for; default the active Flow's. Needed under row-level security on a connection that has not set it. */
|
|
225
|
+
export interface ScopeOptions {
|
|
226
|
+
readonly tenant?: string
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** One recorded result of a step. `instance` is -1 for a step that does not fan out. */
|
|
230
|
+
export interface StepResultRecord {
|
|
231
|
+
readonly id: string
|
|
232
|
+
readonly step: string
|
|
233
|
+
readonly version: number
|
|
234
|
+
readonly instance: number
|
|
235
|
+
readonly value: unknown
|
|
236
|
+
readonly createdAt: Date
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface RemoveItemOptions extends ScopeOptions {
|
|
240
|
+
/** A run still in progress: 'refuse' (default) throws StepItemRunningError and deletes nothing; 'cancel' cancels it first. */
|
|
241
|
+
readonly running?: 'refuse' | 'cancel'
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface RemoveItemResult {
|
|
245
|
+
readonly runs: number
|
|
246
|
+
readonly tasks: number
|
|
247
|
+
readonly results: number
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface DecideOptions extends ScopeOptions {
|
|
251
|
+
readonly runId: string
|
|
252
|
+
readonly step: string
|
|
253
|
+
/** The instance, for a fan-out person step. */
|
|
254
|
+
readonly index?: number
|
|
255
|
+
readonly value: unknown
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Where a worker surface runs a graph: the Manifold and the database name its handlers use. */
|
|
259
|
+
export interface GraphHost {
|
|
260
|
+
readonly instance: Manifold
|
|
261
|
+
readonly database: string | undefined
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Some classes of a graph's steps, for a worker surface to take. */
|
|
265
|
+
export interface GraphSelection {
|
|
266
|
+
readonly graph: StepGraph
|
|
267
|
+
readonly classes: readonly string[]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface StepGraph {
|
|
271
|
+
readonly name: string
|
|
272
|
+
/** `pipework.step.<name>`: the job type of the default class. */
|
|
273
|
+
readonly jobType: string
|
|
274
|
+
/** The job type of every class the graph's steps use. */
|
|
275
|
+
readonly jobTypes: readonly string[]
|
|
276
|
+
/** The queue the step jobs are enqueued on. */
|
|
277
|
+
readonly queue: Queue
|
|
278
|
+
/** Asks for the targets of an item. Only steps without a recorded result for their inputs run. */
|
|
279
|
+
request(db: DB, options: RequestOptions): Promise<RequestResult>
|
|
280
|
+
/** What a request would reuse and what it would run, without running anything. */
|
|
281
|
+
plan(db: DB, options: PlanOptions): Promise<PlanResult>
|
|
282
|
+
status(db: DB, runId: string, options?: ScopeOptions): Promise<RunStatus>
|
|
283
|
+
/** Cancels a running run: pending work is cancelled, no callback fires. */
|
|
284
|
+
cancel(db: DB, runId: string, options?: ScopeOptions): Promise<void>
|
|
285
|
+
/** Records a person's value on a waiting person step, or replaces it on a finished one. */
|
|
286
|
+
decide(db: DB, options: DecideOptions): Promise<void>
|
|
287
|
+
/** Resolves with the run's final status, or 'timeout'. */
|
|
288
|
+
wait(db: DB, runId: string, options: { readonly timeoutMs: number; readonly tenant?: string }): Promise<GraphRunStatus | 'timeout'>
|
|
289
|
+
/**
|
|
290
|
+
* Deletes, for one tenant, the results and the finished runs (with their tasks) older than `resultRetentionDays`;
|
|
291
|
+
* returns how many results. Runs in `options.tenant` or the Flow's; under forced row-level security a sweep with
|
|
292
|
+
* neither sees no rows, so sweep each tenant. On an owner connection with no tenant it sweeps all of them.
|
|
293
|
+
*/
|
|
294
|
+
sweep(db: DB, options?: ScopeOptions): Promise<number>
|
|
295
|
+
/**
|
|
296
|
+
* The runs of this graph that have ended, oldest finish first, in `options.tenant` or the Flow's; under forced
|
|
297
|
+
* row-level security a call with neither sees no rows. On an owner connection with no tenant it lists every tenant's.
|
|
298
|
+
*/
|
|
299
|
+
ended(db: DB, options?: EndedRunsOptions): Promise<readonly EndedRun[]>
|
|
300
|
+
/** Deletes a tenant's runs, tasks and results. */
|
|
301
|
+
removeTenant(db: DB, tenantId: string): Promise<void>
|
|
302
|
+
/** The results recorded for one item of this graph, in the tenant; their values hold whatever the steps returned (e.g. file references to remove). */
|
|
303
|
+
results(db: DB, item: string, options?: ScopeOptions): Promise<readonly StepResultRecord[]>
|
|
304
|
+
/** Deletes one item's runs, tasks and results in the caller's transaction and tenant. A done run not yet delivered gets no onDone. */
|
|
305
|
+
removeItem(db: DB, item: string, options?: RemoveItemOptions): Promise<RemoveItemResult>
|
|
306
|
+
/**
|
|
307
|
+
* A selection of the steps of the named worker classes, for `WorkerSurfaceConfig.graphs`; the graph itself selects all.
|
|
308
|
+
* Concurrency is per surface: run one worker surface per class, each with its own `concurrency`.
|
|
309
|
+
* Throws naming the graph and class when no step uses a class.
|
|
310
|
+
*/
|
|
311
|
+
workers(...classes: string[]): GraphSelection
|
|
312
|
+
/** Used by the worker surface. */
|
|
313
|
+
readonly worker: {
|
|
314
|
+
/** One handler per class, or per selected class. */
|
|
315
|
+
handlers(database: string | undefined, classes?: readonly string[]): Handler<Record<string, unknown>, unknown>[]
|
|
316
|
+
onDeadLetter(tx: DB, job: DeadLetterInfo, host: GraphHost): Promise<void>
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Installs the audit hash chain: head/retired bookkeeping tables, the
|
|
2
|
+
// canonical-form function, and the triggers on `audit_record`. Everything is
|
|
3
|
+
// CREATE OR REPLACE / IF NOT EXISTS, so installing twice is a no-op.
|
|
4
|
+
//
|
|
5
|
+
// The chain is written only by the BEFORE INSERT trigger: it locks the tenant's
|
|
6
|
+
// head row, so concurrent writers to one chain queue behind each other and the
|
|
7
|
+
// sequence is gapless (a rolled-back transaction releases the lock and leaves
|
|
8
|
+
// no gap). A second trigger rejects UPDATE and DELETE; partition DROP, which
|
|
9
|
+
// is how retention works, does not fire row triggers.
|
|
10
|
+
|
|
11
|
+
import { sql } from '../../data/db/sql.js'
|
|
12
|
+
import type { DB } from '../../data/db/index.js'
|
|
13
|
+
import { quoteIdentifier } from '../../data/db/identifiers.js'
|
|
14
|
+
import { MAINTENANCE_SETTING } from '../../data/db/maintenance.js'
|
|
15
|
+
|
|
16
|
+
/** Chain key of rows with no tenant — they form their own chain. */
|
|
17
|
+
export const NULL_TENANT_CHAIN_KEY = '00000000-0000-0000-0000-000000000000'
|
|
18
|
+
|
|
19
|
+
/** Set transaction-locally by `audit.removeTenant` to the one tenant whose rows may be deleted. */
|
|
20
|
+
export const AUDIT_ERASE_SETTING = 'pipework.audit_erase_tenant'
|
|
21
|
+
|
|
22
|
+
export const CHAIN_HEAD_TABLE = 'pipework_audit_chain_head'
|
|
23
|
+
export const CHAIN_RETIRED_TABLE = 'pipework_audit_chain_retired'
|
|
24
|
+
|
|
25
|
+
/** SQL expression for a row's chain key; the chain index is built on exactly this. */
|
|
26
|
+
export const CHAIN_KEY_SQL = `coalesce(tenant_id, '${NULL_TENANT_CHAIN_KEY}'::uuid)`
|
|
27
|
+
|
|
28
|
+
const GENESIS_HASH_HEX = '00'.repeat(32)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The text hashed for one row. `jsonb_build_array(...)::text` of, in order:
|
|
32
|
+
* chain_seq, id, trace_day (YYYY-MM-DD), trace_id, trace_step_id, tenant_id,
|
|
33
|
+
* actor_id, entity, verb, before, after, captured_inputs, occurred_at (UTC,
|
|
34
|
+
* `YYYY-MM-DD"T"HH24:MI:SS.US"Z"`), then entity_id only when it is not null.
|
|
35
|
+
* A row without an entity_id (read-touches, rows written before the column
|
|
36
|
+
* existed) hashes exactly as it always did. Independent of the session TimeZone.
|
|
37
|
+
*/
|
|
38
|
+
export const CANONICAL_FUNCTION_SQL = `
|
|
39
|
+
CREATE OR REPLACE FUNCTION pipework_audit_record_canonical(r audit_record) RETURNS text
|
|
40
|
+
LANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $$
|
|
41
|
+
SELECT (jsonb_build_array(
|
|
42
|
+
r.chain_seq, r.id, to_char(r.trace_day, 'YYYY-MM-DD'), r.trace_id, r.trace_step_id,
|
|
43
|
+
r.tenant_id, r.actor_id, r.entity, r.verb, r.before, r.after, r.captured_inputs,
|
|
44
|
+
to_char(r.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')
|
|
45
|
+
) || CASE WHEN r.entity_id IS NULL THEN '[]'::jsonb ELSE jsonb_build_array(r.entity_id) END)::text
|
|
46
|
+
$$`
|
|
47
|
+
|
|
48
|
+
function insertTriggerFunctionSql(schema: string): string {
|
|
49
|
+
return `
|
|
50
|
+
CREATE OR REPLACE FUNCTION pipework_audit_chain_before_insert() RETURNS trigger
|
|
51
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog, ${schema} AS $$
|
|
52
|
+
DECLARE
|
|
53
|
+
k uuid := coalesce(NEW.tenant_id, '${NULL_TENANT_CHAIN_KEY}'::uuid);
|
|
54
|
+
h record;
|
|
55
|
+
BEGIN
|
|
56
|
+
INSERT INTO ${CHAIN_HEAD_TABLE} (chain_key, seq, hash)
|
|
57
|
+
VALUES (k, 0, decode('${GENESIS_HASH_HEX}', 'hex'))
|
|
58
|
+
ON CONFLICT DO NOTHING;
|
|
59
|
+
SELECT seq, hash INTO h FROM ${CHAIN_HEAD_TABLE} WHERE chain_key = k FOR UPDATE;
|
|
60
|
+
NEW.chain_seq := h.seq + 1;
|
|
61
|
+
NEW.prev_hash := encode(h.hash, 'hex');
|
|
62
|
+
NEW.hash := encode(sha256(h.hash || convert_to(pipework_audit_record_canonical(NEW), 'UTF8')), 'hex');
|
|
63
|
+
UPDATE ${CHAIN_HEAD_TABLE} SET seq = NEW.chain_seq, hash = decode(NEW.hash, 'hex') WHERE chain_key = k;
|
|
64
|
+
RETURN NEW;
|
|
65
|
+
END
|
|
66
|
+
$$`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const APPEND_ONLY_FUNCTION_SQL = `
|
|
70
|
+
CREATE OR REPLACE FUNCTION pipework_audit_chain_append_only() RETURNS trigger
|
|
71
|
+
LANGUAGE plpgsql SET search_path = pg_catalog AS $$
|
|
72
|
+
BEGIN
|
|
73
|
+
IF TG_OP = 'DELETE'
|
|
74
|
+
AND current_setting('${MAINTENANCE_SETTING}', true) = 'on'
|
|
75
|
+
AND OLD.tenant_id::text = current_setting('${AUDIT_ERASE_SETTING}', true) THEN
|
|
76
|
+
RETURN OLD;
|
|
77
|
+
END IF;
|
|
78
|
+
RAISE EXCEPTION 'audit_record is append-only: % is not allowed', TG_OP
|
|
79
|
+
USING ERRCODE = 'restrict_violation';
|
|
80
|
+
END
|
|
81
|
+
$$`
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Install the audit hash chain on `audit_record`. The table must exist.
|
|
85
|
+
* Idempotent.
|
|
86
|
+
*/
|
|
87
|
+
export async function installAuditChain(db: DB): Promise<void> {
|
|
88
|
+
const schemaRows = await db.execute(sql`SELECT current_schema() AS schema`)
|
|
89
|
+
const schema = quoteIdentifier(String(schemaRows[0]!['schema']), 'schema')
|
|
90
|
+
|
|
91
|
+
await db.execute(sql.raw(
|
|
92
|
+
`CREATE TABLE IF NOT EXISTS ${CHAIN_HEAD_TABLE} (` +
|
|
93
|
+
`chain_key uuid PRIMARY KEY, seq bigint NOT NULL, hash bytea NOT NULL)`,
|
|
94
|
+
))
|
|
95
|
+
await db.execute(sql.raw(
|
|
96
|
+
`CREATE TABLE IF NOT EXISTS ${CHAIN_RETIRED_TABLE} (` +
|
|
97
|
+
`chain_key uuid NOT NULL, seq bigint NOT NULL, hash bytea NOT NULL, PRIMARY KEY (chain_key, seq))`,
|
|
98
|
+
))
|
|
99
|
+
await db.execute(sql.raw(CANONICAL_FUNCTION_SQL))
|
|
100
|
+
await db.execute(sql.raw(insertTriggerFunctionSql(schema)))
|
|
101
|
+
await db.execute(sql.raw(APPEND_ONLY_FUNCTION_SQL))
|
|
102
|
+
await db.execute(sql.raw(
|
|
103
|
+
`CREATE OR REPLACE TRIGGER pipework_audit_chain_insert BEFORE INSERT ON audit_record ` +
|
|
104
|
+
`FOR EACH ROW EXECUTE FUNCTION pipework_audit_chain_before_insert()`,
|
|
105
|
+
))
|
|
106
|
+
await db.execute(sql.raw(
|
|
107
|
+
`CREATE OR REPLACE TRIGGER pipework_audit_chain_append_only BEFORE UPDATE OR DELETE ON audit_record ` +
|
|
108
|
+
`FOR EACH ROW EXECUTE FUNCTION pipework_audit_chain_append_only()`,
|
|
109
|
+
))
|
|
110
|
+
await db.execute(sql.raw(
|
|
111
|
+
`CREATE INDEX IF NOT EXISTS audit_record_chain_idx ON audit_record ((${CHAIN_KEY_SQL}), chain_seq)`,
|
|
112
|
+
))
|
|
113
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Retention for the audit chain: before an `audit_record` day partition is
|
|
2
|
+
// dropped, its (seq, hash) pairs move to the retired table so the rows that
|
|
3
|
+
// remain still link to the ones that went.
|
|
4
|
+
|
|
5
|
+
import { sql } from '../../data/db/sql.js'
|
|
6
|
+
import type { DB } from '../../data/db/index.js'
|
|
7
|
+
import { CHAIN_HEAD_TABLE, CHAIN_RETIRED_TABLE, CHAIN_KEY_SQL } from './install.js'
|
|
8
|
+
|
|
9
|
+
/** True when the chain tables exist, i.e. the chain was installed on this database. */
|
|
10
|
+
export async function auditChainInstalled(db: DB): Promise<boolean> {
|
|
11
|
+
const rows = await db.execute(sql`
|
|
12
|
+
SELECT to_regclass(${CHAIN_HEAD_TABLE}) IS NOT NULL
|
|
13
|
+
AND to_regclass(${CHAIN_RETIRED_TABLE}) IS NOT NULL AS installed
|
|
14
|
+
`)
|
|
15
|
+
return rows[0]?.['installed'] === true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Removes one tenant's chain: its head and its retired entries. Run inside the
|
|
20
|
+
* same maintenance transaction that deletes the tenant's records.
|
|
21
|
+
*/
|
|
22
|
+
export async function removeTenantChain(tx: DB, tenantId: string): Promise<void> {
|
|
23
|
+
await tx.execute(sql`DELETE FROM ${sql.raw(CHAIN_HEAD_TABLE)} WHERE chain_key = ${tenantId}::uuid`)
|
|
24
|
+
await tx.execute(sql`DELETE FROM ${sql.raw(CHAIN_RETIRED_TABLE)} WHERE chain_key = ${tenantId}::uuid`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Retire and drop one audit partition. Run inside a transaction. `child` is the
|
|
29
|
+
* already-quoted partition name.
|
|
30
|
+
*
|
|
31
|
+
* After the drop, a chain keeps only the retired entries at or above
|
|
32
|
+
* `(lowest remaining seq) - 1` — the one entry the oldest remaining row links
|
|
33
|
+
* to. A chain with no rows left keeps just its highest retired entry.
|
|
34
|
+
*/
|
|
35
|
+
export async function retireAuditPartition(tx: DB, child: string): Promise<void> {
|
|
36
|
+
// The pruning DELETE below scans every audit partition; with a few hundred
|
|
37
|
+
// partitions Postgres spends tens of seconds JIT-compiling it (measured 10s
|
|
38
|
+
// with no row-level security, 20s with it) to save milliseconds.
|
|
39
|
+
await tx.execute(sql`SELECT set_config('jit', 'off', true)`)
|
|
40
|
+
await tx.execute(sql.raw(
|
|
41
|
+
`INSERT INTO ${CHAIN_RETIRED_TABLE} (chain_key, seq, hash) ` +
|
|
42
|
+
`SELECT ${CHAIN_KEY_SQL}, chain_seq, decode(hash, 'hex') FROM ${child} ` +
|
|
43
|
+
`WHERE chain_seq IS NOT NULL ON CONFLICT DO NOTHING`,
|
|
44
|
+
))
|
|
45
|
+
await tx.execute(sql.raw(`DROP TABLE IF EXISTS ${child}`))
|
|
46
|
+
await tx.execute(sql.raw(
|
|
47
|
+
`DELETE FROM ${CHAIN_RETIRED_TABLE} r WHERE r.seq < coalesce(` +
|
|
48
|
+
`(SELECT min(a.chain_seq) - 1 FROM audit_record a ` +
|
|
49
|
+
`WHERE ${CHAIN_KEY_SQL.replace('tenant_id', 'a.tenant_id')} = r.chain_key AND a.chain_seq IS NOT NULL), ` +
|
|
50
|
+
`(SELECT max(x.seq) FROM ${CHAIN_RETIRED_TABLE} x WHERE x.chain_key = r.chain_key))`,
|
|
51
|
+
))
|
|
52
|
+
}
|