@blamejs/blamejs-shop 0.4.30 → 0.4.32
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 +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/checkout.js +8 -0
- package/lib/order.js +71 -11
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -70,6 +70,7 @@ var lazyRequire = require("./lazy-require");
|
|
|
70
70
|
var safeAsync = require("./safe-async");
|
|
71
71
|
var safeJson = require("./safe-json");
|
|
72
72
|
var safeSql = require("./safe-sql");
|
|
73
|
+
var sql = require("./sql");
|
|
73
74
|
var validateOpts = require("./validate-opts");
|
|
74
75
|
var { defineClass } = require("./framework-error");
|
|
75
76
|
|
|
@@ -84,6 +85,12 @@ var DEFAULT_MAX_ATTEMPTS = 10;
|
|
|
84
85
|
var DEFAULT_BACKOFF_INITIAL = C.TIME.seconds(1);
|
|
85
86
|
var DEFAULT_BACKOFF_MAX = C.TIME.minutes(5);
|
|
86
87
|
var DEFAULT_BACKOFF_FACTOR = 2; // multiplier, not bytes
|
|
88
|
+
// Lease after which an in-flight row is treated as stranded by a crashed
|
|
89
|
+
// publisher and reclaimed to 'pending'. Must exceed the longest expected
|
|
90
|
+
// publish so a slow-but-live publish isn't reclaimed mid-flight (a reclaim
|
|
91
|
+
// then re-publish is a duplicate, which at-least-once tolerates, but a tight
|
|
92
|
+
// lease makes duplicates routine). Default 5 min, matching backoff.maxMs.
|
|
93
|
+
var DEFAULT_CLAIM_RECLAIM_MS = C.TIME.minutes(5);
|
|
87
94
|
var TOPIC_MAX_LEN = C.BYTES.bytes(255);
|
|
88
95
|
var KEY_MAX_LEN = C.BYTES.bytes(255);
|
|
89
96
|
|
|
@@ -93,6 +100,15 @@ function _validateTableName(name) {
|
|
|
93
100
|
return safeSql.quoteIdentifier(name);
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
// Map the operator backend's dialect tag to the b.sql dialect vocabulary.
|
|
104
|
+
// b.sql's terminal toExternalSql() then emits $1..$N for postgres and `?`
|
|
105
|
+
// for sqlite / mysql, matching what the operator-supplied driver expects.
|
|
106
|
+
function _sqlDialect(externalDb) {
|
|
107
|
+
var d = externalDb && externalDb.dialect;
|
|
108
|
+
if (d === "postgres" || d === "mysql") return d;
|
|
109
|
+
return "sqlite";
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
function _utcNowExpr(externalDb) {
|
|
97
113
|
// The framework's externalDb backends wrap Postgres + SQLite. Both
|
|
98
114
|
// accept a parameterized timestamp via JS Date → ISO string for
|
|
@@ -187,7 +203,7 @@ function create(opts) {
|
|
|
187
203
|
validateOpts.requireObject(opts, "outbox", OutboxError);
|
|
188
204
|
validateOpts(opts, [
|
|
189
205
|
"externalDb", "table", "publisher",
|
|
190
|
-
"pollIntervalMs", "batchSize", "maxAttempts",
|
|
206
|
+
"pollIntervalMs", "batchSize", "maxAttempts", "claimReclaimMs",
|
|
191
207
|
"retryBackoff", "audit", "name",
|
|
192
208
|
"envelope", "connectorName", "connectorVersion", "dbName",
|
|
193
209
|
], "outbox.create");
|
|
@@ -198,7 +214,10 @@ function create(opts) {
|
|
|
198
214
|
}
|
|
199
215
|
validateOpts.requireNonEmptyString(opts.table,
|
|
200
216
|
"outbox.create: table", OutboxError, "outbox/bad-table");
|
|
201
|
-
|
|
217
|
+
// Validate the table identifier at create-time so a bad name throws at
|
|
218
|
+
// boot, not at first query. b.sql re-quotes the name by construction on
|
|
219
|
+
// every emitted statement (the builder owns identifier quoting now).
|
|
220
|
+
_validateTableName(opts.table);
|
|
202
221
|
|
|
203
222
|
if (typeof opts.publisher !== "function") {
|
|
204
223
|
throw new OutboxError("outbox/bad-publisher",
|
|
@@ -210,10 +229,13 @@ function create(opts) {
|
|
|
210
229
|
"outbox.create: batchSize", OutboxError, "outbox/bad-opts");
|
|
211
230
|
validateOpts.optionalPositiveFinite(opts.maxAttempts,
|
|
212
231
|
"outbox.create: maxAttempts", OutboxError, "outbox/bad-opts");
|
|
232
|
+
validateOpts.optionalPositiveFinite(opts.claimReclaimMs,
|
|
233
|
+
"outbox.create: claimReclaimMs", OutboxError, "outbox/bad-opts");
|
|
213
234
|
|
|
214
235
|
var pollIntervalMs = opts.pollIntervalMs || DEFAULT_POLL_MS;
|
|
215
236
|
var batchSize = opts.batchSize || DEFAULT_BATCH_SIZE;
|
|
216
237
|
var maxAttempts = opts.maxAttempts || DEFAULT_MAX_ATTEMPTS;
|
|
238
|
+
var claimReclaimMs = opts.claimReclaimMs || DEFAULT_CLAIM_RECLAIM_MS;
|
|
217
239
|
var name = opts.name || "outbox";
|
|
218
240
|
|
|
219
241
|
var backoff = opts.retryBackoff || {};
|
|
@@ -302,38 +324,69 @@ function create(opts) {
|
|
|
302
324
|
"outbox.enqueue: payload/headers must be JSON-serializable: " + e.message);
|
|
303
325
|
}
|
|
304
326
|
|
|
305
|
-
var sql = "INSERT INTO " + quotedTable +
|
|
306
|
-
" (topic, payload, key, headers, enqueued_at, next_attempt_at, attempts, status)" +
|
|
307
|
-
" VALUES ($1, $2, $3, $4, $5, $5, 0, 'pending')";
|
|
308
327
|
var now = _utcNowExpr(externalDb);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
328
|
+
// enqueued_at and next_attempt_at both take the same publisher-clock
|
|
329
|
+
// moment; b.sql binds it as two separate `?` so the placeholder/param
|
|
330
|
+
// parity gate holds (no $5-reused-twice shorthand).
|
|
331
|
+
var stmt = sql.insert(opts.table, { dialect: _sqlDialect(externalDb) })
|
|
332
|
+
.values({
|
|
333
|
+
topic: event.topic,
|
|
334
|
+
payload: payloadJson,
|
|
335
|
+
key: event.key || null,
|
|
336
|
+
headers: headersJson,
|
|
337
|
+
enqueued_at: now,
|
|
338
|
+
next_attempt_at: now,
|
|
339
|
+
attempts: 0,
|
|
340
|
+
status: "pending",
|
|
341
|
+
})
|
|
342
|
+
.toExternalSql(_sqlDialect(externalDb));
|
|
343
|
+
await txn.query(stmt.sql, stmt.params);
|
|
312
344
|
_emitMetric("enqueued", 1);
|
|
313
345
|
}
|
|
314
346
|
|
|
315
347
|
async function declareSchema(xdb) {
|
|
316
348
|
var target = xdb || externalDb;
|
|
317
|
-
var
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
")"
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
349
|
+
var dialect = _sqlDialect(target);
|
|
350
|
+
// The identity PK renders dialect-correct (BIGSERIAL on postgres,
|
|
351
|
+
// INTEGER PRIMARY KEY AUTOINCREMENT on sqlite, BIGINT AUTO_INCREMENT
|
|
352
|
+
// on mysql) - the prior hand-rolled DDL hardcoded Postgres BIGSERIAL /
|
|
353
|
+
// TIMESTAMPTZ even on a sqlite backend, which the dialect-aware type
|
|
354
|
+
// map now corrects. A varchar-with-length / timestamp-with-zone is
|
|
355
|
+
// passed verbatim by the type map (it sits in type position after a
|
|
356
|
+
// quoted column name, so no identifier injection is possible).
|
|
357
|
+
var tsType = dialect === "postgres" ? "TIMESTAMPTZ" : "TIMESTAMP";
|
|
358
|
+
var ddl = sql.toExternalSql(sql.createTable(opts.table, [
|
|
359
|
+
{ name: "id", serial: true },
|
|
360
|
+
{ name: "topic", type: "VARCHAR(255)", notNull: true },
|
|
361
|
+
{ name: "payload", type: "TEXT", notNull: true },
|
|
362
|
+
{ name: "key", type: "VARCHAR(255)" },
|
|
363
|
+
{ name: "headers", type: "TEXT" },
|
|
364
|
+
{ name: "enqueued_at", type: tsType, notNull: true },
|
|
365
|
+
{ name: "next_attempt_at", type: tsType, notNull: true },
|
|
366
|
+
{ name: "published_at", type: tsType },
|
|
367
|
+
{ name: "claimed_at", type: tsType },
|
|
368
|
+
{ name: "attempts", type: "INTEGER", notNull: true, default: 0 },
|
|
369
|
+
{ name: "last_error", type: "TEXT" },
|
|
370
|
+
{ name: "status", type: "VARCHAR(16)", notNull: true, default: "pending" },
|
|
371
|
+
], { dialect: dialect }), dialect);
|
|
372
|
+
// Partial index on the pending pool (the publisher's claim path scans
|
|
373
|
+
// status='pending' ORDER BY next_attempt_at). The 'pending' literal is
|
|
374
|
+
// a builder-emitted static predicate, opted in via allowLiterals.
|
|
375
|
+
var idx = sql.toExternalSql(sql.createIndex(opts.table + "_pending_idx", opts.table,
|
|
376
|
+
["next_attempt_at"], { dialect: dialect, where: "status = 'pending'" }), dialect);
|
|
377
|
+
await target.query(ddl.sql, ddl.params);
|
|
378
|
+
await target.query(idx.sql, idx.params);
|
|
379
|
+
// Back-compat: an outbox table created before the claimed_at column
|
|
380
|
+
// existed predates the stale-in-flight reaper. CREATE TABLE above is
|
|
381
|
+
// IF NOT EXISTS, so it won't add the column to an existing table — add it
|
|
382
|
+
// idempotently here (every dialect errors if the column already exists,
|
|
383
|
+
// which a fresh table from the CREATE will; swallow that). Without
|
|
384
|
+
// claimed_at the reaper can't tell a stranded claim from a live one.
|
|
385
|
+
try {
|
|
386
|
+
var alter = sql.toExternalSql(sql.alterTable(opts.table,
|
|
387
|
+
{ addColumn: { name: "claimed_at", type: tsType } }, { dialect: dialect }), dialect);
|
|
388
|
+
await target.query(alter.sql, alter.params);
|
|
389
|
+
} catch (_e) { /* column already present — idempotent add */ }
|
|
337
390
|
}
|
|
338
391
|
|
|
339
392
|
// ---- Publisher worker ----
|
|
@@ -363,18 +416,24 @@ function create(opts) {
|
|
|
363
416
|
|
|
364
417
|
async function _claimBatch() {
|
|
365
418
|
var supportsSkipLocked = _supportsForUpdateSkipLocked();
|
|
419
|
+
var dialect = _sqlDialect(externalDb);
|
|
420
|
+
var CLAIM_COLS = ["id", "topic", "payload", "key", "headers", "attempts"];
|
|
366
421
|
return await externalDb.transaction(async function (xdb) {
|
|
367
422
|
var nowExpr = _utcNowExpr(externalDb);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
"
|
|
373
|
-
"
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
423
|
+
// status='pending' is a builder-emitted static predicate (opted in
|
|
424
|
+
// via allowLiterals); next_attempt_at <= ? + the LIMIT both bind.
|
|
425
|
+
var selectBuilder = sql.select(opts.table, { dialect: dialect })
|
|
426
|
+
.columns(CLAIM_COLS)
|
|
427
|
+
.whereRaw("status = 'pending'", [], { allowLiterals: true })
|
|
428
|
+
.whereRaw("next_attempt_at <= ?", [nowExpr])
|
|
429
|
+
.orderBy("next_attempt_at")
|
|
430
|
+
.limit(batchSize);
|
|
431
|
+
// FOR UPDATE SKIP LOCKED on postgres / mysql; sqlite is a single
|
|
432
|
+
// writer with no row lock, so the claim there is the conservative
|
|
433
|
+
// mark-then-reselect path below (b.sql refuses forUpdate on sqlite).
|
|
434
|
+
if (supportsSkipLocked) selectBuilder.forUpdate({ skipLocked: true });
|
|
435
|
+
var selectSql = selectBuilder.toExternalSql(dialect);
|
|
436
|
+
var rows = await xdb.query(selectSql.sql, selectSql.params);
|
|
378
437
|
if (!rows || !rows.rows || rows.rows.length === 0) return [];
|
|
379
438
|
var ids = rows.rows.map(function (r) { return r.id; });
|
|
380
439
|
// Atomic claim: when the dialect lacks SKIP LOCKED, the UPDATE
|
|
@@ -386,30 +445,33 @@ function create(opts) {
|
|
|
386
445
|
// way Postgres does).
|
|
387
446
|
var actuallyClaimed;
|
|
388
447
|
if (supportsSkipLocked) {
|
|
389
|
-
// Postgres/MySQL: row lock held; ANY(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
448
|
+
// Postgres/MySQL: row lock held; whereInArray emits `id = ANY(?)`
|
|
449
|
+
// on postgres (the whole id set as one bound array) / expanded
|
|
450
|
+
// `IN (?, ?, ...)` on mysql.
|
|
451
|
+
var claimUpdate = sql.update(opts.table, { dialect: dialect })
|
|
452
|
+
.set({ status: "in-flight", claimed_at: _utcNowExpr(externalDb) })
|
|
453
|
+
.whereInArray("id", ids)
|
|
454
|
+
.toExternalSql(dialect);
|
|
455
|
+
await xdb.query(claimUpdate.sql, claimUpdate.params);
|
|
394
456
|
actuallyClaimed = rows.rows;
|
|
395
457
|
} else {
|
|
396
458
|
// SQLite (or "other") path: emit a portable UPDATE that
|
|
397
459
|
// refuses overlap by gating on status='pending'. After the
|
|
398
460
|
// update we re-read the in-flight rows we own; rows that
|
|
399
|
-
// another publisher beat us to are skipped.
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
"
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
"
|
|
410
|
-
"
|
|
411
|
-
|
|
412
|
-
);
|
|
461
|
+
// another publisher beat us to are skipped. whereInArray expands
|
|
462
|
+
// to an `IN (?, ?, ...)` placeholder list on sqlite.
|
|
463
|
+
var markUpdate = sql.update(opts.table, { dialect: dialect })
|
|
464
|
+
.set({ status: "in-flight", claimed_at: _utcNowExpr(externalDb) })
|
|
465
|
+
.whereRaw("status = 'pending'", [], { allowLiterals: true })
|
|
466
|
+
.whereInArray("id", ids)
|
|
467
|
+
.toExternalSql(dialect);
|
|
468
|
+
await xdb.query(markUpdate.sql, markUpdate.params);
|
|
469
|
+
var afterSelect = sql.select(opts.table, { dialect: dialect })
|
|
470
|
+
.columns(CLAIM_COLS)
|
|
471
|
+
.whereRaw("status = 'in-flight'", [], { allowLiterals: true })
|
|
472
|
+
.whereInArray("id", ids)
|
|
473
|
+
.toExternalSql(dialect);
|
|
474
|
+
var afterRows = await xdb.query(afterSelect.sql, afterSelect.params);
|
|
413
475
|
actuallyClaimed = (afterRows && afterRows.rows) || [];
|
|
414
476
|
}
|
|
415
477
|
return actuallyClaimed.map(function (r) {
|
|
@@ -425,35 +487,72 @@ function create(opts) {
|
|
|
425
487
|
});
|
|
426
488
|
}
|
|
427
489
|
|
|
490
|
+
// Reclaim rows stranded in 'in-flight' by a crashed publisher. A claim
|
|
491
|
+
// flips status pending → in-flight and stamps claimed_at; if the process
|
|
492
|
+
// dies before the row is marked published / retry / dead, it sits in-flight
|
|
493
|
+
// forever, because the claim path only selects status='pending'. That
|
|
494
|
+
// silently drops the event and violates the at-least-once guarantee. Reset
|
|
495
|
+
// any in-flight row whose claim is older than the lease — or that predates
|
|
496
|
+
// the claimed_at column (NULL) — back to 'pending' so the next poll
|
|
497
|
+
// re-publishes it. The lease bounds how long a legitimately slow publish is
|
|
498
|
+
// protected from reclaim; a reclaim+re-publish is a duplicate, which
|
|
499
|
+
// at-least-once tolerates. Best-effort: a failed sweep retries next poll.
|
|
500
|
+
async function _reapStaleInflight() {
|
|
501
|
+
var dialect = _sqlDialect(externalDb);
|
|
502
|
+
var cutoff = new Date(Date.now() - claimReclaimMs);
|
|
503
|
+
var stmt = sql.update(opts.table, { dialect: dialect })
|
|
504
|
+
.set({ status: "pending", claimed_at: null })
|
|
505
|
+
.whereRaw("status = 'in-flight'", [], { allowLiterals: true })
|
|
506
|
+
.whereRaw("(claimed_at IS NULL OR claimed_at <= ?)", [cutoff])
|
|
507
|
+
.toExternalSql(dialect);
|
|
508
|
+
var res = await externalDb.query(stmt.sql, stmt.params);
|
|
509
|
+
return res;
|
|
510
|
+
}
|
|
511
|
+
|
|
428
512
|
async function _markPublished(id) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
513
|
+
var dialect = _sqlDialect(externalDb);
|
|
514
|
+
var stmt = sql.update(opts.table, { dialect: dialect })
|
|
515
|
+
.set({ status: "published", published_at: _utcNowExpr(externalDb) })
|
|
516
|
+
.where("id", id)
|
|
517
|
+
.toExternalSql(dialect);
|
|
518
|
+
await externalDb.query(stmt.sql, stmt.params);
|
|
434
519
|
}
|
|
435
520
|
|
|
436
521
|
async function _markRetry(id, attempts, errMsg) {
|
|
522
|
+
var dialect = _sqlDialect(externalDb);
|
|
437
523
|
var nextAt = new Date(Date.now() + _backoffMs(attempts + 1));
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
524
|
+
var stmt = sql.update(opts.table, { dialect: dialect })
|
|
525
|
+
.set({
|
|
526
|
+
status: "pending",
|
|
527
|
+
attempts: attempts + 1,
|
|
528
|
+
last_error: String(errMsg).slice(0, 1024), // error-message char cap
|
|
529
|
+
next_attempt_at: nextAt,
|
|
530
|
+
})
|
|
531
|
+
.where("id", id)
|
|
532
|
+
.toExternalSql(dialect);
|
|
533
|
+
await externalDb.query(stmt.sql, stmt.params);
|
|
444
534
|
}
|
|
445
535
|
|
|
446
536
|
async function _markDead(id, attempts, errMsg) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
537
|
+
var dialect = _sqlDialect(externalDb);
|
|
538
|
+
var stmt = sql.update(opts.table, { dialect: dialect })
|
|
539
|
+
.set({
|
|
540
|
+
status: "dead",
|
|
541
|
+
attempts: attempts + 1,
|
|
542
|
+
last_error: String(errMsg).slice(0, 1024), // error-message char cap
|
|
543
|
+
})
|
|
544
|
+
.where("id", id)
|
|
545
|
+
.toExternalSql(dialect);
|
|
546
|
+
await externalDb.query(stmt.sql, stmt.params);
|
|
452
547
|
_emitAudit("system.outbox.deadletter", "failure", { id: id, attempts: attempts + 1 });
|
|
453
548
|
_emitMetric("dead-letter", 1);
|
|
454
549
|
}
|
|
455
550
|
|
|
456
551
|
async function _processOnce() {
|
|
552
|
+
// Reclaim crashed-publisher rows before claiming new work, so a stranded
|
|
553
|
+
// in-flight row re-enters the pending pool and is published this cycle.
|
|
554
|
+
try { await _reapStaleInflight(); }
|
|
555
|
+
catch (_e) { /* drop-silent — reaper retries next poll */ }
|
|
457
556
|
var batch = await _claimBatch();
|
|
458
557
|
if (batch.length === 0) return 0;
|
|
459
558
|
for (var i = 0; i < batch.length; i++) {
|
|
@@ -516,19 +615,21 @@ function create(opts) {
|
|
|
516
615
|
_emitAudit("system.outbox.stopped", "success", { name: name });
|
|
517
616
|
}
|
|
518
617
|
|
|
519
|
-
async function
|
|
520
|
-
var
|
|
521
|
-
|
|
522
|
-
)
|
|
618
|
+
async function _statusCount(status) {
|
|
619
|
+
var dialect = _sqlDialect(externalDb);
|
|
620
|
+
// status is a fixed builder-internal literal ('pending' / 'dead'),
|
|
621
|
+
// never operator input; opted in via allowLiterals. COUNT(*) AS n is
|
|
622
|
+
// the count aggregate with an alias.
|
|
623
|
+
var stmt = sql.select(opts.table, { dialect: dialect })
|
|
624
|
+
.count("*", "n")
|
|
625
|
+
.whereRaw("status = '" + status + "'", [], { allowLiterals: true })
|
|
626
|
+
.toExternalSql(dialect);
|
|
627
|
+
var res = await externalDb.query(stmt.sql, stmt.params);
|
|
523
628
|
return Number((res && res.rows && res.rows[0] && res.rows[0].n) || 0);
|
|
524
629
|
}
|
|
525
630
|
|
|
526
|
-
async function
|
|
527
|
-
|
|
528
|
-
"SELECT COUNT(*) AS n FROM " + quotedTable + " WHERE status = 'dead'", []
|
|
529
|
-
);
|
|
530
|
-
return Number((res && res.rows && res.rows[0] && res.rows[0].n) || 0);
|
|
531
|
-
}
|
|
631
|
+
async function pendingCount() { return await _statusCount("pending"); }
|
|
632
|
+
async function deadCount() { return await _statusCount("dead"); }
|
|
532
633
|
|
|
533
634
|
return {
|
|
534
635
|
enqueue: enqueue,
|
|
@@ -13,10 +13,18 @@
|
|
|
13
13
|
* - Processing instructions referencing external resources
|
|
14
14
|
* - Unbounded recursion / element count / attribute count
|
|
15
15
|
* - CDATA sections of arbitrary length
|
|
16
|
+
* - Prototype pollution: an element or attribute named __proto__,
|
|
17
|
+
* constructor, or prototype landing as a key in the result tree
|
|
18
|
+
* (CWE-1321 / OWASP prototype-pollution)
|
|
16
19
|
*
|
|
17
20
|
* This parser closes all of them by default. DOCTYPE, external entities,
|
|
18
21
|
* and processing instructions other than '<?xml ?>' are REJECTED — apps
|
|
19
|
-
* that need them are using the wrong parser.
|
|
22
|
+
* that need them are using the wrong parser. Element and attribute names
|
|
23
|
+
* equal to __proto__ / constructor / prototype are REJECTED with
|
|
24
|
+
* xml/forbidden-name so they can never collide with an inherited member
|
|
25
|
+
* or reassign an accumulator's prototype; the result tree and every
|
|
26
|
+
* nested object it contains have a null prototype, so a consumer reading
|
|
27
|
+
* an absent key sees undefined rather than an inherited Object member.
|
|
20
28
|
*
|
|
21
29
|
* Output: a plain JS object. Element with attributes + children:
|
|
22
30
|
* <root id="x"><child>text</child></root>
|
|
@@ -75,6 +83,18 @@ var ABSOLUTE_MAX_ATTRIBUTES = 1_000;
|
|
|
75
83
|
// XML built-in entities (the ONLY entities allowed)
|
|
76
84
|
var BUILT_IN_ENTITIES = { lt: "<", gt: ">", amp: "&", quot: "\"", apos: "'" };
|
|
77
85
|
|
|
86
|
+
// Names that must never become a key in the result tree. A plain object
|
|
87
|
+
// inherits these from Object.prototype; an element/attribute named after
|
|
88
|
+
// one of them would otherwise collide with the inherited member (a
|
|
89
|
+
// consumer sees a function/object instead of undefined) or — for a
|
|
90
|
+
// computed-member write of an object value — reassign the accumulator's
|
|
91
|
+
// prototype (CWE-1321 / OWASP prototype-pollution). The accumulators are
|
|
92
|
+
// built with a null prototype, and these names are rejected outright so
|
|
93
|
+
// the result is always a clean key→value map. Mirrors the
|
|
94
|
+
// __proto__/constructor/prototype rejection the toml / yaml / ini
|
|
95
|
+
// parsers in this family already apply.
|
|
96
|
+
var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
97
|
+
|
|
78
98
|
function _validateAndCap(name, value, defaultValue, ceiling) {
|
|
79
99
|
if (value === undefined) return defaultValue;
|
|
80
100
|
if (!numericBounds.isPositiveFiniteInt(value)) {
|
|
@@ -178,7 +198,12 @@ function parse(input, opts) {
|
|
|
178
198
|
} else break;
|
|
179
199
|
}
|
|
180
200
|
if (pos === start) throw _err("expected name", "xml/bad-name");
|
|
181
|
-
|
|
201
|
+
var parsed = input.substring(start, pos);
|
|
202
|
+
if (FORBIDDEN_KEYS.has(parsed)) {
|
|
203
|
+
throw _err("element/attribute name '" + parsed +
|
|
204
|
+
"' is reserved (prototype-pollution defense)", "xml/forbidden-name");
|
|
205
|
+
}
|
|
206
|
+
return parsed;
|
|
182
207
|
}
|
|
183
208
|
|
|
184
209
|
// Parse an attribute value (single- or double-quoted)
|
|
@@ -267,7 +292,12 @@ function parse(input, opts) {
|
|
|
267
292
|
|
|
268
293
|
expectChar("<");
|
|
269
294
|
var name = parseName();
|
|
270
|
-
|
|
295
|
+
// Null-prototype accumulator keyed by attacker-influenced attribute
|
|
296
|
+
// names — no inherited Object member can shadow a missing key, and the
|
|
297
|
+
// duplicate-attribute check below can't be fooled by an inherited
|
|
298
|
+
// function (CWE-1321). Forbidden names are already rejected in
|
|
299
|
+
// parseName.
|
|
300
|
+
var attrs = Object.create(null);
|
|
271
301
|
var attrCount = 0;
|
|
272
302
|
|
|
273
303
|
while (pos < len) {
|
|
@@ -351,10 +381,15 @@ function parse(input, opts) {
|
|
|
351
381
|
// Pure-text element → string
|
|
352
382
|
return _make(name, textParts.join("").trim() === "" ? textParts.join("") : textParts.join(""));
|
|
353
383
|
}
|
|
354
|
-
// Mixed / attributed element → object
|
|
355
|
-
|
|
384
|
+
// Mixed / attributed element → object. Both accumulators carry a null
|
|
385
|
+
// prototype: `grouped` is keyed by attacker-influenced child element
|
|
386
|
+
// names and `obj` receives them via Object.assign, so neither may
|
|
387
|
+
// expose an inherited Object member or be prototype-poisoned by a
|
|
388
|
+
// computed-member write (CWE-1321). Forbidden child names were already
|
|
389
|
+
// rejected in parseName.
|
|
390
|
+
var obj = Object.create(null);
|
|
356
391
|
if (hasAttrs) obj["@attrs"] = attrs;
|
|
357
|
-
var grouped =
|
|
392
|
+
var grouped = Object.create(null);
|
|
358
393
|
for (var i = 0; i < elementChildren.length; i++) {
|
|
359
394
|
var childWrap = elementChildren[i].value;
|
|
360
395
|
var childName = Object.keys(childWrap)[0];
|
|
@@ -374,7 +409,12 @@ function parse(input, opts) {
|
|
|
374
409
|
}
|
|
375
410
|
|
|
376
411
|
function _make(name, value) {
|
|
377
|
-
|
|
412
|
+
// Null-prototype wrapper keyed by the element name (parser-controlled,
|
|
413
|
+
// attacker-influenced). `out[name] = value` with a forbidden name
|
|
414
|
+
// would otherwise reassign the wrapper's prototype when value is an
|
|
415
|
+
// object; the name is already rejected in parseName and the null
|
|
416
|
+
// prototype removes the inherited-member surface entirely (CWE-1321).
|
|
417
|
+
var out = Object.create(null);
|
|
378
418
|
out[name] = value;
|
|
379
419
|
return out;
|
|
380
420
|
}
|
|
@@ -41,6 +41,33 @@ var PqcAgentError = defineClass("PqcAgentError", { alwaysPermanent: true });
|
|
|
41
41
|
// cycles when pqc-agent is required during framework bootstrap.
|
|
42
42
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
43
43
|
|
|
44
|
+
// Observe an outbound socket's negotiated TLS key-exchange group and audit a
|
|
45
|
+
// classical (non-PQC) downgrade. node:tls reports getEphemeralKeyInfo() as
|
|
46
|
+
// { type:"ECDH", name:"X25519", ... } for a classical group and as {} for an
|
|
47
|
+
// ML-KEM hybrid (it doesn't model the hybrid as ECDH). So a NON-empty name
|
|
48
|
+
// that doesn't carry "MLKEM" means the peer offered no hybrid and the
|
|
49
|
+
// handshake fell back to classical X25519 (the framework's last-resort
|
|
50
|
+
// group) — emit the downgrade so operators can see which dependencies are
|
|
51
|
+
// not yet PQC-ready. Best-effort + drop-silent: an audit failure must never
|
|
52
|
+
// break the request that triggered it.
|
|
53
|
+
function auditClassicalDowngrade(socket, meta) {
|
|
54
|
+
try {
|
|
55
|
+
if (!socket || typeof socket.getEphemeralKeyInfo !== "function") return;
|
|
56
|
+
var info = socket.getEphemeralKeyInfo() || {};
|
|
57
|
+
var group = info.name;
|
|
58
|
+
if (!group || /MLKEM/i.test(group)) return; // hybrid (or unreported) — not a downgrade
|
|
59
|
+
audit().safeEmit({
|
|
60
|
+
action: "tls.classical_downgrade",
|
|
61
|
+
outcome: "success",
|
|
62
|
+
metadata: {
|
|
63
|
+
group: group,
|
|
64
|
+
host: (meta && (meta.host || meta.servername)) || null,
|
|
65
|
+
port: (meta && meta.port) || null,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
} catch (_e) { /* drop-silent — audit is best-effort; never break TLS */ }
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
// IANA TLS Supported Groups Registry — every named-group identifier
|
|
45
72
|
// the framework knows by name. Operators with `allowOperatorGroups:
|
|
46
73
|
// true` may pass any entry from this registry; entries outside it
|
|
@@ -186,6 +213,19 @@ function create(opts) {
|
|
|
186
213
|
var built = _buildAgentOpts(opts);
|
|
187
214
|
var agent = new https.Agent(built);
|
|
188
215
|
agent._builtOpts = built;
|
|
216
|
+
// Observe each NEW outbound socket's negotiated group (createConnection
|
|
217
|
+
// runs per fresh connection, not per keep-alive reuse). A classical
|
|
218
|
+
// negotiation means the peer offered no ML-KEM hybrid — audit the
|
|
219
|
+
// downgrade. Hybrid stays preferred on every handshake; this only fires on
|
|
220
|
+
// the classical fallback.
|
|
221
|
+
var _origCreateConnection = agent.createConnection.bind(agent);
|
|
222
|
+
agent.createConnection = function (options, cb) {
|
|
223
|
+
var socket = _origCreateConnection(options, cb);
|
|
224
|
+
if (socket && typeof socket.once === "function") {
|
|
225
|
+
socket.once("secureConnect", function () { auditClassicalDowngrade(socket, options); });
|
|
226
|
+
}
|
|
227
|
+
return socket;
|
|
228
|
+
};
|
|
189
229
|
// Per-instance cert rotation. The pre-v0.10.9 path required process
|
|
190
230
|
// restart for cert rotation on agents built via explicit `create()`
|
|
191
231
|
// (only the framework's lazy default had `b.pqcAgent.reload()`).
|
|
@@ -345,6 +385,10 @@ module.exports = {
|
|
|
345
385
|
create: create,
|
|
346
386
|
createHttp: createHttp,
|
|
347
387
|
reload: reload,
|
|
388
|
+
// Internal — shared with lib/http-client.js's h2 transport, which connects
|
|
389
|
+
// via node:http2 (not this agent) and so needs the same downgrade
|
|
390
|
+
// observation. Underscore-prefixed: not a public operator primitive.
|
|
391
|
+
_auditClassicalDowngrade: auditClassicalDowngrade,
|
|
348
392
|
DEFAULT_OPTS: DEFAULT_OPTS,
|
|
349
393
|
KNOWN_TLS_GROUPS: KNOWN_TLS_GROUPS,
|
|
350
394
|
enforced: true,
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* publishedBy)`. Created by `lib/cluster-storage.js` migrations.
|
|
23
23
|
*/
|
|
24
24
|
var clusterStorage = require("./cluster-storage");
|
|
25
|
+
var frameworkSchema = require("./framework-schema");
|
|
26
|
+
var sql = require("./sql");
|
|
25
27
|
var C = require("./constants");
|
|
26
28
|
var lazyRequire = require("./lazy-require");
|
|
27
29
|
var validateOpts = require("./validate-opts");
|
|
@@ -31,6 +33,24 @@ var logger = lazyRequire(function () { return require("./log").boot("pubsub-clus
|
|
|
31
33
|
|
|
32
34
|
var PubsubError = defineClass("PubsubError");
|
|
33
35
|
|
|
36
|
+
// Resolved once: the fan-out table's concrete name, honoring the
|
|
37
|
+
// configurable framework-table prefix. clusterStorage.execute leaves
|
|
38
|
+
// this self-prefixed name unrewritten (its rewrite map is identity-
|
|
39
|
+
// filtered for already-prefixed tables) and translates `?` to `$N` for
|
|
40
|
+
// Postgres, so b.sql emits the bare resolved name + `?` placeholders.
|
|
41
|
+
var MESSAGES_TABLE = frameworkSchema.tableName("_blamejs_pubsub_messages"); // allow:hand-rolled-sql — single canonical logical-name reference
|
|
42
|
+
|
|
43
|
+
// b.sql opts for every fan-out statement: thread the ACTIVE backend
|
|
44
|
+
// dialect (clusterStorage.dialect() — "sqlite" single-node, "postgres" |
|
|
45
|
+
// "mysql" in cluster mode) so the emitted identifier quoting matches the
|
|
46
|
+
// backend the SQL dispatches to. Without it b.sql defaults to "sqlite"
|
|
47
|
+
// and double-quotes identifiers — correct on Postgres (both double-quote)
|
|
48
|
+
// but read as STRING LITERALS by MySQL (no ANSI_QUOTES), turning the
|
|
49
|
+
// INSERT/SELECT/DELETE into syntax errors. clusterStorage.execute still
|
|
50
|
+
// rewrites table names + translates `?` placeholders at dispatch; this
|
|
51
|
+
// controls only the builder-side identifier quoting.
|
|
52
|
+
function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
|
|
53
|
+
|
|
34
54
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
35
55
|
var DEFAULT_RETENTION_MS = C.TIME.minutes(1);
|
|
36
56
|
var DEFAULT_PRUNE_EVERY_MS = C.TIME.minutes(5);
|
|
@@ -65,11 +85,13 @@ function create(opts) {
|
|
|
65
85
|
|
|
66
86
|
async function publishRemote(scopedChannel, payload) {
|
|
67
87
|
var serialized = JSON.stringify(payload);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
var built = sql.insert(MESSAGES_TABLE, _sqlOpts()).values({
|
|
89
|
+
topic: scopedChannel,
|
|
90
|
+
payload: serialized,
|
|
91
|
+
publishedAt: Date.now(),
|
|
92
|
+
publishedBy: _nodeId(),
|
|
93
|
+
}).toSql();
|
|
94
|
+
await clusterStorage.execute(built.sql, built.params);
|
|
73
95
|
return { remote: 1 };
|
|
74
96
|
}
|
|
75
97
|
|
|
@@ -78,24 +100,25 @@ function create(opts) {
|
|
|
78
100
|
var nodeId = _nodeId();
|
|
79
101
|
try {
|
|
80
102
|
// First poll: prime lastSeenId to the current MAX so we don't
|
|
81
|
-
// re-dispatch every historical row on startup.
|
|
103
|
+
// re-dispatch every historical row on startup. MAX(id) is NULL on
|
|
104
|
+
// an empty table; Number(null) || 0 below maps that to 0 (the same
|
|
105
|
+
// result the prior COALESCE(MAX(id), 0) produced).
|
|
82
106
|
if (!primed) {
|
|
83
|
-
var
|
|
84
|
-
|
|
85
|
-
[]
|
|
86
|
-
);
|
|
107
|
+
var primerBuilt = sql.select(MESSAGES_TABLE, _sqlOpts()).max("id", "maxId").toSql();
|
|
108
|
+
var primer = await clusterStorage.execute(primerBuilt.sql, primerBuilt.params);
|
|
87
109
|
if (primer.rows && primer.rows[0]) {
|
|
88
110
|
lastSeenId = Number(primer.rows[0].maxId) || 0;
|
|
89
111
|
}
|
|
90
112
|
primed = true;
|
|
91
113
|
return;
|
|
92
114
|
}
|
|
93
|
-
var
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
var pollBuilt = sql.select(MESSAGES_TABLE, _sqlOpts())
|
|
116
|
+
.columns(["id", "topic", "payload", "publishedAt", "publishedBy"])
|
|
117
|
+
.where("id", ">", lastSeenId)
|
|
118
|
+
.where("publishedBy", "<>", nodeId)
|
|
119
|
+
.orderBy("id", "asc")
|
|
120
|
+
.toSql();
|
|
121
|
+
var result = await clusterStorage.execute(pollBuilt.sql, pollBuilt.params);
|
|
99
122
|
var rows = result.rows || [];
|
|
100
123
|
for (var i = 0; i < rows.length; i++) {
|
|
101
124
|
var row = rows[i];
|
|
@@ -116,10 +139,9 @@ function create(opts) {
|
|
|
116
139
|
var now = Date.now();
|
|
117
140
|
if (now - lastPruneAt >= pruneEveryMs) {
|
|
118
141
|
lastPruneAt = now;
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
);
|
|
142
|
+
var pruneBuilt = sql.delete(MESSAGES_TABLE, _sqlOpts())
|
|
143
|
+
.where("publishedAt", "<", now - retentionMs).toSql();
|
|
144
|
+
await clusterStorage.execute(pruneBuilt.sql, pruneBuilt.params);
|
|
123
145
|
}
|
|
124
146
|
} catch (e) {
|
|
125
147
|
try { logger().warn("pubsub-cluster poll failed: " +
|