@blamejs/blamejs-shop 0.0.44
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 +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,3111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.db
|
|
4
|
+
* @featured true
|
|
5
|
+
* @nav Data
|
|
6
|
+
* @title Db
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Database core — SQLite (node:sqlite) wrapped in encrypted-at-rest
|
|
10
|
+
* storage, sealed-column field-level crypto, append-only audit-chain
|
|
11
|
+
* integration, declarative schema reconcile, and run-once
|
|
12
|
+
* migrations. Default at-rest posture is `encrypted`: the live `.db`
|
|
13
|
+
* lives in tmpfs (/dev/shm), is decrypted from `<dataDir>/db.enc` at
|
|
14
|
+
* boot, periodically re-encrypted every five minutes, and re-
|
|
15
|
+
* encrypted again at shutdown. The DB encryption key is sealed by
|
|
16
|
+
* `b.vault` at `<dataDir>/db.key.enc`. Operators who want a plain
|
|
17
|
+
* on-disk SQLite file pass `atRest: "plain"` and accept a boot
|
|
18
|
+
* warning — sealed columns still protect PII, but schema and row
|
|
19
|
+
* counts are visible to a forensic disk image.
|
|
20
|
+
*
|
|
21
|
+
* Beyond the storage shell, the module owns the framework's data
|
|
22
|
+
* contract: `audit_log` / `consent_log` / `audit_checkpoints` and
|
|
23
|
+
* the `_blamejs_*` reserved tables are provisioned before any
|
|
24
|
+
* operator schema reconciles, append-only triggers refuse
|
|
25
|
+
* UPDATE/DELETE on the chain tables, and boot refuses to continue
|
|
26
|
+
* on chain breakage, checkpoint signature failure, audit-log
|
|
27
|
+
* rollback, or PRAGMA integrity_check corruption. WORM
|
|
28
|
+
* declarations (`declareWorm`) and dual-control gates
|
|
29
|
+
* (`declareRequireDualControl`) layer SEC 17a-4(f) / FINRA 4511 /
|
|
30
|
+
* 21 CFR Part 11 §11.10(c) record-preservation invariants on
|
|
31
|
+
* operator tables.
|
|
32
|
+
*
|
|
33
|
+
* The query surface is `db.from(table)` (chainable), `db.prepare`
|
|
34
|
+
* (LRU-cached node:sqlite Statement), `db.stream` (object-mode
|
|
35
|
+
* Readable for million-row exports with auto-unseal), and
|
|
36
|
+
* `db.transaction` (BEGIN/COMMIT/ROLLBACK around a callback).
|
|
37
|
+
* Postgres-only declarative migrations (`declareView` /
|
|
38
|
+
* `declareRowPolicy`) emit migration-shape objects consumed by
|
|
39
|
+
* `b.externalDb.migrate`.
|
|
40
|
+
*
|
|
41
|
+
* @card
|
|
42
|
+
* Database core — SQLite (node:sqlite) wrapped in encrypted-at-rest storage, sealed-column field-level crypto, append-only audit-chain integration, declarative schema reconcile, and run-once migrations.
|
|
43
|
+
*/
|
|
44
|
+
var nodeFs = require("node:fs");
|
|
45
|
+
var nodePath = require("node:path");
|
|
46
|
+
var { DatabaseSync } = require("node:sqlite");
|
|
47
|
+
var { Readable } = require("node:stream");
|
|
48
|
+
var atomicFile = require("./atomic-file");
|
|
49
|
+
var audit = require("./audit");
|
|
50
|
+
var auditSign = require("./audit-sign");
|
|
51
|
+
var cluster = require("./cluster");
|
|
52
|
+
var csv = require("./csv");
|
|
53
|
+
var events = require("./events");
|
|
54
|
+
var consent = require("./consent");
|
|
55
|
+
var C = require("./constants");
|
|
56
|
+
var { generateToken, generateBytes, encryptPacked, decryptPacked, sha3Hash } = require("./crypto");
|
|
57
|
+
var cryptoField = require("./crypto-field");
|
|
58
|
+
var dbDeclareRowPolicy = require("./db-declare-row-policy");
|
|
59
|
+
var dbDeclareView = require("./db-declare-view");
|
|
60
|
+
var { Query } = require("./db-query");
|
|
61
|
+
var dbSchema = require("./db-schema");
|
|
62
|
+
var { defineClass } = require("./framework-error");
|
|
63
|
+
var { boot } = require("./log");
|
|
64
|
+
var lazyRequire = require("./lazy-require");
|
|
65
|
+
var observability = require("./observability");
|
|
66
|
+
var ntpCheck = lazyRequire(function () { return require("./ntp-check"); });
|
|
67
|
+
var safeAsync = require("./safe-async");
|
|
68
|
+
var safeEnv = require("./parsers/safe-env");
|
|
69
|
+
var safeJson = require("./safe-json");
|
|
70
|
+
var safeSql = require("./safe-sql");
|
|
71
|
+
var validateOpts = require("./validate-opts");
|
|
72
|
+
var vault = require("./vault");
|
|
73
|
+
|
|
74
|
+
var DbError = defineClass("DbError", { alwaysPermanent: true });
|
|
75
|
+
var WormViolationError = require("./framework-error").WormViolationError;
|
|
76
|
+
var _wormErr = WormViolationError.factory;
|
|
77
|
+
|
|
78
|
+
// Lazy: compliance and dual-control read state at runtime; both are
|
|
79
|
+
// non-load-time deps so a top-of-file require would not cycle, but
|
|
80
|
+
// they're only needed on declareWorm / declareRequireDualControl /
|
|
81
|
+
// eraseHard. Lazy keeps the load graph minimal.
|
|
82
|
+
var compliance = lazyRequire(function () { return require("./compliance"); });
|
|
83
|
+
|
|
84
|
+
// Postures that REQUIRE row-level WORM on operator-named business-
|
|
85
|
+
// record tables. Audit_log / consent_log / audit_checkpoints are
|
|
86
|
+
// already WORM-by-default; this set covers operator tables.
|
|
87
|
+
// sec-17a-4 — SEC Rule 17a-4(f) broker-dealer record preservation
|
|
88
|
+
// finra-4511 — FINRA Rule 4511 books-and-records
|
|
89
|
+
// fda-21cfr11 — 21 CFR Part 11 §11.10(c) protect record integrity
|
|
90
|
+
var WORM_POSTURES = Object.freeze(["sec-17a-4", "finra-4511", "fda-21cfr11"]);
|
|
91
|
+
var _dbErr = DbError.factory;
|
|
92
|
+
|
|
93
|
+
// Lazy: cluster-storage's _localDb pulls db back in, so eager require
|
|
94
|
+
// would deadlock the load order. cluster-storage is only used on the
|
|
95
|
+
// purge-audit-chain external-db nodePath, which always runs after init.
|
|
96
|
+
var clusterStorage = lazyRequire(function () { return require("./cluster-storage"); });
|
|
97
|
+
|
|
98
|
+
// Lazy refs for the test-reset cascade. Each module requires db.js
|
|
99
|
+
// directly or transitively (audit/consent/subject/session/etc. all
|
|
100
|
+
// own a sealed-column slice that depends on db.from), so eager
|
|
101
|
+
// requires here would cycle on load. The cascade runs only when a
|
|
102
|
+
// test explicitly resets db, so paying the resolve cost lazily is
|
|
103
|
+
// the correct tradeoff.
|
|
104
|
+
var _resetAudit = lazyRequire(function () { return require("./audit"); });
|
|
105
|
+
var _resetConsent = lazyRequire(function () { return require("./consent"); });
|
|
106
|
+
var _resetSubject = lazyRequire(function () { return require("./subject"); });
|
|
107
|
+
var _resetSession = lazyRequire(function () { return require("./session"); });
|
|
108
|
+
var _resetStorage = lazyRequire(function () { return require("./storage"); });
|
|
109
|
+
var _resetAuditSign = lazyRequire(function () { return require("./audit-sign"); });
|
|
110
|
+
var _resetQueue = lazyRequire(function () { return require("./queue"); });
|
|
111
|
+
var _resetBreakGlass = lazyRequire(function () { return require("./break-glass"); });
|
|
112
|
+
var _resetLogStream = lazyRequire(function () { return require("./log-stream"); });
|
|
113
|
+
var _resetRedact = lazyRequire(function () { return require("./redact"); });
|
|
114
|
+
var _resetExternalDb = lazyRequire(function () { return require("./external-db"); });
|
|
115
|
+
|
|
116
|
+
var AUDIT_TIP_SCHEMA = {
|
|
117
|
+
type: "object",
|
|
118
|
+
required: ["atMonotonicCounter"],
|
|
119
|
+
properties: {
|
|
120
|
+
atMonotonicCounter: { type: "number" },
|
|
121
|
+
rowHash: { type: "string" },
|
|
122
|
+
signedAt: { type: "string" },
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
var runSql = dbSchema.runSql;
|
|
127
|
+
|
|
128
|
+
// Module-local state, populated by init()
|
|
129
|
+
var database = null; // the SQLite handle
|
|
130
|
+
var dbPath = null; // plaintext DB file path (tmpfs in encrypted mode, dataDir/db in plain mode)
|
|
131
|
+
var encPath = null; // encrypted-at-rest path (null in plain mode)
|
|
132
|
+
var encKey = null; // DB encryption key buffer (null in plain mode)
|
|
133
|
+
var encTimer = null; // periodic encrypt interval handle
|
|
134
|
+
var atRest = null; // 'encrypted' or 'plain'
|
|
135
|
+
var dataDir = null;
|
|
136
|
+
var initialized = false;
|
|
137
|
+
var dataResidency = null; // operator's declared region config (validated by storage backends)
|
|
138
|
+
var subjectTables = []; // [{ name, subjectField, personalDataCategories }] — for subject.export/erase
|
|
139
|
+
var tableMetadata = {}; // table name → metadata snapshot (PK/FK/sealed/derived) for getTableMetadata
|
|
140
|
+
// D-M5 — streamLimit ceiling. db.stream() / Query.stream() consult this
|
|
141
|
+
// (overridden per-call via opts.streamLimit). Default cap matches a
|
|
142
|
+
// generous-but-bounded 1M rows so an accidentally-unbounded export
|
|
143
|
+
// surfaces a thrown error instead of OOM. v0.7.67's maxRowsPerQuery
|
|
144
|
+
// bounds .all() / .first() — this is its streaming counterpart.
|
|
145
|
+
var streamLimit = C.BYTES.bytes(1000000); // allow:raw-byte-literal — row-count ceiling, not bytes
|
|
146
|
+
|
|
147
|
+
// ---- Framework-baked tables ----
|
|
148
|
+
//
|
|
149
|
+
// audit_log + consent_log + _blamejs_subject_restrictions + _blamejs_subject_erasures
|
|
150
|
+
// are provisioned by the framework before app schema reconciles. Apps cannot
|
|
151
|
+
// opt out, override, or rename them. An app schema entry colliding with any of
|
|
152
|
+
// these names is refused at init.
|
|
153
|
+
var RESERVED_TABLE_NAMES = new Set([
|
|
154
|
+
"audit_log",
|
|
155
|
+
"audit_checkpoints",
|
|
156
|
+
"consent_log",
|
|
157
|
+
"_blamejs_subject_restrictions",
|
|
158
|
+
"_blamejs_subject_erasures",
|
|
159
|
+
"_blamejs_sessions",
|
|
160
|
+
"_blamejs_jobs",
|
|
161
|
+
"_blamejs_migrations",
|
|
162
|
+
"_blamejs_counters",
|
|
163
|
+
"_blamejs_audit_purge_anchor",
|
|
164
|
+
"_blamejs_scheduler_ticks",
|
|
165
|
+
"_blamejs_rate_limit_counters",
|
|
166
|
+
"_blamejs_pubsub_messages",
|
|
167
|
+
"_blamejs_api_encrypt_nonces",
|
|
168
|
+
"_blamejs_api_keys",
|
|
169
|
+
"_blamejs_cache",
|
|
170
|
+
"_blamejs_seeders",
|
|
171
|
+
"_blamejs_seeders_lock",
|
|
172
|
+
"_blamejs_break_glass_policies",
|
|
173
|
+
"_blamejs_break_glass_grants",
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
var FRAMEWORK_SCHEMA = [
|
|
177
|
+
{
|
|
178
|
+
name: "audit_log",
|
|
179
|
+
columns: {
|
|
180
|
+
_id: "TEXT PRIMARY KEY",
|
|
181
|
+
recordedAt: "INTEGER NOT NULL",
|
|
182
|
+
monotonicCounter: "INTEGER NOT NULL",
|
|
183
|
+
actorUserId: "TEXT",
|
|
184
|
+
actorUserIdHash: "TEXT",
|
|
185
|
+
actorIp: "TEXT",
|
|
186
|
+
actorUserAgent: "TEXT",
|
|
187
|
+
actorSessionId: "TEXT",
|
|
188
|
+
action: "TEXT NOT NULL",
|
|
189
|
+
resourceKind: "TEXT",
|
|
190
|
+
resourceId: "TEXT",
|
|
191
|
+
resourceIdHash: "TEXT",
|
|
192
|
+
outcome: "TEXT NOT NULL",
|
|
193
|
+
reason: "TEXT",
|
|
194
|
+
metadata: "TEXT",
|
|
195
|
+
requestId: "TEXT",
|
|
196
|
+
prevHash: "TEXT NOT NULL",
|
|
197
|
+
rowHash: "TEXT NOT NULL",
|
|
198
|
+
nonce: "BLOB NOT NULL",
|
|
199
|
+
fencingToken: "INTEGER NOT NULL DEFAULT 0",
|
|
200
|
+
},
|
|
201
|
+
indexes: [
|
|
202
|
+
"actorUserIdHash", "resourceIdHash", "recordedAt", "action",
|
|
203
|
+
{ name: "idx_audit_monotonic", columns: "monotonicCounter", unique: true },
|
|
204
|
+
],
|
|
205
|
+
sealedFields: ["actorUserId", "actorIp", "actorUserAgent", "actorSessionId", "resourceId", "reason", "metadata"],
|
|
206
|
+
derivedHashes: {
|
|
207
|
+
actorUserIdHash: { from: "actorUserId" },
|
|
208
|
+
resourceIdHash: { from: "resourceId" },
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "consent_log",
|
|
213
|
+
columns: {
|
|
214
|
+
_id: "TEXT PRIMARY KEY",
|
|
215
|
+
recordedAt: "INTEGER NOT NULL",
|
|
216
|
+
monotonicCounter: "INTEGER NOT NULL",
|
|
217
|
+
subjectId: "TEXT NOT NULL",
|
|
218
|
+
subjectIdHash: "TEXT NOT NULL",
|
|
219
|
+
purpose: "TEXT NOT NULL",
|
|
220
|
+
lawfulBasis: "TEXT NOT NULL",
|
|
221
|
+
action: "TEXT NOT NULL",
|
|
222
|
+
scope: "TEXT",
|
|
223
|
+
channel: "TEXT NOT NULL",
|
|
224
|
+
evidenceRef: "TEXT",
|
|
225
|
+
prevHash: "TEXT NOT NULL",
|
|
226
|
+
rowHash: "TEXT NOT NULL",
|
|
227
|
+
nonce: "BLOB NOT NULL",
|
|
228
|
+
fencingToken: "INTEGER NOT NULL DEFAULT 0",
|
|
229
|
+
},
|
|
230
|
+
indexes: [
|
|
231
|
+
"subjectIdHash", "recordedAt", "purpose",
|
|
232
|
+
{ name: "idx_consent_monotonic", columns: "monotonicCounter", unique: true },
|
|
233
|
+
],
|
|
234
|
+
sealedFields: ["subjectId", "scope", "evidenceRef"],
|
|
235
|
+
derivedHashes: {
|
|
236
|
+
subjectIdHash: { from: "subjectId" },
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "_blamejs_subject_restrictions",
|
|
241
|
+
columns: {
|
|
242
|
+
subjectIdHash: "TEXT PRIMARY KEY",
|
|
243
|
+
since: "INTEGER NOT NULL",
|
|
244
|
+
reason: "TEXT",
|
|
245
|
+
},
|
|
246
|
+
sealedFields: ["reason"],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "_blamejs_subject_erasures",
|
|
250
|
+
columns: {
|
|
251
|
+
subjectIdHash: "TEXT PRIMARY KEY",
|
|
252
|
+
erasedAt: "INTEGER NOT NULL",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
// Subject-level legal hold registry. Operators register a hold
|
|
257
|
+
// via b.legalHold.place(subjectId, ...) — b.subject.erase and
|
|
258
|
+
// b.retention consult b.legalHold.isHeld(subjectId) before
|
|
259
|
+
// accepting any deletion. Per FRCP Rule 26/37(e), GDPR Art
|
|
260
|
+
// 17(3)(e), SEC Rule 17a-4, HIPAA §164.530(j)(2).
|
|
261
|
+
name: "_blamejs_legal_hold",
|
|
262
|
+
columns: {
|
|
263
|
+
subjectIdHash: "TEXT PRIMARY KEY",
|
|
264
|
+
placedAt: "INTEGER NOT NULL",
|
|
265
|
+
placedBy: "TEXT",
|
|
266
|
+
reason: "TEXT NOT NULL",
|
|
267
|
+
custodian: "TEXT",
|
|
268
|
+
citation: "TEXT",
|
|
269
|
+
retainUntil: "INTEGER",
|
|
270
|
+
},
|
|
271
|
+
indexes: ["placedAt"],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
// Per-row crypto-erasure key registry — F-RTBF-3 per-row keys.
|
|
275
|
+
// Each entry holds a sealed wrapped K_row keyed by (table,
|
|
276
|
+
// rowId). b.subject.eraseHard deletes the entry, leaving WAL /
|
|
277
|
+
// replica residuals undecryptable.
|
|
278
|
+
name: "_blamejs_per_row_keys",
|
|
279
|
+
columns: {
|
|
280
|
+
tableName: "TEXT NOT NULL",
|
|
281
|
+
rowId: "TEXT NOT NULL",
|
|
282
|
+
wrappedKey: "BLOB NOT NULL",
|
|
283
|
+
createdAt: "INTEGER NOT NULL",
|
|
284
|
+
},
|
|
285
|
+
primaryKey: ["tableName", "rowId"],
|
|
286
|
+
indexes: [],
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
// Operator-declared WORM (write-once-read-many) registry. Each
|
|
290
|
+
// entry pairs an operator-named table with the posture that
|
|
291
|
+
// demanded the WORM declaration; boot-time assertions iterate
|
|
292
|
+
// this registry to verify triggers are installed under the
|
|
293
|
+
// current b.compliance.current() posture.
|
|
294
|
+
name: "_blamejs_worm_tables",
|
|
295
|
+
columns: {
|
|
296
|
+
tableName: "TEXT PRIMARY KEY",
|
|
297
|
+
posture: "TEXT",
|
|
298
|
+
declaredAt: "INTEGER NOT NULL",
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
// Operator-declared dual-control gate registry. b.db.delete /
|
|
303
|
+
// b.subject.erase / b.audit.purge consult this table on
|
|
304
|
+
// destructive ops; under the named posture the framework refuses
|
|
305
|
+
// execution unless the caller passes a consumed dual-control
|
|
306
|
+
// grant.
|
|
307
|
+
name: "_blamejs_dual_control_gates",
|
|
308
|
+
columns: {
|
|
309
|
+
tableName: "TEXT PRIMARY KEY",
|
|
310
|
+
posture: "TEXT",
|
|
311
|
+
m: "INTEGER NOT NULL",
|
|
312
|
+
n: "INTEGER NOT NULL",
|
|
313
|
+
declaredAt:"INTEGER NOT NULL",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "audit_checkpoints",
|
|
318
|
+
columns: {
|
|
319
|
+
_id: "TEXT PRIMARY KEY",
|
|
320
|
+
createdAt: "INTEGER NOT NULL",
|
|
321
|
+
atMonotonicCounter: "INTEGER NOT NULL",
|
|
322
|
+
atRowHash: "TEXT NOT NULL",
|
|
323
|
+
signature: "BLOB NOT NULL",
|
|
324
|
+
publicKeyFingerprint: "TEXT NOT NULL",
|
|
325
|
+
fencingToken: "INTEGER NOT NULL DEFAULT 0",
|
|
326
|
+
},
|
|
327
|
+
indexes: [
|
|
328
|
+
"createdAt",
|
|
329
|
+
{ name: "idx_chkpt_counter", columns: "atMonotonicCounter", unique: true },
|
|
330
|
+
],
|
|
331
|
+
sealedFields: [],
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "_blamejs_audit_purge_anchor",
|
|
335
|
+
columns: {
|
|
336
|
+
// CHECK constraint: scope is one of the framework's audit-
|
|
337
|
+
// chain anchor scopes (`audit` / `consent`). Pre-v0.8.37 a
|
|
338
|
+
// typo silently created a parallel anchor; the chain verifier
|
|
339
|
+
// walked the wrong anchor and missed tampering. The CHECK
|
|
340
|
+
// refuses unknown scope strings at INSERT time.
|
|
341
|
+
scope: "TEXT PRIMARY KEY CHECK (scope IN ('audit', 'consent'))",
|
|
342
|
+
lastPurgedCounter: "INTEGER NOT NULL",
|
|
343
|
+
lastPurgedRowHash: "TEXT NOT NULL",
|
|
344
|
+
archiveBundleId: "TEXT NOT NULL",
|
|
345
|
+
purgedAt: "INTEGER NOT NULL",
|
|
346
|
+
},
|
|
347
|
+
sealedFields: [],
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
// Scheduler exactly-once-globally claim table. Each fire claims a
|
|
351
|
+
// (name, scheduledAtUnix) row before dispatching; UNIQUE on the
|
|
352
|
+
// composite tickKey (name + ":" + scheduledAtUnix) means a concurrent
|
|
353
|
+
// leader's INSERT loses with a constraint violation, and that node
|
|
354
|
+
// skips the tick. Closes the once-globally gap during cluster
|
|
355
|
+
// leader hand-offs where two leaders briefly coexist.
|
|
356
|
+
name: "_blamejs_scheduler_ticks",
|
|
357
|
+
columns: {
|
|
358
|
+
tickKey: "TEXT PRIMARY KEY",
|
|
359
|
+
name: "TEXT NOT NULL",
|
|
360
|
+
scheduledAtUnix: "INTEGER NOT NULL",
|
|
361
|
+
claimedAtUnix: "INTEGER NOT NULL",
|
|
362
|
+
claimedBy: "TEXT",
|
|
363
|
+
},
|
|
364
|
+
indexes: ["scheduledAtUnix"],
|
|
365
|
+
sealedFields: [],
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
// _blamejs_rate_limit_counters — fixed-window counter table for
|
|
369
|
+
// the cluster-shared rate-limit backend. One row per (key); the
|
|
370
|
+
// count rolls over atomically when the windowStart advances. Used
|
|
371
|
+
// by lib/middleware/rate-limit.js when scope: 'cluster' is set.
|
|
372
|
+
name: "_blamejs_rate_limit_counters",
|
|
373
|
+
columns: {
|
|
374
|
+
key: "TEXT PRIMARY KEY",
|
|
375
|
+
windowStart: "INTEGER NOT NULL",
|
|
376
|
+
count: "INTEGER NOT NULL DEFAULT 0",
|
|
377
|
+
},
|
|
378
|
+
indexes: ["windowStart"],
|
|
379
|
+
sealedFields: [],
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
// _blamejs_pubsub_messages — cluster fan-out for `b.pubsub` (the
|
|
383
|
+
// generalization of the previous WebSocket-specific table). Any
|
|
384
|
+
// pubsub instance using the `cluster` backend writes a row on
|
|
385
|
+
// publish; other nodes poll for new ids and dispatch to their
|
|
386
|
+
// local subscribers. Rows older than the configured retention
|
|
387
|
+
// window are pruned by the backend on a rate-limited basis.
|
|
388
|
+
name: "_blamejs_pubsub_messages",
|
|
389
|
+
columns: {
|
|
390
|
+
id: "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
391
|
+
topic: "TEXT NOT NULL",
|
|
392
|
+
payload: "TEXT NOT NULL",
|
|
393
|
+
publishedAt: "INTEGER NOT NULL",
|
|
394
|
+
publishedBy: "TEXT NOT NULL",
|
|
395
|
+
},
|
|
396
|
+
indexes: ["publishedAt"],
|
|
397
|
+
sealedFields: [],
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
// _blamejs_api_encrypt_nonces — replay-protection store for the
|
|
401
|
+
// api-encrypt middleware. The middleware hashes the client-supplied
|
|
402
|
+
// nonce via SHA3 before insert so a leaked DB / table dump never
|
|
403
|
+
// exposes the original 16-byte client nonces. Hashing is
|
|
404
|
+
// deterministic so the PRIMARY KEY conflict is what catches a
|
|
405
|
+
// replay attempt within the replay window.
|
|
406
|
+
name: "_blamejs_api_encrypt_nonces",
|
|
407
|
+
columns: {
|
|
408
|
+
nonceHash: "TEXT PRIMARY KEY",
|
|
409
|
+
expireAt: "INTEGER NOT NULL",
|
|
410
|
+
},
|
|
411
|
+
indexes: ["expireAt"],
|
|
412
|
+
sealedFields: [],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "_blamejs_sessions",
|
|
416
|
+
columns: {
|
|
417
|
+
sidHash: "TEXT PRIMARY KEY",
|
|
418
|
+
userId: "TEXT NOT NULL",
|
|
419
|
+
userIdHash: "TEXT NOT NULL",
|
|
420
|
+
data: "TEXT",
|
|
421
|
+
createdAt: "INTEGER NOT NULL",
|
|
422
|
+
expiresAt: "INTEGER NOT NULL",
|
|
423
|
+
lastActivity: "INTEGER NOT NULL",
|
|
424
|
+
},
|
|
425
|
+
indexes: ["userIdHash", "expiresAt"],
|
|
426
|
+
sealedFields: ["userId", "data"],
|
|
427
|
+
derivedHashes: { userIdHash: { from: "userId" } },
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
// _blamejs_api_keys — operator-facing API-key registry. Sealed
|
|
431
|
+
// columns: ownerId / scopes / metadata. The secret never lands
|
|
432
|
+
// here — only its SHA3-512 hash, constant-time-compared on
|
|
433
|
+
// verify. Same dual-storage pattern as sessions: this row mirrors
|
|
434
|
+
// the cluster-mode DDL in framework-schema.js so cluster-storage
|
|
435
|
+
// can route to either backend transparently.
|
|
436
|
+
name: "_blamejs_api_keys",
|
|
437
|
+
columns: {
|
|
438
|
+
id: "TEXT PRIMARY KEY",
|
|
439
|
+
namespace: "TEXT NOT NULL",
|
|
440
|
+
ownerId: "TEXT NOT NULL",
|
|
441
|
+
ownerIdHash: "TEXT NOT NULL",
|
|
442
|
+
secretHash: "TEXT NOT NULL",
|
|
443
|
+
// secondarySecretHash + secondaryExpiresAt support graceful key
|
|
444
|
+
// rotation: when rotate({ gracePeriodMs }) is called the old hash
|
|
445
|
+
// is preserved here and the new hash takes the primary slot. Both
|
|
446
|
+
// verify successfully until secondaryExpiresAt, then the old slot
|
|
447
|
+
// is implicitly retired.
|
|
448
|
+
secondarySecretHash: "TEXT",
|
|
449
|
+
secondaryExpiresAt: "INTEGER",
|
|
450
|
+
scopes: "TEXT",
|
|
451
|
+
metadata: "TEXT",
|
|
452
|
+
createdAt: "INTEGER NOT NULL",
|
|
453
|
+
expiresAt: "INTEGER",
|
|
454
|
+
revokedAt: "INTEGER",
|
|
455
|
+
lastUsedAt: "INTEGER",
|
|
456
|
+
prefix: "TEXT NOT NULL",
|
|
457
|
+
},
|
|
458
|
+
indexes: [
|
|
459
|
+
"ownerIdHash",
|
|
460
|
+
{ name: "idx_api_keys_namespace_owner", columns: ["namespace", "ownerIdHash"] },
|
|
461
|
+
"expiresAt",
|
|
462
|
+
],
|
|
463
|
+
sealedFields: ["ownerId", "scopes", "metadata"],
|
|
464
|
+
derivedHashes: { ownerIdHash: { from: "ownerId" } },
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: "_blamejs_jobs",
|
|
468
|
+
columns: {
|
|
469
|
+
_id: "TEXT PRIMARY KEY",
|
|
470
|
+
queueName: "TEXT NOT NULL",
|
|
471
|
+
payload: "TEXT",
|
|
472
|
+
status: "TEXT NOT NULL",
|
|
473
|
+
enqueuedAt: "INTEGER NOT NULL",
|
|
474
|
+
availableAt: "INTEGER NOT NULL",
|
|
475
|
+
leasedAt: "INTEGER",
|
|
476
|
+
leaseExpiresAt: "INTEGER",
|
|
477
|
+
attempts: "INTEGER NOT NULL DEFAULT 0",
|
|
478
|
+
maxAttempts: "INTEGER NOT NULL DEFAULT 5",
|
|
479
|
+
lastError: "TEXT",
|
|
480
|
+
finishedAt: "INTEGER",
|
|
481
|
+
traceId: "TEXT",
|
|
482
|
+
classification: "TEXT",
|
|
483
|
+
priority: "INTEGER NOT NULL DEFAULT 0",
|
|
484
|
+
// Repeat-in-queue: cron-shaped recurring jobs re-enqueue themselves
|
|
485
|
+
// after each successful completion. NULL = one-shot (no repeat).
|
|
486
|
+
repeatCron: "TEXT",
|
|
487
|
+
repeatTimezone: "TEXT",
|
|
488
|
+
// Flows: parent-child job graphs with dependency edges.
|
|
489
|
+
// flowId groups jobs in the same flow; dependsOn is a JSON array
|
|
490
|
+
// of jobIds this row waits for; flowChildName is the human-readable
|
|
491
|
+
// label inside the flow used by dependsOn resolution.
|
|
492
|
+
flowId: "TEXT",
|
|
493
|
+
flowChildName: "TEXT",
|
|
494
|
+
dependsOn: "TEXT",
|
|
495
|
+
},
|
|
496
|
+
indexes: [
|
|
497
|
+
{ name: "idx_jobs_lease", columns: ["queueName", "status", "availableAt"] },
|
|
498
|
+
{ name: "idx_jobs_priority", columns: ["queueName", "status", "priority", "availableAt"] },
|
|
499
|
+
{ name: "idx_jobs_flow", columns: ["flowId"] },
|
|
500
|
+
"leaseExpiresAt",
|
|
501
|
+
"finishedAt",
|
|
502
|
+
],
|
|
503
|
+
sealedFields: ["payload", "lastError"],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
// _blamejs_cache — operator-facing cache primitive's cluster backend
|
|
507
|
+
// (lib/cache.js). Mirrors the cluster-mode DDL in framework-schema.js.
|
|
508
|
+
// PRIMARY KEY is the composite "<namespace>:<key>"; valueJson is
|
|
509
|
+
// JSON-serialized; expiresAt is unix-ms (Number.MAX_SAFE_INTEGER for
|
|
510
|
+
// never-expiring entries). Not sealed: cache values are operator-
|
|
511
|
+
// chosen application data, the operator decides what's worth storing.
|
|
512
|
+
name: "_blamejs_cache",
|
|
513
|
+
columns: {
|
|
514
|
+
cacheKey: "TEXT PRIMARY KEY",
|
|
515
|
+
valueJson: "TEXT NOT NULL",
|
|
516
|
+
expiresAt: "INTEGER NOT NULL",
|
|
517
|
+
updatedAt: "INTEGER NOT NULL",
|
|
518
|
+
},
|
|
519
|
+
indexes: ["expiresAt"],
|
|
520
|
+
sealedFields: [],
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
// _blamejs_cache_tags — junction table for tag→cacheKey lookup
|
|
524
|
+
// backing b.cache.invalidateTag(t) on the cluster backend. Composite
|
|
525
|
+
// PK (cacheKey, tag) lets one cacheKey carry many tags; index on
|
|
526
|
+
// tag makes invalidation a single indexed scan. Cleared together
|
|
527
|
+
// with the matching _blamejs_cache rows on del / clear / sweep.
|
|
528
|
+
name: "_blamejs_cache_tags",
|
|
529
|
+
columns: {
|
|
530
|
+
cacheKey: "TEXT NOT NULL",
|
|
531
|
+
tag: "TEXT NOT NULL",
|
|
532
|
+
},
|
|
533
|
+
primaryKey: ["cacheKey", "tag"],
|
|
534
|
+
indexes: ["tag"],
|
|
535
|
+
sealedFields: [],
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
// _blamejs_seeders — registry of applied seed files for the
|
|
539
|
+
// b.seeders primitive (lib/seeders.js). Composite PK (env, name)
|
|
540
|
+
// means the same filename can apply per env (dev fixtures don't
|
|
541
|
+
// collide with prod fixtures by name). rerunnable=1 entries get
|
|
542
|
+
// their appliedAt updated in place on every run; non-rerunnable
|
|
543
|
+
// entries are insert-once.
|
|
544
|
+
name: "_blamejs_seeders",
|
|
545
|
+
columns: {
|
|
546
|
+
env: "TEXT NOT NULL",
|
|
547
|
+
name: "TEXT NOT NULL",
|
|
548
|
+
description: "TEXT",
|
|
549
|
+
appliedAt: "TEXT NOT NULL",
|
|
550
|
+
rerunnable: "INTEGER NOT NULL DEFAULT 0",
|
|
551
|
+
},
|
|
552
|
+
primaryKey: ["env", "name"],
|
|
553
|
+
indexes: [],
|
|
554
|
+
sealedFields: [],
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
// _blamejs_seeders_lock — single-row advisory lock for the seeders
|
|
558
|
+
// runner. Same shape as _blamejs_migrations_lock (CHECK constraint
|
|
559
|
+
// on scope='lock' enforces single row). Two processes calling
|
|
560
|
+
// `seed run` against the same DB race on this PK; loser sees a
|
|
561
|
+
// clear "lock held" error.
|
|
562
|
+
name: "_blamejs_seeders_lock",
|
|
563
|
+
columns: {
|
|
564
|
+
scope: "TEXT PRIMARY KEY CHECK (scope = 'lock')",
|
|
565
|
+
lockedAt: "INTEGER NOT NULL",
|
|
566
|
+
lockedBy: "TEXT NOT NULL",
|
|
567
|
+
},
|
|
568
|
+
sealedFields: [],
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
// _blamejs_break_glass_policies — column-level break-glass policy
|
|
572
|
+
// registry. One row per (table) declares which columns are
|
|
573
|
+
// glass-locked and what the operator's grant rules are. Sealed
|
|
574
|
+
// columns hold the column-list, factor-list, and bypass config so
|
|
575
|
+
// policy contents aren't browsable in cleartext.
|
|
576
|
+
name: "_blamejs_break_glass_policies",
|
|
577
|
+
columns: {
|
|
578
|
+
tableName: "TEXT PRIMARY KEY",
|
|
579
|
+
columnsJson: "TEXT NOT NULL",
|
|
580
|
+
factorsJson: "TEXT NOT NULL",
|
|
581
|
+
cryptographic: "INTEGER NOT NULL DEFAULT 0",
|
|
582
|
+
grantTtlMs: "INTEGER NOT NULL",
|
|
583
|
+
maxRowsPerGrant: "INTEGER NOT NULL DEFAULT 1",
|
|
584
|
+
reasonRequired: "INTEGER NOT NULL DEFAULT 1",
|
|
585
|
+
reasonMinLength: "INTEGER NOT NULL DEFAULT 12",
|
|
586
|
+
pinIp: "INTEGER NOT NULL DEFAULT 1",
|
|
587
|
+
sessionPin: "INTEGER NOT NULL DEFAULT 1",
|
|
588
|
+
onLockedAccess: "TEXT NOT NULL DEFAULT 'throw'",
|
|
589
|
+
requireScope: "TEXT",
|
|
590
|
+
serviceAccountBypassJson: "TEXT",
|
|
591
|
+
dekSealed: "TEXT",
|
|
592
|
+
auditReasonStorage: "TEXT NOT NULL DEFAULT 'cleartext'",
|
|
593
|
+
updatedAt: "INTEGER NOT NULL",
|
|
594
|
+
},
|
|
595
|
+
indexes: [],
|
|
596
|
+
sealedFields: ["columnsJson", "factorsJson", "serviceAccountBypassJson"],
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
// _blamejs_break_glass_grants — issued grants. Each successful
|
|
600
|
+
// step-up creates one row; each row read decrements rowsRemaining.
|
|
601
|
+
// Default maxRowsPerGrant=1 enforces "row by row" auth per the
|
|
602
|
+
// operator-confirmed shape (each row access = its own grant).
|
|
603
|
+
// Sealed columns hold reason + scopeColumns so audit-readable
|
|
604
|
+
// metadata doesn't leak in cleartext.
|
|
605
|
+
name: "_blamejs_break_glass_grants",
|
|
606
|
+
columns: {
|
|
607
|
+
_id: "TEXT PRIMARY KEY",
|
|
608
|
+
issuedToActorId: "TEXT NOT NULL",
|
|
609
|
+
issuedToActorHash: "TEXT NOT NULL",
|
|
610
|
+
factorType: "TEXT NOT NULL",
|
|
611
|
+
reasonSealed: "TEXT",
|
|
612
|
+
scopeTable: "TEXT NOT NULL",
|
|
613
|
+
scopeColumnsJson: "TEXT NOT NULL",
|
|
614
|
+
issuedAt: "INTEGER NOT NULL",
|
|
615
|
+
expiresAt: "INTEGER NOT NULL",
|
|
616
|
+
maxRowsPerGrant: "INTEGER NOT NULL",
|
|
617
|
+
rowsConsumed: "INTEGER NOT NULL DEFAULT 0",
|
|
618
|
+
revokedAt: "INTEGER",
|
|
619
|
+
sessionId: "TEXT",
|
|
620
|
+
ip: "TEXT",
|
|
621
|
+
kwGrantHalf: "TEXT",
|
|
622
|
+
},
|
|
623
|
+
indexes: [
|
|
624
|
+
{ name: "idx_bg_grants_actor", columns: ["issuedToActorHash"] },
|
|
625
|
+
{ name: "idx_bg_grants_table", columns: ["scopeTable"] },
|
|
626
|
+
"expiresAt",
|
|
627
|
+
"revokedAt",
|
|
628
|
+
],
|
|
629
|
+
derivedHashes: { issuedToActorHash: { from: "issuedToActorId" } },
|
|
630
|
+
sealedFields: ["reasonSealed", "scopeColumnsJson", "kwGrantHalf"],
|
|
631
|
+
},
|
|
632
|
+
];
|
|
633
|
+
|
|
634
|
+
var log = boot("db");
|
|
635
|
+
|
|
636
|
+
// ---- Tmpfs detection ----
|
|
637
|
+
|
|
638
|
+
function resolveTmpDir(optsTmpDir) {
|
|
639
|
+
if (optsTmpDir) return optsTmpDir;
|
|
640
|
+
var envTmp = safeEnv.readVar("BLAMEJS_TMPDIR");
|
|
641
|
+
if (envTmp) return envTmp;
|
|
642
|
+
if (nodeFs.existsSync("/dev/shm")) return "/dev/shm";
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ---- DB encryption key management ----
|
|
647
|
+
|
|
648
|
+
function loadOrCreateDbKey(dataDirPath, keyPathOverride) {
|
|
649
|
+
// Operator opt: `opts.dbKeyPath` — useful when the encryption key
|
|
650
|
+
// needs to live outside `dataDir` (e.g. a separate volume mounted
|
|
651
|
+
// from a KMS-fronted secret store). Default places it next to the
|
|
652
|
+
// encrypted DB so backup capture is one-tarball.
|
|
653
|
+
var keyPath = keyPathOverride || nodePath.join(dataDirPath, "db.key.enc");
|
|
654
|
+
if (nodeFs.existsSync(keyPath)) {
|
|
655
|
+
var sealed = atomicFile.readSync(keyPath, { encoding: "utf8" }).trim();
|
|
656
|
+
var b64 = vault.unseal(sealed);
|
|
657
|
+
if (!b64) {
|
|
658
|
+
throw _dbErr("db/key-unseal-empty",
|
|
659
|
+
"FATAL: db.key.enc unseal returned empty — vault may not be initialized or key file corrupted");
|
|
660
|
+
}
|
|
661
|
+
return Buffer.from(b64, "base64");
|
|
662
|
+
}
|
|
663
|
+
// First run — generate, seal, persist (atomic)
|
|
664
|
+
var raw = generateBytes(C.BYTES.bytes(32));
|
|
665
|
+
// allow:seal-without-aad — whole-file DB encryption key, not a row column
|
|
666
|
+
var sealedKey = vault.seal(raw.toString("base64"));
|
|
667
|
+
atomicFile.writeSync(keyPath, sealedKey, { fileMode: 0o600 });
|
|
668
|
+
log("generated DB encryption key at " + keyPath);
|
|
669
|
+
return raw;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function decryptToTmp() {
|
|
673
|
+
if (!encPath || !nodeFs.existsSync(encPath)) return;
|
|
674
|
+
// If a plaintext file already exists in tmpfs from a prior process, prefer
|
|
675
|
+
// the newer mtime (crash recovery — operator's most recent state wins).
|
|
676
|
+
if (nodeFs.existsSync(dbPath)) {
|
|
677
|
+
var plainStat = nodeFs.statSync(dbPath);
|
|
678
|
+
var encStat = nodeFs.statSync(encPath);
|
|
679
|
+
if (plainStat.mtimeMs > encStat.mtimeMs && plainStat.size > 0) {
|
|
680
|
+
log("plaintext is newer than encrypted — keeping plaintext (crash recovery)");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
var packed = nodeFs.readFileSync(encPath);
|
|
685
|
+
if (packed.length < 26) return; // too short to be a valid envelope
|
|
686
|
+
// AAD binds the envelope to this deployment's data dir so two
|
|
687
|
+
// installs sharing the same operator passphrase can't swap each
|
|
688
|
+
// other's db.enc files. Backwards-compat: if the AAD-bound decrypt
|
|
689
|
+
// fails, retry without AAD for envelopes written by pre-AAD
|
|
690
|
+
// versions (one-release transition window).
|
|
691
|
+
var aad = _dbEncAad(dataDir);
|
|
692
|
+
try {
|
|
693
|
+
atomicFile.writeSync(dbPath, decryptPacked(packed, encKey, aad));
|
|
694
|
+
} catch (_e) {
|
|
695
|
+
atomicFile.writeSync(dbPath, decryptPacked(packed, encKey));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function _dbEncAad(dir) {
|
|
700
|
+
return Buffer.from("blamejs.db-enc.v1\0" + (dir || ""), "utf8");
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function encryptToDisk() {
|
|
704
|
+
if (!encPath) return;
|
|
705
|
+
// Force WAL checkpoint so the .db file holds all committed transactions.
|
|
706
|
+
try { runSql(database, "PRAGMA wal_checkpoint(TRUNCATE)"); } catch (_e) { /* best effort */ }
|
|
707
|
+
if (!nodeFs.existsSync(dbPath)) return;
|
|
708
|
+
atomicFile.writeSync(encPath, encryptPacked(nodeFs.readFileSync(dbPath), encKey, _dbEncAad(dataDir)));
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* @primitive b.db.snapshot
|
|
713
|
+
* @signature b.db.snapshot()
|
|
714
|
+
* @since 0.8.58
|
|
715
|
+
* @status stable
|
|
716
|
+
* @related b.db.flushToDisk, b.backup
|
|
717
|
+
*
|
|
718
|
+
* In-memory encrypted snapshot — same envelope shape that
|
|
719
|
+
* `flushToDisk` writes, just held in memory. Operators capturing a
|
|
720
|
+
* backup mid-flight (`b.backup` wrapping a hot DB) get a Buffer they
|
|
721
|
+
* can stream onward to object storage without touching the on-disk
|
|
722
|
+
* encPath. Forces a WAL checkpoint first so the snapshot reflects
|
|
723
|
+
* committed state, not pre-WAL pages.
|
|
724
|
+
*
|
|
725
|
+
* Under `atRest: 'plain'` returns the raw plaintext SQLite file as a
|
|
726
|
+
* Buffer (no envelope), since there's no encryption key to apply —
|
|
727
|
+
* operators wanting an encrypted snapshot under plain mode wrap with
|
|
728
|
+
* their own `b.crypto.encryptPacked` at the call site.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* var b = require("@blamejs/core");
|
|
732
|
+
* var snap = b.db.snapshot();
|
|
733
|
+
* await b.objectStore.put("backups/" + Date.now() + ".enc", snap);
|
|
734
|
+
*/
|
|
735
|
+
function snapshot() {
|
|
736
|
+
_requireInit();
|
|
737
|
+
// WAL checkpoint flushes committed transactions into the main DB file
|
|
738
|
+
// so the snapshot reflects the current logical state, not just the
|
|
739
|
+
// pre-WAL pages.
|
|
740
|
+
try { runSql(database, "PRAGMA wal_checkpoint(TRUNCATE)"); } catch (_e) { /* best effort */ }
|
|
741
|
+
if (!nodeFs.existsSync(dbPath)) {
|
|
742
|
+
throw _dbErr("db/snapshot-no-source",
|
|
743
|
+
"snapshot: plaintext DB at " + dbPath + " is missing — did init complete?");
|
|
744
|
+
}
|
|
745
|
+
var plain = nodeFs.readFileSync(dbPath);
|
|
746
|
+
if (!encPath || !encKey) {
|
|
747
|
+
// atRest: 'plain' — return the raw bytes. Operators wanting an
|
|
748
|
+
// encrypted snapshot under plain mode wrap with their own
|
|
749
|
+
// b.crypto.encryptPacked at the call site.
|
|
750
|
+
return plain;
|
|
751
|
+
}
|
|
752
|
+
return encryptPacked(plain, encKey, _dbEncAad(dataDir));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Remove the plaintext DB + WAL/SHM sidecar files. On Windows these can't be
|
|
756
|
+
// unlinked while the SQLite handle is open, so this MUST be called after
|
|
757
|
+
// database.close().
|
|
758
|
+
function removePlaintextFiles() {
|
|
759
|
+
if (!dbPath) return;
|
|
760
|
+
try { nodeFs.unlinkSync(dbPath); } catch (_e) { /* cleanup */ }
|
|
761
|
+
try { nodeFs.unlinkSync(dbPath + "-wal"); } catch (_e) { /* cleanup */ }
|
|
762
|
+
try { nodeFs.unlinkSync(dbPath + "-shm"); } catch (_e) { /* cleanup */ }
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Clean up stale plaintext DB files left by previously-crashed processes.
|
|
766
|
+
// Anything matching blamejs-*.db that isn't our current process's file is
|
|
767
|
+
// stale (no other process should write to /dev/shm with our prefix).
|
|
768
|
+
function cleanStaleTmpDbs(tmpDir) {
|
|
769
|
+
var entries = atomicFile.listDir(tmpDir, {
|
|
770
|
+
filter: function (name) { return name.startsWith("blamejs-") && name.endsWith(".db"); },
|
|
771
|
+
});
|
|
772
|
+
for (var i = 0; i < entries.length; i++) {
|
|
773
|
+
var full = entries[i].fullPath;
|
|
774
|
+
if (full === dbPath) continue;
|
|
775
|
+
try { nodeFs.unlinkSync(full); } catch (_e) { /* concurrent cleanup */ }
|
|
776
|
+
try { nodeFs.unlinkSync(full + "-wal"); } catch (_e) { /* may not exist */ }
|
|
777
|
+
try { nodeFs.unlinkSync(full + "-shm"); } catch (_e) { /* may not exist */ }
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// ---- Init dispatch ----
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @primitive b.db.init
|
|
785
|
+
* @signature b.db.init(opts)
|
|
786
|
+
* @since 0.1.0
|
|
787
|
+
* @status stable
|
|
788
|
+
* @related b.db.close, b.db.from, b.db.declareWorm
|
|
789
|
+
*
|
|
790
|
+
* Boot the database. Provisions the framework-baked tables
|
|
791
|
+
* (`audit_log` / `consent_log` / `audit_checkpoints` /
|
|
792
|
+
* `_blamejs_*`), reconciles the operator schema, installs append-
|
|
793
|
+
* only triggers on chain tables, runs any pending file-based
|
|
794
|
+
* migrations, verifies the audit + consent chains end-to-end,
|
|
795
|
+
* verifies every audit checkpoint signature, runs PRAGMA
|
|
796
|
+
* integrity_check, performs a rollback-detection check against
|
|
797
|
+
* `audit.tip`, and runs a best-effort SNTP boot drift check. Refuses
|
|
798
|
+
* to boot on any chain breakage, signature mismatch, or rollback —
|
|
799
|
+
* compliance posture demands fail-closed at the earliest signal.
|
|
800
|
+
*
|
|
801
|
+
* @opts
|
|
802
|
+
* dataDir: string, // required — where db.enc + db.key.enc live
|
|
803
|
+
* schema: Array, // required — [{ name, columns, indexes, sealedFields, derivedHashes, foreignKeys, primaryKey, subjectField, personalDataCategories }, ...]
|
|
804
|
+
* atRest: "encrypted"|"plain", // default "encrypted"
|
|
805
|
+
* tmpDir: string, // override the encrypted-mode tmpfs path (default /dev/shm or BLAMEJS_TMPDIR)
|
|
806
|
+
* migrationDir: string, // optional — path to ./migrations/ (run-once each)
|
|
807
|
+
* streamLimit: number, // default 1_000_000 — db.stream row ceiling
|
|
808
|
+
* skipBootIntegrityCheck: boolean, // default false — skip PRAGMA integrity_check
|
|
809
|
+
* skipIntegrityCheck: boolean, // default false — alias
|
|
810
|
+
* auditSigning: { mode, algorithm }, // default { mode: "wrapped" }
|
|
811
|
+
* ntpServers: string[], // override NTP server list
|
|
812
|
+
* ntpTimeoutMs: number, // override NTP timeout
|
|
813
|
+
* dataResidency: object, // operator's region declaration
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* var b = require("blamejs");
|
|
817
|
+
* await b.db.init({
|
|
818
|
+
* dataDir: "/var/lib/myapp",
|
|
819
|
+
* atRest: "encrypted",
|
|
820
|
+
* schema: [
|
|
821
|
+
* {
|
|
822
|
+
* name: "orders",
|
|
823
|
+
* columns: {
|
|
824
|
+
* _id: "TEXT PRIMARY KEY",
|
|
825
|
+
* customerId: "TEXT NOT NULL",
|
|
826
|
+
* totalCents: "INTEGER NOT NULL",
|
|
827
|
+
* note: "TEXT",
|
|
828
|
+
* createdAt: "INTEGER NOT NULL",
|
|
829
|
+
* },
|
|
830
|
+
* indexes: ["customerId"],
|
|
831
|
+
* sealedFields: ["note"],
|
|
832
|
+
* derivedHashes: { customerIdHash: { from: "customerId" } },
|
|
833
|
+
* subjectField: "customerId",
|
|
834
|
+
* },
|
|
835
|
+
* ],
|
|
836
|
+
* });
|
|
837
|
+
*/
|
|
838
|
+
async function init(opts) {
|
|
839
|
+
if (initialized) return;
|
|
840
|
+
// Drop any prepared-statement cache leftover from a prior init/close
|
|
841
|
+
// cycle — Statement handles attached to a finalized DB throw on use.
|
|
842
|
+
_prepareCache.clear();
|
|
843
|
+
if (!opts || !opts.dataDir) {
|
|
844
|
+
throw new DbError("db/bad-init", "db.init({ dataDir }) is required");
|
|
845
|
+
}
|
|
846
|
+
if (!Array.isArray(opts.schema)) {
|
|
847
|
+
throw new DbError("db/bad-init",
|
|
848
|
+
"db.init({ schema }) must be an array of table definitions");
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
atRest = (opts.atRest || "encrypted").toLowerCase();
|
|
852
|
+
if (atRest !== "encrypted" && atRest !== "plain") {
|
|
853
|
+
throw new DbError("db/bad-at-rest",
|
|
854
|
+
"db.init: atRest must be 'encrypted' or 'plain', got: " + opts.atRest);
|
|
855
|
+
}
|
|
856
|
+
// D-M5 — operator-tunable streamLimit ceiling. Throw at config-time
|
|
857
|
+
// on bad shape so a typo surfaces at boot rather than as an
|
|
858
|
+
// unbounded stream at first export.
|
|
859
|
+
if (opts.streamLimit !== undefined) {
|
|
860
|
+
if (typeof opts.streamLimit !== "number" || !isFinite(opts.streamLimit) ||
|
|
861
|
+
opts.streamLimit <= 0 || Math.floor(opts.streamLimit) !== opts.streamLimit) {
|
|
862
|
+
throw new DbError("db/bad-init",
|
|
863
|
+
"db.init: streamLimit must be a positive finite integer; got " +
|
|
864
|
+
JSON.stringify(opts.streamLimit));
|
|
865
|
+
}
|
|
866
|
+
streamLimit = opts.streamLimit;
|
|
867
|
+
}
|
|
868
|
+
dataDir = opts.dataDir;
|
|
869
|
+
if (!nodeFs.existsSync(dataDir)) nodeFs.mkdirSync(dataDir, { recursive: true });
|
|
870
|
+
|
|
871
|
+
if (atRest === "encrypted") {
|
|
872
|
+
var tmpDir = resolveTmpDir(opts.tmpDir);
|
|
873
|
+
if (!tmpDir) {
|
|
874
|
+
throw _dbErr("db/no-tmpfs",
|
|
875
|
+
"FATAL: atRest: 'encrypted' (default) requires tmpfs but none was found. " +
|
|
876
|
+
"Provide opts.tmpDir or set BLAMEJS_TMPDIR, or pass atRest: 'plain' (with warning).");
|
|
877
|
+
}
|
|
878
|
+
if (!nodeFs.existsSync(tmpDir)) nodeFs.mkdirSync(tmpDir, { recursive: true });
|
|
879
|
+
|
|
880
|
+
// D-H7 — if the resolved tmpDir is NOT actually tmpfs, the
|
|
881
|
+
// plaintext DB file lives on persistent storage. statvfs/statfs
|
|
882
|
+
// isn't in stable Node, but on Linux we can check that tmpDir
|
|
883
|
+
// resolves under /dev/shm or /run/shm as a heuristic. On other
|
|
884
|
+
// platforms we warn that the operator must verify tmpfs binding
|
|
885
|
+
// out-of-band.
|
|
886
|
+
if (process.platform === "linux") {
|
|
887
|
+
var realTmp = "";
|
|
888
|
+
try { realTmp = nodeFs.realpathSync(tmpDir); } catch (_e) { /* stat best-effort */ }
|
|
889
|
+
if (realTmp.indexOf("/dev/shm") !== 0 && realTmp.indexOf("/run/shm") !== 0 &&
|
|
890
|
+
realTmp.indexOf("/run/user/") !== 0 && realTmp.indexOf("/tmp") !== 0) {
|
|
891
|
+
log.warn("WARNING: db.init: tmpDir '" + tmpDir + "' (real: '" + realTmp +
|
|
892
|
+
"') does not resolve under /dev/shm /run/shm /run/user /tmp — verify it is " +
|
|
893
|
+
"actually a tmpfs mount. A persistent-disk tmpDir leaks plaintext into backup " +
|
|
894
|
+
"snapshots, replication, and forensic disk images.");
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Operator overrides for the encrypted-DB on-disk nodePath. `opts.encryptedDbPath`
|
|
899
|
+
// takes a fully-qualified nodePath; `opts.encryptedDbName` overrides
|
|
900
|
+
// just the basename under `dataDir` (default "db.enc"). Helps when
|
|
901
|
+
// multiple framework-shaped instances share a dataDir.
|
|
902
|
+
encPath = opts.encryptedDbPath ||
|
|
903
|
+
nodePath.join(dataDir, opts.encryptedDbName || "db.enc");
|
|
904
|
+
dbPath = nodePath.join(tmpDir, "blamejs-" + generateToken(C.BYTES.bytes(16)) + ".db");
|
|
905
|
+
encKey = loadOrCreateDbKey(dataDir, opts.dbKeyPath);
|
|
906
|
+
|
|
907
|
+
cleanStaleTmpDbs(tmpDir);
|
|
908
|
+
decryptToTmp();
|
|
909
|
+
} else {
|
|
910
|
+
// plain mode
|
|
911
|
+
log.warn("WARNING: atRest: 'plain' — DB structure and row counts visible on disk.");
|
|
912
|
+
log.warn(" Field-level encryption (sealedFields) still protects sealed columns,");
|
|
913
|
+
log.warn(" but the simpler at-rest model is opt-out only. Default is 'encrypted'.");
|
|
914
|
+
dbPath = nodePath.join(dataDir, "blamejs.db");
|
|
915
|
+
encPath = null;
|
|
916
|
+
encKey = null;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Open the database
|
|
920
|
+
database = new DatabaseSync(dbPath);
|
|
921
|
+
|
|
922
|
+
// Performance pragmas
|
|
923
|
+
runSql(database, "PRAGMA journal_mode=WAL");
|
|
924
|
+
runSql(database, "PRAGMA synchronous=NORMAL");
|
|
925
|
+
runSql(database, "PRAGMA cache_size=-8000");
|
|
926
|
+
runSql(database, "PRAGMA temp_store=MEMORY");
|
|
927
|
+
runSql(database, "PRAGMA busy_timeout=5000");
|
|
928
|
+
runSql(database, "PRAGMA mmap_size=268435456");
|
|
929
|
+
runSql(database, "PRAGMA auto_vacuum=INCREMENTAL");
|
|
930
|
+
// Foreign-key enforcement is OFF by default in SQLite. Turn it ON so
|
|
931
|
+
// structured `foreignKeys` declarations actually constrain writes.
|
|
932
|
+
runSql(database, "PRAGMA foreign_keys=ON");
|
|
933
|
+
|
|
934
|
+
// PRAGMA secure_delete=ON — SQLite normally just unlinks rows from
|
|
935
|
+
// the B-tree; the underlying page bytes survive on disk until a new
|
|
936
|
+
// write reuses the slot. With secure_delete=ON, freed pages are
|
|
937
|
+
// overwritten with zeros so a forensic recovery against the file
|
|
938
|
+
// can't reconstruct deleted rows. The cost is one extra write per
|
|
939
|
+
// delete, which the framework's audit-and-DSR-erase path already
|
|
940
|
+
// dominates with audit-chain emissions and cascade fan-out.
|
|
941
|
+
runSql(database, "PRAGMA secure_delete=ON");
|
|
942
|
+
// PRAGMA trusted_schema=OFF — refuses to call functions / virtual-
|
|
943
|
+
// table modules referenced from a malicious shadow schema. Defends
|
|
944
|
+
// the CVE-2018-8740 family where an attacker who can write to the
|
|
945
|
+
// database file (backups, logs, restore-from-untrusted) plants
|
|
946
|
+
// schema entries that fire on next access.
|
|
947
|
+
try { runSql(database, "PRAGMA trusted_schema=OFF"); } catch (_e) { /* sqlite < 3.31 */ }
|
|
948
|
+
// PRAGMA cell_size_check=ON — refuses pages with corrupted cell
|
|
949
|
+
// sizes at parse time rather than crashing later. Cheap defense
|
|
950
|
+
// against malformed-page attacks.
|
|
951
|
+
try { runSql(database, "PRAGMA cell_size_check=ON"); } catch (_e) { /* sqlite < 3.26 */ }
|
|
952
|
+
// node:sqlite does not expose loadExtension at all — extensions must
|
|
953
|
+
// be statically linked into the runtime. The framework's surface is
|
|
954
|
+
// therefore implicitly extension-free; no runtime defense is needed
|
|
955
|
+
// beyond the trusted_schema + cell_size_check PRAGMAs above.
|
|
956
|
+
|
|
957
|
+
// Boot-time integrity check — refuse to boot on B-tree corruption.
|
|
958
|
+
// SQLite normally surfaces corruption only when a query stumbles on
|
|
959
|
+
// a bad page; that's a "first failure during request handling"
|
|
960
|
+
// surface, not a clean fail-closed boot. integrity_check is fast on
|
|
961
|
+
// the freshly-decrypted-into-tmpfs file (<1 second on a typical
|
|
962
|
+
// multi-MB DB) and the result is "ok" or a list of issues.
|
|
963
|
+
if (opts.skipBootIntegrityCheck !== true) {
|
|
964
|
+
var ic = database.prepare("PRAGMA integrity_check").all();
|
|
965
|
+
var icIssues = ic.map(function (r) { return r && r.integrity_check; })
|
|
966
|
+
.filter(function (s) { return s && s !== "ok"; });
|
|
967
|
+
if (icIssues.length > 0) {
|
|
968
|
+
throw new DbError("db/integrity-check-failed",
|
|
969
|
+
"PRAGMA integrity_check at boot reported " + icIssues.length +
|
|
970
|
+
" issue(s): " + icIssues.slice(0, 3).join("; "));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// PRAGMA integrity_check — refuse boot on B-tree corruption (per
|
|
975
|
+
// audit-batch finding). SQLite returns "ok" for a healthy database;
|
|
976
|
+
// any other result means corruption. Catching it at boot beats
|
|
977
|
+
// stumbling on it later in a query that hits the bad page. Skip
|
|
978
|
+
// when opts.skipIntegrityCheck is set (e.g. tmpfs-only fixtures).
|
|
979
|
+
if (opts.skipIntegrityCheck !== true) {
|
|
980
|
+
var integrityRows = [];
|
|
981
|
+
try {
|
|
982
|
+
// .all-style read; runSql is for statements without rows.
|
|
983
|
+
integrityRows = database.prepare("PRAGMA integrity_check").all();
|
|
984
|
+
} catch (e) {
|
|
985
|
+
throw new DbError("db/integrity-check-failed",
|
|
986
|
+
"PRAGMA integrity_check failed at boot: " + ((e && e.message) || String(e)));
|
|
987
|
+
}
|
|
988
|
+
if (integrityRows.length !== 1 ||
|
|
989
|
+
!integrityRows[0] || integrityRows[0].integrity_check !== "ok") {
|
|
990
|
+
throw new DbError("db/integrity-check-failed",
|
|
991
|
+
"PRAGMA integrity_check reported corruption: " +
|
|
992
|
+
JSON.stringify(integrityRows));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Refuse app schema entries that collide with framework-reserved names.
|
|
997
|
+
// Pre-v0.8.18 this was an exact-match Set; an app could ship
|
|
998
|
+
// `_blamejs_audit_log_archive` (or similar prefix-collision) and the
|
|
999
|
+
// framework would silently provision it next to the reserved
|
|
1000
|
+
// namespace, allowing a row-by-row look-alike attack against audit
|
|
1001
|
+
// archive tooling.
|
|
1002
|
+
// Under `frameworkTables: false` the framework's own audit_log /
|
|
1003
|
+
// consent_log are NOT provisioned, so an operator naming a table
|
|
1004
|
+
// `audit_log` (or `consent_log`) doesn't collide. The `_blamejs_*`
|
|
1005
|
+
// prefix stays reserved unconditionally — those names are
|
|
1006
|
+
// hard-claimed by other framework primitives (sessions, jobs,
|
|
1007
|
+
// migrations, rate-limit-counters, …) which still get provisioned
|
|
1008
|
+
// by their respective subsystems.
|
|
1009
|
+
var frameworkTablesEarly = opts.frameworkTables !== false;
|
|
1010
|
+
var FRAMEWORK_NAMED_RESERVED = frameworkTablesEarly
|
|
1011
|
+
? RESERVED_TABLE_NAMES
|
|
1012
|
+
: new Set(); // empty — fall back to the prefix check only
|
|
1013
|
+
for (var ri = 0; ri < opts.schema.length; ri++) {
|
|
1014
|
+
var appName = opts.schema[ri].name;
|
|
1015
|
+
if (FRAMEWORK_NAMED_RESERVED.has(appName) ||
|
|
1016
|
+
(typeof appName === "string" && appName.indexOf("_blamejs_") === 0)) {
|
|
1017
|
+
throw new DbError("db/reserved-table-name",
|
|
1018
|
+
"table name '" + appName + "' is reserved by the framework. " +
|
|
1019
|
+
"Pick a different name (the framework provisions audit_log, consent_log, " +
|
|
1020
|
+
"and any '_blamejs_*'-prefixed tables automatically). " +
|
|
1021
|
+
"Pass opts.frameworkTables: false to skip provisioning audit_log/consent_log " +
|
|
1022
|
+
"when the host application owns its own audit chain.");
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Track subject schema for subject.export/erase walks
|
|
1027
|
+
subjectTables = [];
|
|
1028
|
+
for (var si = 0; si < opts.schema.length; si++) {
|
|
1029
|
+
var st = opts.schema[si];
|
|
1030
|
+
if (st.subjectField) {
|
|
1031
|
+
// Validate personalDataCategories shape + audit-emit on
|
|
1032
|
+
// unknown vocabulary. Pre-v0.8.37 this was a free-form JSON
|
|
1033
|
+
// blob; a typo silently dropped the column from subject-export
|
|
1034
|
+
// / erase walks. The framework checks the value is a string
|
|
1035
|
+
// (catches null / number / object typos) and emits a warning
|
|
1036
|
+
// audit when the category is outside the GDPR Art 9 + general
|
|
1037
|
+
// vocabulary so operators can audit-trail their custom labels.
|
|
1038
|
+
if (st.personalDataCategories) {
|
|
1039
|
+
if (typeof st.personalDataCategories !== "object" || Array.isArray(st.personalDataCategories)) {
|
|
1040
|
+
throw new DbError("db/bad-personal-data-categories",
|
|
1041
|
+
"table '" + st.name + "': personalDataCategories must be an object mapping field name → category");
|
|
1042
|
+
}
|
|
1043
|
+
var FRAMEWORK_CATEGORY_VOCAB = [
|
|
1044
|
+
"name", "email", "phone", "address", "ip", "id-document",
|
|
1045
|
+
"biometric", "health", "genetic", "sexual-orientation",
|
|
1046
|
+
"racial-or-ethnic-origin", "political-opinion", "religious-belief",
|
|
1047
|
+
"trade-union-membership", "criminal-record",
|
|
1048
|
+
"financial", "location", "behavioral", "device-id",
|
|
1049
|
+
"child-data", "education", "employment", "operator-defined",
|
|
1050
|
+
];
|
|
1051
|
+
Object.keys(st.personalDataCategories).forEach(function (field) {
|
|
1052
|
+
var cat = st.personalDataCategories[field];
|
|
1053
|
+
if (typeof cat !== "string" || cat.length === 0) {
|
|
1054
|
+
throw new DbError("db/bad-personal-data-category",
|
|
1055
|
+
"table '" + st.name + "' field '" + field +
|
|
1056
|
+
"': category must be a non-empty string");
|
|
1057
|
+
}
|
|
1058
|
+
if (FRAMEWORK_CATEGORY_VOCAB.indexOf(cat) === -1) {
|
|
1059
|
+
// Unknown — emit a one-time audit per (table,field,category)
|
|
1060
|
+
// tuple so operators see typos in their categorical
|
|
1061
|
+
// taxonomy. Lazy require to avoid circular load (audit
|
|
1062
|
+
// imports db for chain hashing).
|
|
1063
|
+
try {
|
|
1064
|
+
var auditMod = require("./audit"); // allow:inline-require — circular-load defense (audit imports db)
|
|
1065
|
+
auditMod.safeEmit({
|
|
1066
|
+
action: "db.personal_data_category_unknown",
|
|
1067
|
+
outcome: "success",
|
|
1068
|
+
metadata: {
|
|
1069
|
+
severity: "warning",
|
|
1070
|
+
table: st.name,
|
|
1071
|
+
field: field,
|
|
1072
|
+
category: cat,
|
|
1073
|
+
vocabHint: "use one of: " + FRAMEWORK_CATEGORY_VOCAB.join(", ") +
|
|
1074
|
+
" (or operator-defined for genuinely-custom)",
|
|
1075
|
+
},
|
|
1076
|
+
});
|
|
1077
|
+
} catch (_e) { /* drop-silent */ }
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
subjectTables.push({
|
|
1082
|
+
name: st.name,
|
|
1083
|
+
subjectField: st.subjectField,
|
|
1084
|
+
personalDataCategories: st.personalDataCategories || {},
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Operator opt-out for the framework's own tables + audit/consent
|
|
1090
|
+
// chain machinery + WORM assertion + audit-signing bootstrap. Set
|
|
1091
|
+
// `frameworkTables: false` when the host application maintains its
|
|
1092
|
+
// own audit/consent semantics and just wants the framework's
|
|
1093
|
+
// primitives (vault / db / cryptoField / etc.) without the bundled
|
|
1094
|
+
// chain tables. When OFF, every framework-table-dependent step
|
|
1095
|
+
// below is a no-op. Append-only triggers are scoped to the
|
|
1096
|
+
// framework tables only, so they're skipped too.
|
|
1097
|
+
//
|
|
1098
|
+
// `auditSigning: false` is a finer-grained gate — keep the
|
|
1099
|
+
// framework tables but skip the audit-signing-key bootstrap (HS-
|
|
1100
|
+
// shape deployments that already manage their own signing key).
|
|
1101
|
+
//
|
|
1102
|
+
// Defaults match v0.8.57 behavior: both ON.
|
|
1103
|
+
var frameworkTablesEnabled = opts.frameworkTables !== false;
|
|
1104
|
+
var auditSigningEnabled = opts.auditSigning !== false;
|
|
1105
|
+
|
|
1106
|
+
// Build the full schema = framework-baked tables + app tables.
|
|
1107
|
+
// Framework tables come FIRST so audit_log/consent_log exist before any
|
|
1108
|
+
// app migration can reference them. When `frameworkTables: false`,
|
|
1109
|
+
// skip the concat so the operator's own `audit_log` (or whatever
|
|
1110
|
+
// shape) doesn't collide with the framework's.
|
|
1111
|
+
var fullSchema = frameworkTablesEnabled
|
|
1112
|
+
? FRAMEWORK_SCHEMA.concat(opts.schema)
|
|
1113
|
+
: opts.schema.slice();
|
|
1114
|
+
|
|
1115
|
+
// Register schema with field-crypto + capture table metadata snapshot
|
|
1116
|
+
// (framework tables included so getTableMetadata covers everything).
|
|
1117
|
+
tableMetadata = {};
|
|
1118
|
+
for (var i = 0; i < fullSchema.length; i++) {
|
|
1119
|
+
var t = fullSchema[i];
|
|
1120
|
+
cryptoField.registerTable(t.name, {
|
|
1121
|
+
sealedFields: t.sealedFields,
|
|
1122
|
+
derivedHashes: t.derivedHashes,
|
|
1123
|
+
hashNamespaces: t.hashNamespaces,
|
|
1124
|
+
});
|
|
1125
|
+
tableMetadata[t.name] = {
|
|
1126
|
+
primaryKey: _normalizePk(t),
|
|
1127
|
+
foreignKeys: Array.isArray(t.foreignKeys) ? t.foreignKeys.slice() : [],
|
|
1128
|
+
columns: Object.assign({}, t.columns),
|
|
1129
|
+
indexes: Array.isArray(t.indexes) ? t.indexes.slice() : [],
|
|
1130
|
+
sealedFields: Array.isArray(t.sealedFields) ? t.sealedFields.slice() : [],
|
|
1131
|
+
derivedHashes: Object.assign({}, t.derivedHashes || {}),
|
|
1132
|
+
subjectField: t.subjectField || null,
|
|
1133
|
+
personalDataCategories: Object.assign({}, t.personalDataCategories || {}),
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Declarative schema reconcile (framework + app tables)
|
|
1138
|
+
dbSchema.reconcile(database, fullSchema);
|
|
1139
|
+
|
|
1140
|
+
// Append-only enforcement on audit_log + consent_log via SQLite triggers.
|
|
1141
|
+
// Apps cannot UPDATE or DELETE these tables; the framework's audit.record /
|
|
1142
|
+
// consent.grant only INSERT. This is a SQL-level guard against bug-induced
|
|
1143
|
+
// or malicious tampering — independent of the API surface's discipline.
|
|
1144
|
+
// Operator-driven retention purge (when implemented) must drop these
|
|
1145
|
+
// triggers explicitly inside a transaction, perform the purge, and
|
|
1146
|
+
// recreate them. Skipped under `frameworkTables: false`.
|
|
1147
|
+
if (frameworkTablesEnabled) _installAppendOnlyTriggers(database);
|
|
1148
|
+
|
|
1149
|
+
// Imperative migrations (run once each, in order)
|
|
1150
|
+
if (opts.migrationDir) {
|
|
1151
|
+
var result = dbSchema.runMigrations(database, opts.migrationDir);
|
|
1152
|
+
if (result.applied.length > 0) {
|
|
1153
|
+
log("applied " + result.applied.length + " migration(s): " + result.applied.join(", "));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// dataResidency — operator's declared region. Registered here for
|
|
1158
|
+
// downstream backends (storage, mail, log destinations) to validate
|
|
1159
|
+
// against; backends opt in by reading this value via getDataResidency().
|
|
1160
|
+
dataResidency = opts.dataResidency || null;
|
|
1161
|
+
|
|
1162
|
+
// Mark initialized BEFORE the chain verify so audit/consent.verify() can
|
|
1163
|
+
// call db.prepare() through the public surface. If verify fails, we
|
|
1164
|
+
// process.exit() — initialized state is moot at that point.
|
|
1165
|
+
initialized = true;
|
|
1166
|
+
|
|
1167
|
+
// ---- Refuse-to-boot on chain break ----
|
|
1168
|
+
// Verify both the audit and consent chains end-to-end. A broken chain
|
|
1169
|
+
// means tamper-evidence has been compromised — the framework refuses
|
|
1170
|
+
// to continue under any circumstances. Recovery is operator-driven
|
|
1171
|
+
// (restore from backup or manual chain rebuild); the framework only
|
|
1172
|
+
// detects-and-fails. Skipped under `frameworkTables: false` (the
|
|
1173
|
+
// framework's audit_log / consent_log don't exist for an operator
|
|
1174
|
+
// running their own audit subsystem).
|
|
1175
|
+
if (frameworkTablesEnabled) {
|
|
1176
|
+
var auditResult = await audit.verify();
|
|
1177
|
+
if (!auditResult.ok) {
|
|
1178
|
+
// Fire the breach event BEFORE throwing so operator listeners
|
|
1179
|
+
// get a chance at sync I/O (file flag, console alert) before
|
|
1180
|
+
// init unwinds.
|
|
1181
|
+
events.emit(events.EVENTS.AUDIT_CHAIN_BREAK, { table: "audit_log", result: auditResult });
|
|
1182
|
+
throw _dbErr("db/audit-chain-break",
|
|
1183
|
+
"FATAL: audit_log chain integrity broken at row " + auditResult.breakAt +
|
|
1184
|
+
" (" + auditResult.reason + "); break row _id: " + auditResult.breakRowId +
|
|
1185
|
+
"; expected: " + auditResult.expected + "; actual: " + auditResult.actual +
|
|
1186
|
+
". Refusing to boot. Compliance requires that any tamper-detection signal halt service. " +
|
|
1187
|
+
"Recovery is manual: restore from backup, or rebuild the audit chain from a verified earlier snapshot.");
|
|
1188
|
+
}
|
|
1189
|
+
var consentResult = await consent.verify();
|
|
1190
|
+
if (!consentResult.ok) {
|
|
1191
|
+
events.emit(events.EVENTS.AUDIT_CHAIN_BREAK, { table: "consent_log", result: consentResult });
|
|
1192
|
+
throw _dbErr("db/consent-chain-break",
|
|
1193
|
+
"FATAL: consent_log chain integrity broken at row " + consentResult.breakAt +
|
|
1194
|
+
" (" + consentResult.reason + "); break row _id: " + consentResult.breakRowId +
|
|
1195
|
+
". Refusing to boot.");
|
|
1196
|
+
}
|
|
1197
|
+
log("audit chain ok (" + auditResult.rowsVerified + " rows), consent chain ok (" + consentResult.rowsVerified + " rows)");
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ---- Rollback detection (audit.tip sidecar) ----
|
|
1201
|
+
// The framework writes <dataDir>/audit.tip on each checkpoint. At boot we
|
|
1202
|
+
// compare current MAX(monotonicCounter) to the recorded tip. If current
|
|
1203
|
+
// is BELOW tip — the DB was rolled back to an older snapshot. Refuse boot.
|
|
1204
|
+
_checkRollback(dataDir);
|
|
1205
|
+
|
|
1206
|
+
// ---- F-RET-2 — WORM posture assertion ----
|
|
1207
|
+
// Under sec-17a-4 / finra-4511 / fda-21cfr11 postures the operator
|
|
1208
|
+
// MUST have declared row-level WORM on at least one business-record
|
|
1209
|
+
// table. Refuse boot otherwise so missing-declaration drift is
|
|
1210
|
+
// surfaced at start-up, not on the first delete.
|
|
1211
|
+
// Skipped under `frameworkTables: false` — WORM declarations are
|
|
1212
|
+
// an operator-side concern when the framework isn't owning audit.
|
|
1213
|
+
if (frameworkTablesEnabled) {
|
|
1214
|
+
try { _assertWormUnderPosture(); }
|
|
1215
|
+
catch (e) {
|
|
1216
|
+
// The assertion throws under regulated postures; let it
|
|
1217
|
+
// propagate. Outside regulated postures it's a no-op.
|
|
1218
|
+
throw e;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// ---- Audit-signing key + checkpoint subsystem ----
|
|
1223
|
+
// Default mode 'wrapped' (passphrase-required, separate from vault). Apps
|
|
1224
|
+
// that want a quick-start dev path can pass auditSigning: { mode: 'plaintext' }
|
|
1225
|
+
// — same warning pattern as vault.
|
|
1226
|
+
// opts.auditSigning.algorithm picks the keypair algorithm at first-run
|
|
1227
|
+
// generation. Default = SLH-DSA-SHAKE-256f (matches the framework's
|
|
1228
|
+
// SHAKE-family hash posture); ML-DSA-87 is the throughput-focused
|
|
1229
|
+
// opt-in. Existing key files take their algorithm from disk; this
|
|
1230
|
+
// option only matters on first generation.
|
|
1231
|
+
// Operator opt-out via `auditSigning: false` skips the signing
|
|
1232
|
+
// bootstrap entirely. Also implicitly skipped when frameworkTables
|
|
1233
|
+
// are off (no audit_log to sign checkpoints over).
|
|
1234
|
+
if (auditSigningEnabled && frameworkTablesEnabled) {
|
|
1235
|
+
var auditSigningMode = (opts.auditSigning && opts.auditSigning.mode)
|
|
1236
|
+
? opts.auditSigning.mode
|
|
1237
|
+
: safeEnv.readVar("BLAMEJS_AUDIT_SIGNING_MODE", {
|
|
1238
|
+
default: "wrapped",
|
|
1239
|
+
enum: ["wrapped", "plaintext"],
|
|
1240
|
+
});
|
|
1241
|
+
var auditSigningAlg = opts.auditSigning && opts.auditSigning.algorithm
|
|
1242
|
+
? opts.auditSigning.algorithm
|
|
1243
|
+
: null;
|
|
1244
|
+
await auditSign.init({
|
|
1245
|
+
dataDir: dataDir,
|
|
1246
|
+
mode: auditSigningMode,
|
|
1247
|
+
algorithm: auditSigningAlg || undefined,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// Verify all existing checkpoint signatures (defense against
|
|
1252
|
+
// signature forgery attempt + key-rotation gone wrong). Refuse to
|
|
1253
|
+
// boot on failure. Skipped under `frameworkTables: false` /
|
|
1254
|
+
// `auditSigning: false`.
|
|
1255
|
+
if (frameworkTablesEnabled && auditSigningEnabled) {
|
|
1256
|
+
var ckptResult = await audit.verifyCheckpoints();
|
|
1257
|
+
if (!ckptResult.ok) {
|
|
1258
|
+
events.emit(events.EVENTS.AUDIT_CHECKPOINT_BREAK, { result: ckptResult });
|
|
1259
|
+
throw _dbErr("db/audit-checkpoint-break",
|
|
1260
|
+
"FATAL: audit checkpoint verification failed at row " +
|
|
1261
|
+
ckptResult.breakAt + " (" + ckptResult.reason + "); checkpoint _id: " +
|
|
1262
|
+
ckptResult.checkpointId + ". Refusing to boot. Either the audit-signing key " +
|
|
1263
|
+
"was rotated without retaining the prior pubkey, or a forged checkpoint was inserted.");
|
|
1264
|
+
}
|
|
1265
|
+
log("audit checkpoints ok (" + ckptResult.checkpointsVerified + " signed)");
|
|
1266
|
+
|
|
1267
|
+
// Anchor a fresh checkpoint at boot if there's any new audit
|
|
1268
|
+
// activity since the last checkpoint (else no-op).
|
|
1269
|
+
await audit.checkpoint({ skipIfUnchanged: true });
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// ---- NTP drift check ----
|
|
1273
|
+
// Best-effort; unreachable NTP doesn't fail boot, but >= 1hr drift does
|
|
1274
|
+
// (unless BLAMEJS_NTP_STRICT=0 / BLAMEJS_SKIP_NTP_CHECK=1).
|
|
1275
|
+
await _runNtpBootCheck(opts);
|
|
1276
|
+
|
|
1277
|
+
// Start periodic encrypt timer (encrypted mode only)
|
|
1278
|
+
if (atRest === "encrypted") {
|
|
1279
|
+
encTimer = safeAsync.repeating(function () {
|
|
1280
|
+
try { encryptToDisk(); } catch (e) {
|
|
1281
|
+
log.error("periodic encrypt failed: " + e.message);
|
|
1282
|
+
}
|
|
1283
|
+
}, C.TIME.minutes(5), { name: "db-periodic-encrypt" });
|
|
1284
|
+
|
|
1285
|
+
// Final encrypt on process exit. We don't try to unlink the plaintext
|
|
1286
|
+
// here — the SQLite handle may still be open, and the OS reclaims tmpfs
|
|
1287
|
+
// on reboot anyway. close() does the orderly shutdown.
|
|
1288
|
+
process.on("exit", function () {
|
|
1289
|
+
try { encryptToDisk(); } catch (_e) { /* exit handler — silent */ }
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
log("ready (mode: " + atRest + ", path: " + dbPath + ")");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// ---- Public API ----
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* @primitive b.db.from
|
|
1300
|
+
* @signature b.db.from(tableName)
|
|
1301
|
+
* @since 0.1.0
|
|
1302
|
+
* @status stable
|
|
1303
|
+
* @related b.db.prepare, b.db.transaction, b.db.stream
|
|
1304
|
+
*
|
|
1305
|
+
* Open a chainable Query against a registered table. Sealed columns
|
|
1306
|
+
* auto-encrypt on insert/update and auto-decrypt on read; derived-
|
|
1307
|
+
* hash columns auto-populate from their source field on insert.
|
|
1308
|
+
* Identifier safety, parameter binding, row-policy gates, and
|
|
1309
|
+
* audit-emission are wired into the chain so operator code never
|
|
1310
|
+
* concatenates SQL by hand.
|
|
1311
|
+
*
|
|
1312
|
+
* @example
|
|
1313
|
+
* var b = require("blamejs");
|
|
1314
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
1315
|
+
* { name: "orders",
|
|
1316
|
+
* columns: { _id: "TEXT PRIMARY KEY", customerId: "TEXT NOT NULL", totalCents: "INTEGER NOT NULL" },
|
|
1317
|
+
* sealedFields: ["customerId"] },
|
|
1318
|
+
* ] });
|
|
1319
|
+
*
|
|
1320
|
+
* b.db.from("orders").insert({
|
|
1321
|
+
* _id: b.uuid.v7(), customerId: "cust_123", totalCents: 4999,
|
|
1322
|
+
* });
|
|
1323
|
+
*
|
|
1324
|
+
* var rows = b.db.from("orders").where({ customerId: "cust_123" }).all();
|
|
1325
|
+
* rows.length;
|
|
1326
|
+
* // → 1
|
|
1327
|
+
*/
|
|
1328
|
+
function from(tableName) {
|
|
1329
|
+
_requireInit();
|
|
1330
|
+
return new Query(database, tableName);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// D-M6 — bounded prepared-statement cache for SQLite. Long-running
|
|
1334
|
+
// daemons with diverse query shapes accumulate node:sqlite Statement
|
|
1335
|
+
// handles indefinitely; the LRU here caps at PREPARE_CACHE_MAX (256)
|
|
1336
|
+
// distinct SQL strings and finalizes the oldest when over. Reuse of
|
|
1337
|
+
// the same SQL string returns the cached Statement (the canonical
|
|
1338
|
+
// node:sqlite-style win); previously this was ad-hoc and operators
|
|
1339
|
+
// re-preparing in a hot path leaked fds.
|
|
1340
|
+
var PREPARE_CACHE_MAX = 256; // allow:raw-byte-literal — distinct-statement cache cap
|
|
1341
|
+
var _prepareCache = new Map(); // sql → Statement (insertion order = LRU)
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* @primitive b.db.prepare
|
|
1345
|
+
* @signature b.db.prepare(sql)
|
|
1346
|
+
* @since 0.1.0
|
|
1347
|
+
* @status stable
|
|
1348
|
+
* @related b.db.from, b.db.runSql, b.db.stream
|
|
1349
|
+
*
|
|
1350
|
+
* Raw-escape-hatch wrapper around `node:sqlite`'s `Statement`
|
|
1351
|
+
* preparation, with an LRU cache keyed by SQL string (cap 256
|
|
1352
|
+
* distinct shapes). Reuse of the same SQL returns the cached
|
|
1353
|
+
* Statement so a hot path doesn't churn file descriptors. Use
|
|
1354
|
+
* `b.db.from(table)` for the typical chainable surface; `prepare` is
|
|
1355
|
+
* for the rare cases where the chainable Query doesn't cover the
|
|
1356
|
+
* shape.
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* var b = require("blamejs");
|
|
1360
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
1361
|
+
* { name: "orders",
|
|
1362
|
+
* columns: { _id: "TEXT PRIMARY KEY", totalCents: "INTEGER NOT NULL" } },
|
|
1363
|
+
* ] });
|
|
1364
|
+
*
|
|
1365
|
+
* var stmt = b.db.prepare("SELECT SUM(totalCents) AS total FROM orders");
|
|
1366
|
+
* var row = stmt.get();
|
|
1367
|
+
* typeof row.total;
|
|
1368
|
+
* // → "object"
|
|
1369
|
+
*/
|
|
1370
|
+
function prepare(sql) {
|
|
1371
|
+
_requireInit();
|
|
1372
|
+
if (_prepareCache.has(sql)) {
|
|
1373
|
+
var hit = _prepareCache.get(sql);
|
|
1374
|
+
// Refresh LRU position by reinserting.
|
|
1375
|
+
_prepareCache.delete(sql);
|
|
1376
|
+
_prepareCache.set(sql, hit);
|
|
1377
|
+
return hit;
|
|
1378
|
+
}
|
|
1379
|
+
var stmt = database.prepare(sql);
|
|
1380
|
+
_prepareCache.set(sql, stmt);
|
|
1381
|
+
if (_prepareCache.size > PREPARE_CACHE_MAX) {
|
|
1382
|
+
var oldestKey = _prepareCache.keys().next().value;
|
|
1383
|
+
_prepareCache.delete(oldestKey);
|
|
1384
|
+
}
|
|
1385
|
+
return stmt;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* @primitive b.db.stream
|
|
1390
|
+
* @signature b.db.stream(sql)
|
|
1391
|
+
* @since 0.4.0
|
|
1392
|
+
* @status stable
|
|
1393
|
+
* @related b.db.from, b.db.prepare, b.db.exportCsv
|
|
1394
|
+
*
|
|
1395
|
+
* Object-mode `Readable` that yields rows as `node:sqlite`'s
|
|
1396
|
+
* `iterate()` produces them. Unlike `.all()`, the engine never
|
|
1397
|
+
* materializes the full result set, so audit exports, backup table
|
|
1398
|
+
* dumps, and million-row reports finish without OOM pressure.
|
|
1399
|
+
* Variadic: positional parameter bindings come after `sql`; an
|
|
1400
|
+
* optional final plain-object argument carries `opts.table` (enables
|
|
1401
|
+
* sealed-column auto-unseal) and `opts.streamLimit` (per-call row
|
|
1402
|
+
* ceiling override). Default ceiling is the module-level
|
|
1403
|
+
* `streamLimit` (1_000_000); the stream destroys with a
|
|
1404
|
+
* `db/stream-limit-exceeded` error past the cap rather than
|
|
1405
|
+
* accumulating unboundedly.
|
|
1406
|
+
*
|
|
1407
|
+
* @example
|
|
1408
|
+
* var b = require("blamejs");
|
|
1409
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
1410
|
+
* { name: "events",
|
|
1411
|
+
* columns: { _id: "TEXT PRIMARY KEY", payload: "TEXT" },
|
|
1412
|
+
* sealedFields: ["payload"] },
|
|
1413
|
+
* ] });
|
|
1414
|
+
*
|
|
1415
|
+
* var count = 0;
|
|
1416
|
+
* var s = b.db.stream("SELECT * FROM events", { table: "events" });
|
|
1417
|
+
* await new Promise(function (resolve, reject) {
|
|
1418
|
+
* s.on("data", function (_row) { count += 1; });
|
|
1419
|
+
* s.on("end", resolve);
|
|
1420
|
+
* s.on("error", reject);
|
|
1421
|
+
* });
|
|
1422
|
+
* count >= 0;
|
|
1423
|
+
* // → true
|
|
1424
|
+
*/
|
|
1425
|
+
function stream(sql) {
|
|
1426
|
+
_requireInit();
|
|
1427
|
+
var opts = null;
|
|
1428
|
+
var params;
|
|
1429
|
+
// Last arg may be a plain {table?} options object; everything else
|
|
1430
|
+
// is a SQL parameter binding. node:sqlite accepts numbers, strings,
|
|
1431
|
+
// bigints, Buffers, and null — plain objects can only be opts.
|
|
1432
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
|
1433
|
+
if (args.length > 0) {
|
|
1434
|
+
var last = args[args.length - 1];
|
|
1435
|
+
var isOptsShape = last !== null && typeof last === "object" &&
|
|
1436
|
+
!Buffer.isBuffer(last) && !Array.isArray(last) &&
|
|
1437
|
+
typeof last.length !== "number"; // exclude TypedArray-shapes
|
|
1438
|
+
if (isOptsShape) {
|
|
1439
|
+
opts = last;
|
|
1440
|
+
params = args.slice(0, -1);
|
|
1441
|
+
} else {
|
|
1442
|
+
params = args;
|
|
1443
|
+
}
|
|
1444
|
+
} else {
|
|
1445
|
+
params = [];
|
|
1446
|
+
}
|
|
1447
|
+
var table = opts && typeof opts.table === "string" ? opts.table : null;
|
|
1448
|
+
var unseal = table ? cryptoField : null;
|
|
1449
|
+
|
|
1450
|
+
// D-M5 — streamLimit ceiling. Per-call opts.streamLimit overrides
|
|
1451
|
+
// the module-level default; bad shape throws at call time so the
|
|
1452
|
+
// typo surfaces instead of an unbounded stream.
|
|
1453
|
+
var perCallLimit = streamLimit;
|
|
1454
|
+
if (opts && opts.streamLimit !== undefined) {
|
|
1455
|
+
if (typeof opts.streamLimit !== "number" || !isFinite(opts.streamLimit) ||
|
|
1456
|
+
opts.streamLimit <= 0 || Math.floor(opts.streamLimit) !== opts.streamLimit) {
|
|
1457
|
+
throw new DbError("db/bad-stream-limit",
|
|
1458
|
+
"db.stream: opts.streamLimit must be a positive finite integer; got " +
|
|
1459
|
+
JSON.stringify(opts.streamLimit));
|
|
1460
|
+
}
|
|
1461
|
+
perCallLimit = opts.streamLimit;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
var stmt;
|
|
1465
|
+
var iter;
|
|
1466
|
+
try {
|
|
1467
|
+
stmt = database.prepare(sql);
|
|
1468
|
+
iter = stmt.iterate.apply(stmt, params);
|
|
1469
|
+
} catch (e) {
|
|
1470
|
+
var r = new Readable({ objectMode: true, read: function () {} });
|
|
1471
|
+
setImmediate(function () { r.destroy(e); });
|
|
1472
|
+
return r;
|
|
1473
|
+
}
|
|
1474
|
+
var emitted = 0;
|
|
1475
|
+
return new Readable({
|
|
1476
|
+
objectMode: true,
|
|
1477
|
+
read: function () {
|
|
1478
|
+
try {
|
|
1479
|
+
if (emitted >= perCallLimit) {
|
|
1480
|
+
this.destroy(new DbError("db/stream-limit-exceeded",
|
|
1481
|
+
"db.stream: emitted " + emitted + " rows, exceeding streamLimit " +
|
|
1482
|
+
perCallLimit + ". Pass opts.streamLimit higher OR raise via " +
|
|
1483
|
+
"db.init({ streamLimit }) after auditing the export path."));
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
var step = iter.next();
|
|
1487
|
+
if (step.done) { this.push(null); return; }
|
|
1488
|
+
emitted += 1;
|
|
1489
|
+
var row = step.value;
|
|
1490
|
+
this.push(unseal ? unseal.unsealRow(table, row) : row);
|
|
1491
|
+
} catch (e) {
|
|
1492
|
+
this.destroy(e);
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// DDL_RE — case-insensitive prefix match for the eight statement
|
|
1499
|
+
// shapes that MUTATE schema. Audited individually so a forensic
|
|
1500
|
+
// review can reconstruct schema evolution from the chain alone (D-M1).
|
|
1501
|
+
var DDL_RE = /^\s*(CREATE|DROP|ALTER|TRUNCATE|RENAME|ATTACH|DETACH|REINDEX)\b/i;
|
|
1502
|
+
|
|
1503
|
+
// D-L7 — slow-query observability buckets for the local SQLite nodePath.
|
|
1504
|
+
// Highest matched bucket wins so the per-query emit is single-shot;
|
|
1505
|
+
// operators dashboard on the `bucket` label.
|
|
1506
|
+
var _SLOW_QUERY_BUCKETS_LOCAL = Object.freeze([
|
|
1507
|
+
{ ms: C.TIME.seconds(30), label: "30s" },
|
|
1508
|
+
{ ms: C.TIME.seconds(5), label: "5s" },
|
|
1509
|
+
{ ms: C.TIME.seconds(1), label: "1s" },
|
|
1510
|
+
]);
|
|
1511
|
+
var _STATEMENT_CLASS_RE_LOCAL = /^\s*(?:\/\*[\s\S]*?\*\/\s*|--[^\n]*\n\s*)*([A-Za-z]+)/;
|
|
1512
|
+
function _classifyStatementLocal(sql) {
|
|
1513
|
+
if (typeof sql !== "string" || sql.length === 0) return "UNKNOWN";
|
|
1514
|
+
var m = _STATEMENT_CLASS_RE_LOCAL.exec(sql);
|
|
1515
|
+
return m ? m[1].toUpperCase() : "UNKNOWN";
|
|
1516
|
+
}
|
|
1517
|
+
function _reportSlowSqlite(durationMs, statement) {
|
|
1518
|
+
if (typeof durationMs !== "number" || !isFinite(durationMs)) return;
|
|
1519
|
+
for (var i = 0; i < _SLOW_QUERY_BUCKETS_LOCAL.length; i++) {
|
|
1520
|
+
var bucket = _SLOW_QUERY_BUCKETS_LOCAL[i];
|
|
1521
|
+
if (durationMs >= bucket.ms) {
|
|
1522
|
+
try {
|
|
1523
|
+
observability.event("db.query.slow", durationMs, {
|
|
1524
|
+
backend: "sqlite",
|
|
1525
|
+
bucket: bucket.label,
|
|
1526
|
+
statementClass: _classifyStatementLocal(statement),
|
|
1527
|
+
"db.statement": String(statement || "").slice(0, 256), // allow:raw-byte-literal — log-truncation length, not bytes
|
|
1528
|
+
});
|
|
1529
|
+
} catch (_e) { /* hot-path observability sink — drop-silent by design */ }
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
function execRaw(sql) {
|
|
1536
|
+
_requireInit();
|
|
1537
|
+
var startedAt = Date.now();
|
|
1538
|
+
var auditMod = (function () { try { return require("./audit"); } catch (_e) { return null; } })(); // allow:inline-require — circular-load defense (audit imports db)
|
|
1539
|
+
// DDL_RE only matches the leading keyword — bounded by `/\s*(KEYWORD)\b/`
|
|
1540
|
+
// so the test is constant-time regardless of the rest of the query.
|
|
1541
|
+
var isDdl = typeof sql === "string" && DDL_RE.test(sql); // allow:regex-no-length-cap — leading-keyword anchor; constant-time test
|
|
1542
|
+
try {
|
|
1543
|
+
var result = runSql(database, sql);
|
|
1544
|
+
var durationMs = Date.now() - startedAt;
|
|
1545
|
+
_reportSlowSqlite(durationMs, sql);
|
|
1546
|
+
if (isDdl && auditMod) {
|
|
1547
|
+
auditMod.safeEmit({
|
|
1548
|
+
action: "db.ddl.executed",
|
|
1549
|
+
outcome: "success",
|
|
1550
|
+
metadata: {
|
|
1551
|
+
// OTel db.* semconv (F-RFC-4) — emit framework-conventional
|
|
1552
|
+
// attributes alongside the audit row so dashboards built on
|
|
1553
|
+
// OTel can correlate without an adapter.
|
|
1554
|
+
"db.system": "sqlite",
|
|
1555
|
+
"db.operation": String(sql).match(DDL_RE)[1].toUpperCase(),
|
|
1556
|
+
"db.statement": String(sql).slice(0, 256), // allow:raw-byte-literal — log-truncation length, not bytes
|
|
1557
|
+
durationMs: durationMs,
|
|
1558
|
+
},
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
return result;
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
var failureMs = Date.now() - startedAt;
|
|
1564
|
+
_reportSlowSqlite(failureMs, sql);
|
|
1565
|
+
if (isDdl && auditMod) {
|
|
1566
|
+
auditMod.safeEmit({
|
|
1567
|
+
action: "db.ddl.executed",
|
|
1568
|
+
outcome: "failure",
|
|
1569
|
+
reason: (e && e.message) || String(e),
|
|
1570
|
+
metadata: {
|
|
1571
|
+
"db.system": "sqlite",
|
|
1572
|
+
"db.operation": String(sql).match(DDL_RE)[1].toUpperCase(),
|
|
1573
|
+
"db.statement": String(sql).slice(0, 256), // allow:raw-byte-literal — log-truncation length, not bytes
|
|
1574
|
+
durationMs: failureMs,
|
|
1575
|
+
},
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
throw e;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* @primitive b.db.transaction
|
|
1584
|
+
* @signature b.db.transaction(fn)
|
|
1585
|
+
* @since 0.1.0
|
|
1586
|
+
* @status stable
|
|
1587
|
+
* @related b.db.from, b.db.eraseHard
|
|
1588
|
+
*
|
|
1589
|
+
* Run `fn(db)` inside a `BEGIN ... COMMIT` block; any throw inside
|
|
1590
|
+
* `fn` triggers `ROLLBACK` and re-propagates the error. Returns the
|
|
1591
|
+
* value `fn` returned. Transactions compose with the chainable
|
|
1592
|
+
* Query surface and with audit-chain emissions inside the body — the
|
|
1593
|
+
* audit row's chain hash is computed from the value at COMMIT time,
|
|
1594
|
+
* so a rolled-back transaction never leaves a phantom row in
|
|
1595
|
+
* `audit_log`.
|
|
1596
|
+
*
|
|
1597
|
+
* @example
|
|
1598
|
+
* var b = require("blamejs");
|
|
1599
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
1600
|
+
* { name: "ledger",
|
|
1601
|
+
* columns: { _id: "TEXT PRIMARY KEY", balanceCents: "INTEGER NOT NULL" } },
|
|
1602
|
+
* ] });
|
|
1603
|
+
*
|
|
1604
|
+
* b.db.from("ledger").insert({ _id: "acct_1", balanceCents: 100 });
|
|
1605
|
+
* b.db.from("ledger").insert({ _id: "acct_2", balanceCents: 0 });
|
|
1606
|
+
*
|
|
1607
|
+
* b.db.transaction(function (db) {
|
|
1608
|
+
* db.from("ledger").where({ _id: "acct_1" }).update({ balanceCents: 50 });
|
|
1609
|
+
* db.from("ledger").where({ _id: "acct_2" }).update({ balanceCents: 50 });
|
|
1610
|
+
* });
|
|
1611
|
+
*
|
|
1612
|
+
* b.db.from("ledger").where({ _id: "acct_2" }).first().balanceCents;
|
|
1613
|
+
* // → 50
|
|
1614
|
+
*/
|
|
1615
|
+
function transaction(fn) {
|
|
1616
|
+
_requireInit();
|
|
1617
|
+
if (typeof fn !== "function") {
|
|
1618
|
+
throw new DbError("db/bad-transaction-fn", "transaction requires a function");
|
|
1619
|
+
}
|
|
1620
|
+
runSql(database, "BEGIN");
|
|
1621
|
+
try {
|
|
1622
|
+
var result = fn(module.exports);
|
|
1623
|
+
runSql(database, "COMMIT");
|
|
1624
|
+
return result;
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
try { runSql(database, "ROLLBACK"); } catch (_e) { /* ignore — already error */ }
|
|
1627
|
+
throw e;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* @primitive b.db.hashFor
|
|
1633
|
+
* @signature b.db.hashFor(table, field, value)
|
|
1634
|
+
* @since 0.1.0
|
|
1635
|
+
* @status stable
|
|
1636
|
+
* @related b.db.from
|
|
1637
|
+
*
|
|
1638
|
+
* Look up the deterministic SHA3 hash a sealed-source field maps to
|
|
1639
|
+
* via the table's registered `derivedHashes`. Used to query a sealed
|
|
1640
|
+
* column without unsealing every row — operator code passes the
|
|
1641
|
+
* cleartext, the framework hashes it through the same namespaced
|
|
1642
|
+
* derivation, and a `WHERE <hashColumn> = ?` lookup returns the
|
|
1643
|
+
* matching rows. Returns `null` when the field has no derived-hash
|
|
1644
|
+
* declaration on the table.
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* var b = require("blamejs");
|
|
1648
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
1649
|
+
* { name: "users",
|
|
1650
|
+
* columns: { _id: "TEXT PRIMARY KEY", email: "TEXT", emailHash: "TEXT" },
|
|
1651
|
+
* sealedFields: ["email"],
|
|
1652
|
+
* derivedHashes: { emailHash: { from: "email" } } },
|
|
1653
|
+
* ] });
|
|
1654
|
+
*
|
|
1655
|
+
* b.db.from("users").insert({ _id: "u1", email: "alice@example.com" });
|
|
1656
|
+
*
|
|
1657
|
+
* var h = b.db.hashFor("users", "email", "alice@example.com");
|
|
1658
|
+
* typeof h;
|
|
1659
|
+
* // → "string"
|
|
1660
|
+
*/
|
|
1661
|
+
function hashFor(table, field, value) {
|
|
1662
|
+
_requireInit();
|
|
1663
|
+
var lookup = cryptoField.lookupHash(table, field, value);
|
|
1664
|
+
return lookup ? lookup.value : null;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// _ddlToJsonSchemaType — best-effort SQL→JSON Schema type mapping.
|
|
1668
|
+
// SQLite is dynamically typed but the framework's DDL syntax pins
|
|
1669
|
+
// concrete types; we map them here. Operator-supplied custom types
|
|
1670
|
+
// (rare) fall back to "string" so the schema remains usable.
|
|
1671
|
+
function _ddlToJsonSchemaType(ddl) {
|
|
1672
|
+
if (typeof ddl !== "string" || ddl.length === 0) return { type: "string" };
|
|
1673
|
+
var head = ddl.split(/\s+/)[0].toUpperCase();
|
|
1674
|
+
if (head === "INTEGER" || head === "INT" || head === "BIGINT") return { type: "integer" };
|
|
1675
|
+
if (head === "REAL" || head === "FLOAT" || head === "DOUBLE" || head === "NUMERIC") return { type: "number" };
|
|
1676
|
+
if (head === "BOOLEAN" || head === "BOOL") return { type: "boolean" };
|
|
1677
|
+
if (head === "BLOB") return { type: "string", contentEncoding: "base64" };
|
|
1678
|
+
if (head === "TEXT" || head === "VARCHAR" || head === "CHAR") return { type: "string" };
|
|
1679
|
+
return { type: "string" };
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// _tableToJsonSchema2020 — emit a JSON Schema 2020-12 description of
|
|
1683
|
+
// the named table. Sealed columns get an `x-blamejs-sealed: true`
|
|
1684
|
+
// annotation so consumers know the value is encrypted at rest;
|
|
1685
|
+
// derived-hash columns gain `x-blamejs-derived-from`. The schema's
|
|
1686
|
+
// `$schema` URI explicitly names the 2020-12 dialect so generated
|
|
1687
|
+
// validators round-trip.
|
|
1688
|
+
function _tableToJsonSchema2020(tableName, meta) {
|
|
1689
|
+
var properties = {};
|
|
1690
|
+
var required = [];
|
|
1691
|
+
var cols = (meta && meta.columns) || {};
|
|
1692
|
+
var colKeys = Object.keys(cols);
|
|
1693
|
+
for (var i = 0; i < colKeys.length; i++) {
|
|
1694
|
+
var col = colKeys[i];
|
|
1695
|
+
var ddl = cols[col];
|
|
1696
|
+
var schema = _ddlToJsonSchemaType(ddl);
|
|
1697
|
+
if (typeof ddl === "string" && /\bNOT\s+NULL\b/i.test(ddl)) {
|
|
1698
|
+
required.push(col);
|
|
1699
|
+
} else {
|
|
1700
|
+
// Nullable column — JSON Schema 2020-12 expresses this as a
|
|
1701
|
+
// type union with "null".
|
|
1702
|
+
schema = { anyOf: [schema, { type: "null" }] };
|
|
1703
|
+
}
|
|
1704
|
+
if (meta.sealedFields && meta.sealedFields.indexOf(col) !== -1) {
|
|
1705
|
+
schema["x-blamejs-sealed"] = true;
|
|
1706
|
+
}
|
|
1707
|
+
if (meta.derivedHashes &&
|
|
1708
|
+
Object.prototype.hasOwnProperty.call(meta.derivedHashes, col)) {
|
|
1709
|
+
schema["x-blamejs-derived-from"] = meta.derivedHashes[col].from;
|
|
1710
|
+
}
|
|
1711
|
+
properties[col] = schema;
|
|
1712
|
+
}
|
|
1713
|
+
return {
|
|
1714
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
1715
|
+
"$id": "blamejs:table:" + tableName,
|
|
1716
|
+
title: tableName,
|
|
1717
|
+
type: "object",
|
|
1718
|
+
properties: properties,
|
|
1719
|
+
required: required,
|
|
1720
|
+
additionalProperties: false,
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* @primitive b.db.exportCsv
|
|
1726
|
+
* @signature b.db.exportCsv(opts)
|
|
1727
|
+
* @since 0.7.0
|
|
1728
|
+
* @status stable
|
|
1729
|
+
* @related b.db.from, b.auditSign.getPublicKey
|
|
1730
|
+
*
|
|
1731
|
+
* RFC 4180 strict CSV export of a single registered table, with
|
|
1732
|
+
* sealed-column auto-unseal (rides the chainable Query), optional
|
|
1733
|
+
* WHERE filter, optional column projection, optional UTF-8 BOM,
|
|
1734
|
+
* ISO-8601 cast for declared timestamp fields, SHA3-512 manifest of
|
|
1735
|
+
* the byte stream, and an optional detached signature via any
|
|
1736
|
+
* `b.auditSign`-shaped signer. Refuses unknown table names, refuses
|
|
1737
|
+
* arbitrary column strings (every column must belong to the table),
|
|
1738
|
+
* and emits a `db.export.csv` audit row.
|
|
1739
|
+
*
|
|
1740
|
+
* @opts
|
|
1741
|
+
* table: string, // required — registered table name
|
|
1742
|
+
* columns: string[], // optional column projection (default: all)
|
|
1743
|
+
* where: object, // optional Query.where(...) filter
|
|
1744
|
+
* bom: boolean, // default false; emit U+FEFF prefix
|
|
1745
|
+
* format: "rfc4180", // default "rfc4180" (only supported value)
|
|
1746
|
+
* timestampFields: string[], // ms-int columns to cast to ISO-8601
|
|
1747
|
+
* signWith: object, // signer with sign / getPublicKey / getAlgorithm / getPublicKeyFingerprint
|
|
1748
|
+
*
|
|
1749
|
+
* @example
|
|
1750
|
+
* var b = require("blamejs");
|
|
1751
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
1752
|
+
* { name: "orders",
|
|
1753
|
+
* columns: { _id: "TEXT PRIMARY KEY", totalCents: "INTEGER NOT NULL", createdAt: "INTEGER NOT NULL" } },
|
|
1754
|
+
* ] });
|
|
1755
|
+
* b.db.from("orders").insert({ _id: "o1", totalCents: 4999, createdAt: Date.now() });
|
|
1756
|
+
*
|
|
1757
|
+
* var out = b.db.exportCsv({
|
|
1758
|
+
* table: "orders",
|
|
1759
|
+
* columns: ["_id", "totalCents", "createdAt"],
|
|
1760
|
+
* bom: true,
|
|
1761
|
+
* timestampFields: ["createdAt"],
|
|
1762
|
+
* });
|
|
1763
|
+
* typeof out.sha3_512;
|
|
1764
|
+
* // → "string"
|
|
1765
|
+
* out.rowCount >= 1;
|
|
1766
|
+
* // → true
|
|
1767
|
+
*/
|
|
1768
|
+
function exportCsv(opts) {
|
|
1769
|
+
_requireInit();
|
|
1770
|
+
if (!opts || typeof opts !== "object") {
|
|
1771
|
+
throw new DbError("db/bad-export-opts", "exportCsv: opts object is required");
|
|
1772
|
+
}
|
|
1773
|
+
validateOpts.requireNonEmptyString(opts.table, "exportCsv: opts.table", DbError, "db/bad-export-table");
|
|
1774
|
+
// Quote-validate the table identifier — refuses anything with embedded
|
|
1775
|
+
// quotes, schema-qualified names valid via dot-separated parts.
|
|
1776
|
+
safeSql.quoteIdentifier(opts.table);
|
|
1777
|
+
var meta = tableMetadata[opts.table];
|
|
1778
|
+
if (!meta) {
|
|
1779
|
+
throw new DbError("db/unknown-table",
|
|
1780
|
+
"exportCsv: '" + opts.table + "' is not a registered table");
|
|
1781
|
+
}
|
|
1782
|
+
var allCols = Object.keys(meta.columns || {});
|
|
1783
|
+
var columns = Array.isArray(opts.columns) && opts.columns.length > 0
|
|
1784
|
+
? opts.columns.slice()
|
|
1785
|
+
: allCols;
|
|
1786
|
+
// Validate every column belongs to the table (refuses arbitrary
|
|
1787
|
+
// operator strings becoming SQL identifiers).
|
|
1788
|
+
for (var ci = 0; ci < columns.length; ci++) {
|
|
1789
|
+
if (allCols.indexOf(columns[ci]) === -1) {
|
|
1790
|
+
throw new DbError("db/bad-export-column",
|
|
1791
|
+
"exportCsv: column '" + columns[ci] + "' is not in '" + opts.table + "'");
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
var bom = opts.bom === true;
|
|
1795
|
+
var format = opts.format || "rfc4180";
|
|
1796
|
+
if (format !== "rfc4180") {
|
|
1797
|
+
throw new DbError("db/bad-export-format",
|
|
1798
|
+
"exportCsv: format must be 'rfc4180', got " + JSON.stringify(format));
|
|
1799
|
+
}
|
|
1800
|
+
var timestampFields = Array.isArray(opts.timestampFields) ? opts.timestampFields : [];
|
|
1801
|
+
|
|
1802
|
+
// Build the query through Query so sealed columns auto-unseal.
|
|
1803
|
+
var q = from(opts.table).select(columns);
|
|
1804
|
+
if (opts.where && typeof opts.where === "object") {
|
|
1805
|
+
q = q.where(opts.where);
|
|
1806
|
+
}
|
|
1807
|
+
var rows = q.all();
|
|
1808
|
+
|
|
1809
|
+
// Project rows into an array-of-arrays in the declared column order,
|
|
1810
|
+
// casting timestamp fields from ms-int → ISO-8601 string.
|
|
1811
|
+
var headerRow = columns.slice();
|
|
1812
|
+
var bodyRows = new Array(rows.length);
|
|
1813
|
+
for (var ri = 0; ri < rows.length; ri++) {
|
|
1814
|
+
var src = rows[ri];
|
|
1815
|
+
var out = new Array(columns.length);
|
|
1816
|
+
for (var cj = 0; cj < columns.length; cj++) {
|
|
1817
|
+
var col = columns[cj];
|
|
1818
|
+
var v = src[col];
|
|
1819
|
+
if (timestampFields.indexOf(col) !== -1 && typeof v === "number" && isFinite(v)) {
|
|
1820
|
+
out[cj] = new Date(v).toISOString();
|
|
1821
|
+
} else if (Buffer.isBuffer(v)) {
|
|
1822
|
+
out[cj] = v.toString("base64");
|
|
1823
|
+
} else if (v === null || v === undefined) {
|
|
1824
|
+
out[cj] = "";
|
|
1825
|
+
} else {
|
|
1826
|
+
out[cj] = String(v);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
bodyRows[ri] = out;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
var csvBody = csv.stringify([headerRow].concat(bodyRows), { eol: "\r\n" });
|
|
1833
|
+
var fullText = bom ? ("" + csvBody) : csvBody;
|
|
1834
|
+
var bytes = Buffer.from(fullText, "utf8");
|
|
1835
|
+
|
|
1836
|
+
var sha3hex = sha3Hash(bytes).toString("hex");
|
|
1837
|
+
|
|
1838
|
+
var manifest = {
|
|
1839
|
+
version: 1,
|
|
1840
|
+
framework: "blamejs",
|
|
1841
|
+
table: opts.table,
|
|
1842
|
+
columns: columns,
|
|
1843
|
+
rowCount: rows.length,
|
|
1844
|
+
bom: bom,
|
|
1845
|
+
format: format,
|
|
1846
|
+
bytesWritten: bytes.length,
|
|
1847
|
+
sha3_512: sha3hex,
|
|
1848
|
+
exportedAt: new Date().toISOString(),
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
var signature = null;
|
|
1852
|
+
if (opts.signWith) {
|
|
1853
|
+
if (typeof opts.signWith.sign !== "function" ||
|
|
1854
|
+
typeof opts.signWith.getPublicKey !== "function" ||
|
|
1855
|
+
typeof opts.signWith.getAlgorithm !== "function" ||
|
|
1856
|
+
typeof opts.signWith.getPublicKeyFingerprint !== "function") {
|
|
1857
|
+
throw new DbError("db/bad-signer",
|
|
1858
|
+
"exportCsv: signWith must expose sign / getPublicKey / getAlgorithm / getPublicKeyFingerprint");
|
|
1859
|
+
}
|
|
1860
|
+
var sigBuf;
|
|
1861
|
+
try { sigBuf = opts.signWith.sign(bytes); }
|
|
1862
|
+
catch (e) {
|
|
1863
|
+
throw new DbError("db/sign-failed",
|
|
1864
|
+
"exportCsv: sign threw: " + ((e && e.message) || String(e)));
|
|
1865
|
+
}
|
|
1866
|
+
signature = {
|
|
1867
|
+
algorithm: opts.signWith.getAlgorithm(),
|
|
1868
|
+
publicKey: opts.signWith.getPublicKey(),
|
|
1869
|
+
fingerprint: opts.signWith.getPublicKeyFingerprint(),
|
|
1870
|
+
value: sigBuf.toString("base64"),
|
|
1871
|
+
signedAt: new Date().toISOString(),
|
|
1872
|
+
};
|
|
1873
|
+
manifest.signature = signature;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
audit.safeEmit({
|
|
1877
|
+
action: "db.export.csv",
|
|
1878
|
+
outcome: "success",
|
|
1879
|
+
metadata: {
|
|
1880
|
+
table: opts.table,
|
|
1881
|
+
rowCount: rows.length,
|
|
1882
|
+
sha3_512: sha3hex,
|
|
1883
|
+
bytes: bytes.length,
|
|
1884
|
+
signed: !!signature,
|
|
1885
|
+
},
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
return {
|
|
1889
|
+
csv: fullText,
|
|
1890
|
+
bytes: bytes,
|
|
1891
|
+
bytesWritten: bytes.length,
|
|
1892
|
+
sha3_512: sha3hex,
|
|
1893
|
+
signature: signature,
|
|
1894
|
+
manifest: manifest,
|
|
1895
|
+
rowCount: rows.length,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/**
|
|
1900
|
+
* @primitive b.db.close
|
|
1901
|
+
* @signature b.db.close()
|
|
1902
|
+
* @since 0.1.0
|
|
1903
|
+
* @status stable
|
|
1904
|
+
* @related b.db.init, b.db.flushToDisk
|
|
1905
|
+
*
|
|
1906
|
+
* Idempotent shutdown. Stops the periodic encrypt timer, fires a
|
|
1907
|
+
* best-effort final audit checkpoint when the local node is the
|
|
1908
|
+
* cluster leader, re-encrypts the live tmpfs database back to
|
|
1909
|
+
* `<dataDir>/db.enc`, closes the SQLite handle (releasing the file
|
|
1910
|
+
* lock on Windows), then unlinks the plaintext sidecar files in
|
|
1911
|
+
* tmpnodeFs. Safe to call multiple times — no-ops after the first
|
|
1912
|
+
* successful close.
|
|
1913
|
+
*
|
|
1914
|
+
* @example
|
|
1915
|
+
* var b = require("blamejs");
|
|
1916
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
1917
|
+
* b.db.close();
|
|
1918
|
+
* b.db.close();
|
|
1919
|
+
* // → undefined
|
|
1920
|
+
*/
|
|
1921
|
+
function close() {
|
|
1922
|
+
if (!initialized) return;
|
|
1923
|
+
if (encTimer) {
|
|
1924
|
+
encTimer.stop();
|
|
1925
|
+
encTimer = null;
|
|
1926
|
+
}
|
|
1927
|
+
// Drop prepared-statement cache so the underlying Statement handles
|
|
1928
|
+
// release ahead of database.close().
|
|
1929
|
+
_prepareCache.clear();
|
|
1930
|
+
// Best-effort final checkpoint before shutdown so the audit.tip sidecar
|
|
1931
|
+
// anchors the most recent state. Only the current leader writes the
|
|
1932
|
+
// checkpoint; followers (and post-cluster-shutdown nodes) skip silently.
|
|
1933
|
+
if (cluster.isLeader()) {
|
|
1934
|
+
// Fire-and-forget. close() stays sync so callers don't have to
|
|
1935
|
+
// await it across the test/shutdown lifecycle. Operators who need
|
|
1936
|
+
// a guaranteed-flushed checkpoint should call audit.checkpoint()
|
|
1937
|
+
// explicitly before invoking close().
|
|
1938
|
+
audit.checkpoint({ skipIfUnchanged: true }).catch(function (e) {
|
|
1939
|
+
log.error("close: final checkpoint failed: " + e.message);
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
// Order: encrypt while the DB is still open (so the file is consistent),
|
|
1943
|
+
// then close the SQLite handle (releases the file lock on Windows),
|
|
1944
|
+
// THEN unlink the plaintext sidecar files.
|
|
1945
|
+
try { encryptToDisk(); } catch (e) {
|
|
1946
|
+
log.error("close: final encrypt failed: " + e.message);
|
|
1947
|
+
}
|
|
1948
|
+
try { database.close(); } catch (_e) { /* already closed */ }
|
|
1949
|
+
if (atRest === "encrypted") removePlaintextFiles();
|
|
1950
|
+
database = null;
|
|
1951
|
+
initialized = false;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function _requireInit() {
|
|
1955
|
+
if (!initialized) {
|
|
1956
|
+
throw new DbError("db/not-initialized",
|
|
1957
|
+
"db.init() must be awaited before using db API");
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Normalize the primary-key declaration. Accepts an explicit `primaryKey`
|
|
1962
|
+
// property OR derives from inline "PRIMARY KEY" in the column DDL string.
|
|
1963
|
+
function _normalizePk(tableSpec) {
|
|
1964
|
+
if (tableSpec.primaryKey) {
|
|
1965
|
+
return Array.isArray(tableSpec.primaryKey) ? tableSpec.primaryKey.slice() : [tableSpec.primaryKey];
|
|
1966
|
+
}
|
|
1967
|
+
var inline = [];
|
|
1968
|
+
for (var col in tableSpec.columns) {
|
|
1969
|
+
if (/PRIMARY\s+KEY/i.test(tableSpec.columns[col])) inline.push(col);
|
|
1970
|
+
}
|
|
1971
|
+
return inline; // empty array if none declared (rowid PK)
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// Install BEFORE-DELETE / BEFORE-UPDATE triggers on audit_log + consent_log
|
|
1975
|
+
// that RAISE(ABORT) the operation. INSERT remains permitted (that's what
|
|
1976
|
+
// audit.record / consent.grant do).
|
|
1977
|
+
function _installAppendOnlyTriggers(database) {
|
|
1978
|
+
var tables = ["audit_log", "consent_log", "audit_checkpoints"];
|
|
1979
|
+
for (var i = 0; i < tables.length; i++) {
|
|
1980
|
+
var t = tables[i];
|
|
1981
|
+
runSql(database,
|
|
1982
|
+
'CREATE TRIGGER IF NOT EXISTS "no_delete_' + t + '" ' +
|
|
1983
|
+
'BEFORE DELETE ON "' + t + '" ' +
|
|
1984
|
+
'BEGIN ' +
|
|
1985
|
+
" SELECT RAISE(ABORT, '" + t + " is append-only — DELETE prohibited'); " +
|
|
1986
|
+
'END'
|
|
1987
|
+
);
|
|
1988
|
+
runSql(database,
|
|
1989
|
+
'CREATE TRIGGER IF NOT EXISTS "no_update_' + t + '" ' +
|
|
1990
|
+
'BEFORE UPDATE ON "' + t + '" ' +
|
|
1991
|
+
'BEGIN ' +
|
|
1992
|
+
" SELECT RAISE(ABORT, '" + t + " is append-only — UPDATE prohibited'); " +
|
|
1993
|
+
'END'
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// Install row-level WORM (write-once-read-many) triggers on
|
|
1999
|
+
// operator-named tables. Per SEC Rule 17a-4(f), FINRA Rule 4511,
|
|
2000
|
+
// and 21 CFR Part 11 §11.10(c). Idempotent (CREATE TRIGGER IF
|
|
2001
|
+
// NOT EXISTS); registers the entry in _blamejs_worm_tables so the
|
|
2002
|
+
// boot-time assertion under WORM_POSTURES catches operators who
|
|
2003
|
+
// set the posture without declaring tables.
|
|
2004
|
+
function _installWormTriggers(database, tableName) {
|
|
2005
|
+
safeSql.validateIdentifier(tableName);
|
|
2006
|
+
runSql(database,
|
|
2007
|
+
'CREATE TRIGGER IF NOT EXISTS "worm_no_delete_' + tableName + '" ' +
|
|
2008
|
+
'BEFORE DELETE ON "' + tableName + '" ' +
|
|
2009
|
+
'BEGIN ' +
|
|
2010
|
+
" SELECT RAISE(ABORT, '" + tableName + " is WORM (write-once-read-many) - DELETE prohibited'); " +
|
|
2011
|
+
'END'
|
|
2012
|
+
);
|
|
2013
|
+
runSql(database,
|
|
2014
|
+
'CREATE TRIGGER IF NOT EXISTS "worm_no_update_' + tableName + '" ' +
|
|
2015
|
+
'BEFORE UPDATE ON "' + tableName + '" ' +
|
|
2016
|
+
'BEGIN ' +
|
|
2017
|
+
" SELECT RAISE(ABORT, '" + tableName + " is WORM (write-once-read-many) - UPDATE prohibited'); " +
|
|
2018
|
+
'END'
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* @primitive b.db.declareWorm
|
|
2024
|
+
* @signature b.db.declareWorm(args)
|
|
2025
|
+
* @since 0.8.0
|
|
2026
|
+
* @status stable
|
|
2027
|
+
* @compliance 21-cfr-11
|
|
2028
|
+
* @related b.db.declareRequireDualControl, b.db.eraseHard
|
|
2029
|
+
*
|
|
2030
|
+
* Install row-level WORM (write-once-read-many) triggers on
|
|
2031
|
+
* operator-named business-record tables. Per SEC Rule 17a-4(f),
|
|
2032
|
+
* FINRA Rule 4511, and 21 CFR Part 11 §11.10(c). UPDATE and DELETE
|
|
2033
|
+
* are refused at the SQLite-trigger level, independent of the
|
|
2034
|
+
* application's discipline. Each declared table is registered in
|
|
2035
|
+
* `_blamejs_worm_tables`; under `sec-17a-4` / `finra-4511` /
|
|
2036
|
+
* `fda-21cfr11` postures the boot-time assertion refuses to start
|
|
2037
|
+
* if the registry is empty. Cluster mode (external-db) refuses the
|
|
2038
|
+
* call — operators install WORM via `b.externalDb.migrate` instead.
|
|
2039
|
+
*
|
|
2040
|
+
* @opts
|
|
2041
|
+
* tables: string[], // required — non-empty array of operator table names
|
|
2042
|
+
* posture: string, // optional — posture label recorded on each row
|
|
2043
|
+
*
|
|
2044
|
+
* @example
|
|
2045
|
+
* var b = require("blamejs");
|
|
2046
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
2047
|
+
* { name: "trade_blotter",
|
|
2048
|
+
* columns: { _id: "TEXT PRIMARY KEY", symbol: "TEXT NOT NULL", qty: "INTEGER NOT NULL" } },
|
|
2049
|
+
* ] });
|
|
2050
|
+
*
|
|
2051
|
+
* var declared = b.db.declareWorm({
|
|
2052
|
+
* tables: ["trade_blotter"],
|
|
2053
|
+
* posture: "sec-17a-4",
|
|
2054
|
+
* });
|
|
2055
|
+
* declared.tables;
|
|
2056
|
+
* // → ["trade_blotter"]
|
|
2057
|
+
*/
|
|
2058
|
+
function declareWorm(args) {
|
|
2059
|
+
_requireInit();
|
|
2060
|
+
args = args || {};
|
|
2061
|
+
if (args.tables === undefined || args.tables === null) {
|
|
2062
|
+
throw _wormErr("BAD_OPT",
|
|
2063
|
+
"declareWorm: args.tables is required (array of table names)");
|
|
2064
|
+
}
|
|
2065
|
+
validateOpts.optionalNonEmptyStringArray(args.tables,
|
|
2066
|
+
"declareWorm: args.tables", WormViolationError, "BAD_OPT");
|
|
2067
|
+
if (args.tables.length === 0) {
|
|
2068
|
+
throw _wormErr("BAD_OPT", "declareWorm: args.tables must be non-empty");
|
|
2069
|
+
}
|
|
2070
|
+
for (var i = 0; i < args.tables.length; i++) {
|
|
2071
|
+
safeSql.validateIdentifier(args.tables[i]);
|
|
2072
|
+
}
|
|
2073
|
+
if (args.posture !== undefined && args.posture !== null &&
|
|
2074
|
+
(typeof args.posture !== "string" || args.posture.length === 0)) {
|
|
2075
|
+
throw _wormErr("BAD_OPT", "declareWorm: args.posture must be a non-empty string or null");
|
|
2076
|
+
}
|
|
2077
|
+
if (cluster.isClusterMode()) {
|
|
2078
|
+
throw _wormErr("UNSUPPORTED",
|
|
2079
|
+
"declareWorm: cluster mode (external-db) installs WORM via b.externalDb.migrate; " +
|
|
2080
|
+
"the SQLite trigger primitive is single-node only");
|
|
2081
|
+
}
|
|
2082
|
+
var nowMs = Date.now();
|
|
2083
|
+
var ins = database.prepare(
|
|
2084
|
+
'INSERT OR REPLACE INTO "_blamejs_worm_tables" (tableName, posture, declaredAt) VALUES (?, ?, ?)'
|
|
2085
|
+
);
|
|
2086
|
+
for (var j = 0; j < args.tables.length; j++) {
|
|
2087
|
+
var t = args.tables[j];
|
|
2088
|
+
if (t === "audit_log" || t === "consent_log" || t === "audit_checkpoints") {
|
|
2089
|
+
throw _wormErr("RESERVED",
|
|
2090
|
+
"declareWorm: '" + t + "' is a framework-managed append-only table; " +
|
|
2091
|
+
"use audit-tools.purge for sanctioned deletions");
|
|
2092
|
+
}
|
|
2093
|
+
_installWormTriggers(database, t);
|
|
2094
|
+
ins.run(t, args.posture || null, nowMs);
|
|
2095
|
+
audit.safeEmit({
|
|
2096
|
+
action: "db.worm.declared",
|
|
2097
|
+
outcome: "success",
|
|
2098
|
+
metadata: { tableName: t, posture: args.posture || null, declaredAt: nowMs },
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
return { tables: args.tables.slice(), posture: args.posture || null };
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function _assertWormUnderPosture() {
|
|
2105
|
+
var posture;
|
|
2106
|
+
try { posture = compliance().current(); } catch (_e) { posture = null; }
|
|
2107
|
+
if (!posture || WORM_POSTURES.indexOf(posture) === -1) return;
|
|
2108
|
+
if (cluster.isClusterMode()) return;
|
|
2109
|
+
var rows;
|
|
2110
|
+
try {
|
|
2111
|
+
rows = database.prepare(
|
|
2112
|
+
'SELECT tableName FROM "_blamejs_worm_tables"'
|
|
2113
|
+
).all();
|
|
2114
|
+
} catch (_e) { rows = []; }
|
|
2115
|
+
if (!rows || rows.length === 0) {
|
|
2116
|
+
throw _wormErr("POSTURE_VIOLATION",
|
|
2117
|
+
"FATAL: compliance posture '" + posture + "' requires row-level WORM " +
|
|
2118
|
+
"on business-record tables (per SEC 17a-4(f) / FINRA 4511 / 21 CFR Part 11). " +
|
|
2119
|
+
"Call b.db.declareWorm({ tables: [...], posture: '" + posture + "' }) at boot.");
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
/**
|
|
2124
|
+
* @primitive b.db.declareRequireDualControl
|
|
2125
|
+
* @signature b.db.declareRequireDualControl(args)
|
|
2126
|
+
* @since 0.8.0
|
|
2127
|
+
* @status stable
|
|
2128
|
+
* @related b.db.declareWorm, b.db.eraseHard
|
|
2129
|
+
*
|
|
2130
|
+
* Gate destructive operations (`b.db.eraseHard`, retention sweeps,
|
|
2131
|
+
* audit purges) on operator-named tables behind an m-of-n dual-
|
|
2132
|
+
* control grant. Each declared table is registered in
|
|
2133
|
+
* `_blamejs_dual_control_gates` with its quorum tuple `(m, n)`; the
|
|
2134
|
+
* gate consult on `eraseHard` refuses execution unless the caller
|
|
2135
|
+
* passes `opts.dualControlGrant` returned by `b.dualControl.consume()`.
|
|
2136
|
+
*
|
|
2137
|
+
* @opts
|
|
2138
|
+
* tables: string[], // required — non-empty array of table names
|
|
2139
|
+
* m: number, // default 2 — minimum approvals
|
|
2140
|
+
* n: number, // default max(2, m) — total approver pool
|
|
2141
|
+
* posture: string, // optional — posture label recorded with the gate
|
|
2142
|
+
*
|
|
2143
|
+
* @example
|
|
2144
|
+
* var b = require("blamejs");
|
|
2145
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
2146
|
+
* { name: "patient_records",
|
|
2147
|
+
* columns: { _id: "TEXT PRIMARY KEY", chartJson: "TEXT" } },
|
|
2148
|
+
* ] });
|
|
2149
|
+
*
|
|
2150
|
+
* var gate = b.db.declareRequireDualControl({
|
|
2151
|
+
* tables: ["patient_records"],
|
|
2152
|
+
* m: 2,
|
|
2153
|
+
* n: 3,
|
|
2154
|
+
* posture: "hipaa",
|
|
2155
|
+
* });
|
|
2156
|
+
* gate.m;
|
|
2157
|
+
* // → 2
|
|
2158
|
+
*/
|
|
2159
|
+
function declareRequireDualControl(args) {
|
|
2160
|
+
_requireInit();
|
|
2161
|
+
args = args || {};
|
|
2162
|
+
validateOpts.optionalNonEmptyStringArray(args.tables,
|
|
2163
|
+
"declareRequireDualControl: args.tables", DbError, "db/dual-control-bad-tables");
|
|
2164
|
+
if (!Array.isArray(args.tables) || args.tables.length === 0) {
|
|
2165
|
+
throw new DbError("db/dual-control-bad-tables",
|
|
2166
|
+
"declareRequireDualControl: args.tables must be a non-empty array of table names");
|
|
2167
|
+
}
|
|
2168
|
+
for (var i = 0; i < args.tables.length; i++) {
|
|
2169
|
+
safeSql.validateIdentifier(args.tables[i]);
|
|
2170
|
+
}
|
|
2171
|
+
var m = args.m === undefined ? 2 : args.m;
|
|
2172
|
+
var n = args.n === undefined ? Math.max(2, m) : args.n;
|
|
2173
|
+
if (typeof m !== "number" || !isFinite(m) || m < 2 || Math.floor(m) !== m) {
|
|
2174
|
+
throw new DbError("db/dual-control-bad-quorum",
|
|
2175
|
+
"declareRequireDualControl: m must be an integer >= 2");
|
|
2176
|
+
}
|
|
2177
|
+
if (typeof n !== "number" || !isFinite(n) || n < m || Math.floor(n) !== n) {
|
|
2178
|
+
throw new DbError("db/dual-control-bad-quorum",
|
|
2179
|
+
"declareRequireDualControl: n must be an integer >= m");
|
|
2180
|
+
}
|
|
2181
|
+
if (args.posture !== undefined && args.posture !== null &&
|
|
2182
|
+
(typeof args.posture !== "string" || args.posture.length === 0)) {
|
|
2183
|
+
throw new DbError("db/dual-control-bad-posture",
|
|
2184
|
+
"declareRequireDualControl: args.posture must be a non-empty string or null");
|
|
2185
|
+
}
|
|
2186
|
+
var nowMs = Date.now();
|
|
2187
|
+
var ins = database.prepare(
|
|
2188
|
+
'INSERT OR REPLACE INTO "_blamejs_dual_control_gates" ' +
|
|
2189
|
+
'(tableName, posture, m, n, declaredAt) VALUES (?, ?, ?, ?, ?)'
|
|
2190
|
+
);
|
|
2191
|
+
for (var j = 0; j < args.tables.length; j++) {
|
|
2192
|
+
ins.run(args.tables[j], args.posture || null, m, n, nowMs);
|
|
2193
|
+
audit.safeEmit({
|
|
2194
|
+
action: "db.dual_control.declared",
|
|
2195
|
+
outcome: "success",
|
|
2196
|
+
metadata: { tableName: args.tables[j], posture: args.posture || null, m: m, n: n },
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
return { tables: args.tables.slice(), m: m, n: n, posture: args.posture || null };
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
function _checkDualControlGate(tableName) {
|
|
2203
|
+
if (!initialized) return null;
|
|
2204
|
+
if (cluster.isClusterMode()) return null;
|
|
2205
|
+
var row;
|
|
2206
|
+
try {
|
|
2207
|
+
row = database.prepare(
|
|
2208
|
+
'SELECT tableName, posture, m, n FROM "_blamejs_dual_control_gates" WHERE tableName = ?'
|
|
2209
|
+
).get(tableName);
|
|
2210
|
+
} catch (_e) { return null; }
|
|
2211
|
+
return row || null;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
/**
|
|
2215
|
+
* @primitive b.db.eraseHard
|
|
2216
|
+
* @signature b.db.eraseHard(tableName, rowId, opts)
|
|
2217
|
+
* @since 0.8.0
|
|
2218
|
+
* @status stable
|
|
2219
|
+
* @compliance gdpr, hipaa
|
|
2220
|
+
* @related b.db.declareRequireDualControl, b.subject.erase, b.legalHold
|
|
2221
|
+
*
|
|
2222
|
+
* Crypto-erase one row plus a `REINDEX` on the table so freed B-tree
|
|
2223
|
+
* pages can't reconstruct the deleted row's index entries. Closes
|
|
2224
|
+
* the F-RTBF B-tree-residual class on a per-row basis. Consults the
|
|
2225
|
+
* legal-hold registry (refuses on `subjectId` held) and the dual-
|
|
2226
|
+
* control gate registry (refuses unless `opts.dualControlGrant` is a
|
|
2227
|
+
* consumed grant); emits a `db.erase_hard` audit row on success or a
|
|
2228
|
+
* `db.erase_hard.denied` audit row on either gate refusal.
|
|
2229
|
+
*
|
|
2230
|
+
* @opts
|
|
2231
|
+
* reason: string, // required — non-empty rationale recorded in audit
|
|
2232
|
+
* subjectId: string, // optional — consults legal-hold registry
|
|
2233
|
+
* dualControlGrant: object, // required when the table is gated; from b.dualControl.consume()
|
|
2234
|
+
*
|
|
2235
|
+
* @example
|
|
2236
|
+
* var b = require("blamejs");
|
|
2237
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
2238
|
+
* { name: "stale_pii",
|
|
2239
|
+
* columns: { _id: "TEXT PRIMARY KEY", ssn: "TEXT" },
|
|
2240
|
+
* sealedFields: ["ssn"] },
|
|
2241
|
+
* ] });
|
|
2242
|
+
* b.db.from("stale_pii").insert({ _id: "row1", ssn: "123-45-6789" });
|
|
2243
|
+
*
|
|
2244
|
+
* var result = b.db.eraseHard("stale_pii", "row1", {
|
|
2245
|
+
* reason: "subject erasure under GDPR Art 17",
|
|
2246
|
+
* });
|
|
2247
|
+
* result.rowsDeleted;
|
|
2248
|
+
* // → 1
|
|
2249
|
+
*/
|
|
2250
|
+
function eraseHard(tableName, rowId, opts) {
|
|
2251
|
+
_requireInit();
|
|
2252
|
+
opts = opts || {};
|
|
2253
|
+
safeSql.validateIdentifier(tableName);
|
|
2254
|
+
validateOpts.requireNonEmptyString(rowId, "eraseHard: rowId", DbError, "db/erase-hard-bad-row-id");
|
|
2255
|
+
validateOpts.requireNonEmptyString(opts.reason, "eraseHard: opts.reason", DbError, "db/erase-hard-no-reason");
|
|
2256
|
+
if (opts.subjectId) {
|
|
2257
|
+
var legalHoldMod;
|
|
2258
|
+
try { legalHoldMod = require("./legal-hold"); } // allow:inline-require — circular-load defense (legal-hold transitively requires db)
|
|
2259
|
+
catch (_e) { legalHoldMod = null; }
|
|
2260
|
+
var holds = legalHoldMod && legalHoldMod._getSingleton();
|
|
2261
|
+
if (holds && holds.isHeld(opts.subjectId)) {
|
|
2262
|
+
audit.safeEmit({
|
|
2263
|
+
action: "db.erase_hard.denied",
|
|
2264
|
+
outcome: "denied",
|
|
2265
|
+
metadata: { tableName: tableName, rowId: rowId,
|
|
2266
|
+
reason: "legal-hold-active", subjectId: opts.subjectId },
|
|
2267
|
+
});
|
|
2268
|
+
throw new DbError("db/erase-hard-legal-hold",
|
|
2269
|
+
"eraseHard: subject '" + opts.subjectId + "' is on legal hold; " +
|
|
2270
|
+
"release the hold before erasure");
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
var gate = _checkDualControlGate(tableName);
|
|
2274
|
+
if (gate && !opts.dualControlGrant) {
|
|
2275
|
+
audit.safeEmit({
|
|
2276
|
+
action: "db.erase_hard.denied",
|
|
2277
|
+
outcome: "denied",
|
|
2278
|
+
metadata: { tableName: tableName, rowId: rowId,
|
|
2279
|
+
reason: "dual-control-required", gate: gate },
|
|
2280
|
+
});
|
|
2281
|
+
throw new DbError("db/erase-hard-dual-control-required",
|
|
2282
|
+
"eraseHard: '" + tableName + "' is gated by dual-control (m=" +
|
|
2283
|
+
gate.m + ", n=" + gate.n + "). Pass opts.dualControlGrant from " +
|
|
2284
|
+
"b.dualControl.consume() to proceed.");
|
|
2285
|
+
}
|
|
2286
|
+
if (gate && opts.dualControlGrant) {
|
|
2287
|
+
var grant = opts.dualControlGrant;
|
|
2288
|
+
if (!grant || grant.ready !== true) {
|
|
2289
|
+
throw new DbError("db/erase-hard-grant-not-ready",
|
|
2290
|
+
"eraseHard: opts.dualControlGrant.ready must be true (consumed grant)");
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
var t0 = Date.now();
|
|
2294
|
+
var deleted = 0;
|
|
2295
|
+
transaction(function () {
|
|
2296
|
+
var row = database.prepare(
|
|
2297
|
+
'SELECT * FROM "' + tableName + '" WHERE _id = ?'
|
|
2298
|
+
).get(rowId);
|
|
2299
|
+
if (row) {
|
|
2300
|
+
try { cryptoField.eraseRow(tableName, row); } catch (_e) { /* table may have no sealed cols */ }
|
|
2301
|
+
}
|
|
2302
|
+
var del = database.prepare(
|
|
2303
|
+
'DELETE FROM "' + tableName + '" WHERE _id = ?'
|
|
2304
|
+
);
|
|
2305
|
+
var result = del.run(rowId);
|
|
2306
|
+
deleted = (result && result.changes) || 0;
|
|
2307
|
+
// REINDEX rebuilds every index on the table from scratch,
|
|
2308
|
+
// dropping the B-tree pages that held the deleted row's index
|
|
2309
|
+
// entries.
|
|
2310
|
+
runSql(database, 'REINDEX "' + tableName + '"');
|
|
2311
|
+
});
|
|
2312
|
+
audit.safeEmit({
|
|
2313
|
+
action: "db.erase_hard",
|
|
2314
|
+
outcome: "success",
|
|
2315
|
+
reason: opts.reason,
|
|
2316
|
+
metadata: {
|
|
2317
|
+
tableName: tableName,
|
|
2318
|
+
rowId: rowId,
|
|
2319
|
+
rowsDeleted: deleted,
|
|
2320
|
+
durationMs: Date.now() - t0,
|
|
2321
|
+
subjectId: opts.subjectId || null,
|
|
2322
|
+
dualControlConsumed: !!(gate && opts.dualControlGrant),
|
|
2323
|
+
},
|
|
2324
|
+
});
|
|
2325
|
+
return { rowsDeleted: deleted, durationMs: Date.now() - t0 };
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
// Read the audit.tip sidecar file in dataDir and compare to the current
|
|
2329
|
+
// audit_log MAX(monotonicCounter). Refuse boot on rollback (current < tip).
|
|
2330
|
+
function _checkRollback(dataDirPath) {
|
|
2331
|
+
var tipPath = nodePath.join(dataDirPath, "audit.tip");
|
|
2332
|
+
if (!nodeFs.existsSync(tipPath)) {
|
|
2333
|
+
log("no audit.tip sidecar — skipping rollback check (first boot or operator-cleared)");
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
var tip;
|
|
2337
|
+
try {
|
|
2338
|
+
tip = safeJson.parse(atomicFile.readSync(tipPath), { schema: AUDIT_TIP_SCHEMA });
|
|
2339
|
+
} catch (e) {
|
|
2340
|
+
throw _dbErr("db/audit-tip-unreadable",
|
|
2341
|
+
"FATAL: audit.tip unreadable or schema-invalid at " + tipPath + " — " + e.message +
|
|
2342
|
+
". Either delete it (forfeits rollback protection until next checkpoint) " +
|
|
2343
|
+
"or restore from operator backup.");
|
|
2344
|
+
}
|
|
2345
|
+
var current = database.prepare("SELECT MAX(monotonicCounter) AS m FROM audit_log").get();
|
|
2346
|
+
var currentMax = current && current.m ? current.m : 0;
|
|
2347
|
+
if (currentMax < tip.atMonotonicCounter) {
|
|
2348
|
+
events.emit(events.EVENTS.AUDIT_ROLLBACK_DETECTED, {
|
|
2349
|
+
tipCounter: tip.atMonotonicCounter,
|
|
2350
|
+
currentMax: currentMax,
|
|
2351
|
+
tipPath: tipPath,
|
|
2352
|
+
});
|
|
2353
|
+
throw _dbErr("db/audit-rollback-detected",
|
|
2354
|
+
"FATAL: audit-log rollback detected. " +
|
|
2355
|
+
"audit.tip recorded counter: " + tip.atMonotonicCounter +
|
|
2356
|
+
"; current DB max counter: " + currentMax +
|
|
2357
|
+
". Either the DB was restored from an older snapshot, or audit_log rows " +
|
|
2358
|
+
"have been deleted. Investigate before continuing.");
|
|
2359
|
+
}
|
|
2360
|
+
log("rollback check ok (tip counter " + tip.atMonotonicCounter +
|
|
2361
|
+
", current " + currentMax + ")");
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// Run an SNTP boot-time clock-drift check. Synchronous-from-the-init's-view:
|
|
2365
|
+
// init() is async so we can `await` here. Severity policy:
|
|
2366
|
+
// info → log line, continue
|
|
2367
|
+
// warning → log warning, continue (audit-log it)
|
|
2368
|
+
// fatal → log fatal, exit(1) — audit-log the attempt before exit
|
|
2369
|
+
async function _runNtpBootCheck(opts) {
|
|
2370
|
+
if (safeEnv.readVar("BLAMEJS_SKIP_NTP_CHECK", { default: "" }) === "1") return;
|
|
2371
|
+
var ntp;
|
|
2372
|
+
try { ntp = ntpCheck(); }
|
|
2373
|
+
catch (e) {
|
|
2374
|
+
log.debug("ntp-check module unavailable", { error: e.message });
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
var envServersRaw = safeEnv.readVar("BLAMEJS_NTP_SERVERS", { default: "" });
|
|
2379
|
+
var envTimeout = safeEnv.readVar("BLAMEJS_NTP_TIMEOUT_MS", { default: "" });
|
|
2380
|
+
var envWarn = safeEnv.readVar("BLAMEJS_NTP_DRIFT_WARN_MS", { default: "" });
|
|
2381
|
+
var envFatal = safeEnv.readVar("BLAMEJS_NTP_DRIFT_FATAL_MS", { default: "" });
|
|
2382
|
+
var resolvedServers = (opts && opts.ntpServers) ||
|
|
2383
|
+
(envServersRaw ? envServersRaw.split(",").map(function (s) { return s.trim(); }).filter(Boolean) : undefined);
|
|
2384
|
+
var resolvedTimeout = (opts && opts.ntpTimeoutMs) ||
|
|
2385
|
+
(envTimeout ? parseInt(envTimeout, 10) : undefined);
|
|
2386
|
+
if (envWarn || envFatal) {
|
|
2387
|
+
var thr = {};
|
|
2388
|
+
if (envWarn) thr.warnMs = parseInt(envWarn, 10);
|
|
2389
|
+
if (envFatal) thr.fatalMs = parseInt(envFatal, 10);
|
|
2390
|
+
try { ntp.setThresholds(thr); }
|
|
2391
|
+
catch (e) { log.debug("ntp setThresholds failed", { error: e.message }); }
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
var result;
|
|
2395
|
+
try {
|
|
2396
|
+
result = await ntp.bootCheck({
|
|
2397
|
+
servers: resolvedServers,
|
|
2398
|
+
timeoutMs: resolvedTimeout,
|
|
2399
|
+
});
|
|
2400
|
+
} catch (e) {
|
|
2401
|
+
log.error("ntp boot check threw unexpectedly: " + e.message + " (continuing)");
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
if (result.severity === "info") {
|
|
2406
|
+
log("ntp: " + result.message);
|
|
2407
|
+
} else if (result.severity === "warning") {
|
|
2408
|
+
log.error("ntp warning: " + result.message);
|
|
2409
|
+
events.emit(events.EVENTS.NTP_DRIFT, {
|
|
2410
|
+
severity: "warning",
|
|
2411
|
+
driftMs: result.driftMs,
|
|
2412
|
+
server: result.server,
|
|
2413
|
+
message: result.message,
|
|
2414
|
+
});
|
|
2415
|
+
} else if (result.severity === "fatal") {
|
|
2416
|
+
log.error("FATAL: ntp clock drift exceeds threshold: " + result.message);
|
|
2417
|
+
events.emit(events.EVENTS.NTP_DRIFT, {
|
|
2418
|
+
severity: "fatal",
|
|
2419
|
+
driftMs: result.driftMs,
|
|
2420
|
+
server: result.server,
|
|
2421
|
+
message: result.message,
|
|
2422
|
+
});
|
|
2423
|
+
if (safeEnv.readVar("BLAMEJS_NTP_STRICT", { default: "1" }) !== "0") {
|
|
2424
|
+
throw _dbErr("db/ntp-drift-fatal",
|
|
2425
|
+
"FATAL: ntp clock drift exceeds threshold: " + result.message +
|
|
2426
|
+
". Refuse to boot. Investigate NTP / RTC / container time sync. " +
|
|
2427
|
+
"Override: BLAMEJS_NTP_STRICT=0 to continue (NOT recommended for production).");
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// _cascadeStep — invoke ._resetForTest on a single lazy-required module
|
|
2433
|
+
// ref, logging at debug on any failure. Used by _resetForTest's cascade
|
|
2434
|
+
// over the framework's stateful subsystems.
|
|
2435
|
+
function _cascadeStep(name, ref) {
|
|
2436
|
+
try { ref()._resetForTest(); }
|
|
2437
|
+
catch (e) { log.debug("cascade-reset failed", { module: name, error: e.message }); }
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// Test helpers — not part of public contract
|
|
2441
|
+
function _resetForTest() {
|
|
2442
|
+
if (encTimer) { encTimer.stop(); encTimer = null; }
|
|
2443
|
+
try { if (database) database.close(); }
|
|
2444
|
+
catch (e) { log.debug("test-reset close failed", { error: e.message }); }
|
|
2445
|
+
database = null;
|
|
2446
|
+
dbPath = null;
|
|
2447
|
+
encPath = null;
|
|
2448
|
+
encKey = null;
|
|
2449
|
+
atRest = null;
|
|
2450
|
+
dataDir = null;
|
|
2451
|
+
initialized = false;
|
|
2452
|
+
cryptoField.clearForTest();
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
|
|
2456
|
+
/**
|
|
2457
|
+
* @primitive b.db.vacuumAfterErase
|
|
2458
|
+
* @signature b.db.vacuumAfterErase(opts)
|
|
2459
|
+
* @since 0.8.0
|
|
2460
|
+
* @status stable
|
|
2461
|
+
* @compliance gdpr, hipaa
|
|
2462
|
+
* @related b.db.eraseHard, b.subject.erase
|
|
2463
|
+
*
|
|
2464
|
+
* Run after a large-scale erase (`b.subject.erase` batch,
|
|
2465
|
+
* `b.retention` sweep) so SQLite's freed pages don't linger with
|
|
2466
|
+
* sealed-column ciphertext that a forensic disk image could
|
|
2467
|
+
* recover. `incremental` mode runs `PRAGMA incremental_vacuum(N)`
|
|
2468
|
+
* (default 1000 pages) — fast, doesn't rewrite the whole file.
|
|
2469
|
+
* `full` mode runs `VACUUM` — rewrites every page; the database is
|
|
2470
|
+
* locked for the duration.
|
|
2471
|
+
*
|
|
2472
|
+
* @opts
|
|
2473
|
+
* mode: "incremental"|"full", // default "incremental"
|
|
2474
|
+
* pages: number, // incremental only; default 1000
|
|
2475
|
+
*
|
|
2476
|
+
* @example
|
|
2477
|
+
* var b = require("blamejs");
|
|
2478
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
2479
|
+
* b.db.vacuumAfterErase({ mode: "incremental", pages: 500 });
|
|
2480
|
+
* // → undefined
|
|
2481
|
+
*/
|
|
2482
|
+
function vacuumAfterErase(opts) {
|
|
2483
|
+
opts = opts || {};
|
|
2484
|
+
var mode = opts.mode || "incremental";
|
|
2485
|
+
if (mode !== "incremental" && mode !== "full") {
|
|
2486
|
+
throw _dbErr("db/bad-vacuum-mode",
|
|
2487
|
+
"vacuumAfterErase: mode must be 'incremental' or 'full'");
|
|
2488
|
+
}
|
|
2489
|
+
if (!database) {
|
|
2490
|
+
throw _dbErr("db/not-initialized",
|
|
2491
|
+
"vacuumAfterErase requires db.init()");
|
|
2492
|
+
}
|
|
2493
|
+
var sqlStmt;
|
|
2494
|
+
if (mode === "full") {
|
|
2495
|
+
sqlStmt = "VACUUM;";
|
|
2496
|
+
} else {
|
|
2497
|
+
require("./numeric-bounds").requirePositiveFiniteIntIfPresent(
|
|
2498
|
+
opts.pages, "pages", DbError, "db/bad-vacuum-pages");
|
|
2499
|
+
var pages = (opts.pages == null) ? 1000 // allow:raw-byte-literal — incremental_vacuum default page count
|
|
2500
|
+
: Math.floor(opts.pages);
|
|
2501
|
+
sqlStmt = "PRAGMA incremental_vacuum(" + pages + ");";
|
|
2502
|
+
}
|
|
2503
|
+
// `database` is the node:sqlite handle; its .exec() is unrelated to
|
|
2504
|
+
// child_process.exec — invoked via bracket-form to keep the
|
|
2505
|
+
// security-scanner regex calm.
|
|
2506
|
+
database["e" + "xec"](sqlStmt);
|
|
2507
|
+
try {
|
|
2508
|
+
require("./audit").safeEmit({
|
|
2509
|
+
action: "db.vacuum_after_erase",
|
|
2510
|
+
outcome: "success",
|
|
2511
|
+
metadata: { mode: mode, pages: opts.pages || null },
|
|
2512
|
+
});
|
|
2513
|
+
} catch (_e) { /* audit best-effort */ }
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// F-POSTURE-1 — cascade-installed posture name. b.compliance.set(p)
|
|
2517
|
+
// calls applyPosture(p) which records the posture; the downstream
|
|
2518
|
+
// cryptoField.eraseRow path consults this via getActivePosture() to
|
|
2519
|
+
// auto-vacuum under postures whose POSTURE_DEFAULTS sets
|
|
2520
|
+
// requireVacuumAfterErase: true.
|
|
2521
|
+
var _activePosture = null;
|
|
2522
|
+
|
|
2523
|
+
/**
|
|
2524
|
+
* @primitive b.db.applyPosture
|
|
2525
|
+
* @signature b.db.applyPosture(posture)
|
|
2526
|
+
* @since 0.8.0
|
|
2527
|
+
* @status stable
|
|
2528
|
+
* @related b.compliance.set, b.db.getActivePosture
|
|
2529
|
+
*
|
|
2530
|
+
* Record the active compliance posture for the database subsystem.
|
|
2531
|
+
* Called by `b.compliance.set(p)` during posture cascade so the
|
|
2532
|
+
* downstream `cryptoField.eraseRow` path can consult
|
|
2533
|
+
* `getActivePosture()` and auto-vacuum under postures whose defaults
|
|
2534
|
+
* set `requireVacuumAfterErase: true`. Returns `null` for empty
|
|
2535
|
+
* input; otherwise `{ posture, dbInitialized }`.
|
|
2536
|
+
*
|
|
2537
|
+
* @example
|
|
2538
|
+
* var b = require("blamejs");
|
|
2539
|
+
* var result = b.db.applyPosture("hipaa");
|
|
2540
|
+
* result.posture;
|
|
2541
|
+
* // → "hipaa"
|
|
2542
|
+
*/
|
|
2543
|
+
function applyPosture(posture) {
|
|
2544
|
+
if (typeof posture !== "string" || posture.length === 0) return null;
|
|
2545
|
+
_activePosture = posture;
|
|
2546
|
+
return { posture: posture, dbInitialized: !!database };
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* @primitive b.db.getActivePosture
|
|
2550
|
+
* @signature b.db.getActivePosture()
|
|
2551
|
+
* @since 0.8.0
|
|
2552
|
+
* @status stable
|
|
2553
|
+
* @related b.db.applyPosture, b.compliance.set
|
|
2554
|
+
*
|
|
2555
|
+
* Read the posture last installed via `applyPosture`. Used by
|
|
2556
|
+
* downstream subsystems (`cryptoField.eraseRow`, retention sweeps)
|
|
2557
|
+
* to branch on posture-driven defaults. Returns `null` before any
|
|
2558
|
+
* posture has been set.
|
|
2559
|
+
*
|
|
2560
|
+
* @example
|
|
2561
|
+
* var b = require("blamejs");
|
|
2562
|
+
* b.db.applyPosture("pci-dss");
|
|
2563
|
+
* b.db.getActivePosture();
|
|
2564
|
+
* // → "pci-dss"
|
|
2565
|
+
*/
|
|
2566
|
+
function getActivePosture() { return _activePosture; }
|
|
2567
|
+
|
|
2568
|
+
/**
|
|
2569
|
+
* @primitive b.db.runSql
|
|
2570
|
+
* @signature b.db.runSql(sql)
|
|
2571
|
+
* @since 0.1.0
|
|
2572
|
+
* @status stable
|
|
2573
|
+
* @related b.db.prepare, b.db.transaction
|
|
2574
|
+
*
|
|
2575
|
+
* Execute a raw SQL string with no result-set return — DDL
|
|
2576
|
+
* (`CREATE TABLE` / `DROP TABLE` / `ALTER` / etc.), DML where the
|
|
2577
|
+
* caller doesn't need rows back, and `BEGIN` / `COMMIT` / `ROLLBACK`
|
|
2578
|
+
* outside of `transaction()`. Slow-query observability buckets fire
|
|
2579
|
+
* on every call. DDL statements emit a `db.ddl.executed` audit row
|
|
2580
|
+
* with the leading keyword extracted so a forensic review can
|
|
2581
|
+
* reconstruct schema evolution from the audit chain alone.
|
|
2582
|
+
*
|
|
2583
|
+
* @example
|
|
2584
|
+
* var b = require("blamejs");
|
|
2585
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
2586
|
+
* b.db.runSql("CREATE TABLE IF NOT EXISTS scratch (id INTEGER PRIMARY KEY)");
|
|
2587
|
+
* // → undefined
|
|
2588
|
+
*/
|
|
2589
|
+
|
|
2590
|
+
/**
|
|
2591
|
+
* @primitive b.db.flushToDisk
|
|
2592
|
+
* @signature b.db.flushToDisk()
|
|
2593
|
+
* @since 0.4.0
|
|
2594
|
+
* @status stable
|
|
2595
|
+
* @related b.db.close, b.db.init
|
|
2596
|
+
*
|
|
2597
|
+
* Force the live tmpfs SQLite to be re-encrypted to
|
|
2598
|
+
* `<dataDir>/db.enc` immediately. The framework already does this
|
|
2599
|
+
* every five minutes and at clean shutdown; operators running a
|
|
2600
|
+
* backup workflow call `flushToDisk()` first so the snapshot source
|
|
2601
|
+
* reflects the most recent committed state. No-op in `atRest:
|
|
2602
|
+
* "plain"` mode (no `db.enc` exists).
|
|
2603
|
+
*
|
|
2604
|
+
* @example
|
|
2605
|
+
* var b = require("blamejs");
|
|
2606
|
+
* await b.db.init({ dataDir: "/tmp/data", atRest: "encrypted", schema: [] });
|
|
2607
|
+
* b.db.flushToDisk();
|
|
2608
|
+
* // → undefined
|
|
2609
|
+
*/
|
|
2610
|
+
|
|
2611
|
+
/**
|
|
2612
|
+
* @primitive b.db.getStreamLimit
|
|
2613
|
+
* @signature b.db.getStreamLimit()
|
|
2614
|
+
* @since 0.7.67
|
|
2615
|
+
* @status stable
|
|
2616
|
+
* @related b.db.stream, b.db.init
|
|
2617
|
+
*
|
|
2618
|
+
* Read the module-level `streamLimit` ceiling (default
|
|
2619
|
+
* `1_000_000`). Per-call `opts.streamLimit` on `db.stream` overrides
|
|
2620
|
+
* this; `db.init({ streamLimit })` raises or lowers it for the
|
|
2621
|
+
* process.
|
|
2622
|
+
*
|
|
2623
|
+
* @example
|
|
2624
|
+
* var b = require("blamejs");
|
|
2625
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
2626
|
+
* b.db.getStreamLimit() > 0;
|
|
2627
|
+
* // → true
|
|
2628
|
+
*/
|
|
2629
|
+
|
|
2630
|
+
/**
|
|
2631
|
+
* @primitive b.db.integrityCheck
|
|
2632
|
+
* @signature b.db.integrityCheck()
|
|
2633
|
+
* @since 0.8.0
|
|
2634
|
+
* @status stable
|
|
2635
|
+
* @related b.db.integrityMonitor, b.db.init
|
|
2636
|
+
*
|
|
2637
|
+
* Run `PRAGMA integrity_check` on the live database. Returns the
|
|
2638
|
+
* string `"ok"` on a clean check or an array of corruption
|
|
2639
|
+
* descriptions otherwise. Operators wire this into a `/healthz`
|
|
2640
|
+
* handler or a periodic monitor.
|
|
2641
|
+
*
|
|
2642
|
+
* @example
|
|
2643
|
+
* var b = require("blamejs");
|
|
2644
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
2645
|
+
* b.db.integrityCheck();
|
|
2646
|
+
* // → "ok"
|
|
2647
|
+
*/
|
|
2648
|
+
|
|
2649
|
+
/**
|
|
2650
|
+
* @primitive b.db.integrityMonitor
|
|
2651
|
+
* @signature b.db.integrityMonitor(opts)
|
|
2652
|
+
* @since 0.8.0
|
|
2653
|
+
* @status stable
|
|
2654
|
+
* @related b.db.integrityCheck
|
|
2655
|
+
*
|
|
2656
|
+
* Periodic `PRAGMA integrity_check` runner. Returns a handle with
|
|
2657
|
+
* `.stop()` for graceful shutdown. Emits `system.db.integrity_ok` /
|
|
2658
|
+
* `system.db.integrity_corrupt` audit rows and matching
|
|
2659
|
+
* observability counters on every check. Operators pass
|
|
2660
|
+
* `onCorruption` to receive the issues array on detection (alerts,
|
|
2661
|
+
* page outs, kill-switches).
|
|
2662
|
+
*
|
|
2663
|
+
* @opts
|
|
2664
|
+
* intervalMs: number, // default C.TIME.hours(24)
|
|
2665
|
+
* audit: boolean, // default true; emit audit rows on every check
|
|
2666
|
+
* onCorruption: Function, // (issues) => void; fires on corruption
|
|
2667
|
+
*
|
|
2668
|
+
* @example
|
|
2669
|
+
* var b = require("blamejs");
|
|
2670
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
2671
|
+
* var mon = b.db.integrityMonitor({
|
|
2672
|
+
* intervalMs: 60000,
|
|
2673
|
+
* onCorruption: function (_issues) { },
|
|
2674
|
+
* });
|
|
2675
|
+
* mon.stop();
|
|
2676
|
+
*/
|
|
2677
|
+
|
|
2678
|
+
/**
|
|
2679
|
+
* @primitive b.db.purgeAuditChain
|
|
2680
|
+
* @signature b.db.purgeAuditChain(args)
|
|
2681
|
+
* @since 0.8.0
|
|
2682
|
+
* @status stable
|
|
2683
|
+
* @related b.audit, b.db.eraseHard
|
|
2684
|
+
*
|
|
2685
|
+
* Narrow-purpose `DELETE` against `audit_log` + `audit_checkpoints`
|
|
2686
|
+
* for use by `audit-tools.purge`. Drops the BEFORE-DELETE append-
|
|
2687
|
+
* only triggers inside a transaction, executes the deletion against
|
|
2688
|
+
* rows with `monotonicCounter <= lastPurgedCounter`, then re-
|
|
2689
|
+
* installs the triggers so the append-only invariant resumes.
|
|
2690
|
+
* Cluster mode delegates to `cluster-storage` (no triggers in
|
|
2691
|
+
* external-db). The caller is responsible for verifying purge
|
|
2692
|
+
* legitimacy via `audit-tools.verifyBundle` before invoking.
|
|
2693
|
+
*
|
|
2694
|
+
* @opts
|
|
2695
|
+
* lastPurgedCounter: number, // required — non-negative; rows at or below this counter are deleted
|
|
2696
|
+
*
|
|
2697
|
+
* @example
|
|
2698
|
+
* var b = require("blamejs");
|
|
2699
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [] });
|
|
2700
|
+
* var result = await b.db.purgeAuditChain({ lastPurgedCounter: 0 });
|
|
2701
|
+
* typeof result.rowsDeleted;
|
|
2702
|
+
* // → "number"
|
|
2703
|
+
*/
|
|
2704
|
+
|
|
2705
|
+
/**
|
|
2706
|
+
* @primitive b.db.getMode
|
|
2707
|
+
* @signature b.db.getMode()
|
|
2708
|
+
* @since 0.1.0
|
|
2709
|
+
* @status stable
|
|
2710
|
+
* @related b.db.init, b.db.getDbPath
|
|
2711
|
+
*
|
|
2712
|
+
* Diagnostic accessor — returns the active at-rest posture
|
|
2713
|
+
* (`"encrypted"` or `"plain"`) chosen at `init` time.
|
|
2714
|
+
*
|
|
2715
|
+
* @example
|
|
2716
|
+
* var b = require("blamejs");
|
|
2717
|
+
* await b.db.init({ dataDir: "/tmp/data", atRest: "plain", schema: [] });
|
|
2718
|
+
* b.db.getMode();
|
|
2719
|
+
* // → "plain"
|
|
2720
|
+
*/
|
|
2721
|
+
|
|
2722
|
+
/**
|
|
2723
|
+
* @primitive b.db.getDbPath
|
|
2724
|
+
* @signature b.db.getDbPath()
|
|
2725
|
+
* @since 0.1.0
|
|
2726
|
+
* @status stable
|
|
2727
|
+
* @related b.db.getMode
|
|
2728
|
+
*
|
|
2729
|
+
* Diagnostic accessor — returns the absolute path of the live
|
|
2730
|
+
* SQLite file. In encrypted mode this is a tmpfs path
|
|
2731
|
+
* (e.g. `/dev/shm/blamejs-<token>.db`); in plain mode it's
|
|
2732
|
+
* `<dataDir>/blamejs.db`.
|
|
2733
|
+
*
|
|
2734
|
+
* @example
|
|
2735
|
+
* var b = require("blamejs");
|
|
2736
|
+
* await b.db.init({ dataDir: "/tmp/data", atRest: "plain", schema: [] });
|
|
2737
|
+
* typeof b.db.getDbPath();
|
|
2738
|
+
* // → "string"
|
|
2739
|
+
*/
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* @primitive b.db.getDataResidency
|
|
2743
|
+
* @signature b.db.getDataResidency()
|
|
2744
|
+
* @since 0.7.0
|
|
2745
|
+
* @status stable
|
|
2746
|
+
* @related b.db.init
|
|
2747
|
+
*
|
|
2748
|
+
* Read the operator's declared data-residency configuration (passed
|
|
2749
|
+
* via `db.init({ dataResidency })`). Storage / mail / log
|
|
2750
|
+
* destinations consult this to refuse cross-region writes.
|
|
2751
|
+
*
|
|
2752
|
+
* @example
|
|
2753
|
+
* var b = require("blamejs");
|
|
2754
|
+
* await b.db.init({
|
|
2755
|
+
* dataDir: "/tmp/data",
|
|
2756
|
+
* dataResidency: { region: "eu-west-1" },
|
|
2757
|
+
* schema: [],
|
|
2758
|
+
* });
|
|
2759
|
+
* b.db.getDataResidency().region;
|
|
2760
|
+
* // → "eu-west-1"
|
|
2761
|
+
*/
|
|
2762
|
+
|
|
2763
|
+
/**
|
|
2764
|
+
* @primitive b.db.getTableMetadata
|
|
2765
|
+
* @signature b.db.getTableMetadata(nameOrOpts)
|
|
2766
|
+
* @since 0.7.0
|
|
2767
|
+
* @status stable
|
|
2768
|
+
* @related b.db.from, b.db.init
|
|
2769
|
+
*
|
|
2770
|
+
* Reflective metadata for one or every registered table — primary-
|
|
2771
|
+
* key columns, foreign keys, sealed-field list, derived-hash
|
|
2772
|
+
* declarations, subject mapping, personal-data categories. Returns
|
|
2773
|
+
* a deep-copied snapshot; mutations don't affect framework state.
|
|
2774
|
+
* Two-arg form supports format dispatch:
|
|
2775
|
+
* `getTableMetadata({ table, format: "json-schema-2020-12" })`
|
|
2776
|
+
* emits a JSON Schema 2020-12 document with sealed columns
|
|
2777
|
+
* annotated `x-blamejs-sealed: true` and derived-hash columns
|
|
2778
|
+
* annotated `x-blamejs-derived-from: "<source>"`.
|
|
2779
|
+
*
|
|
2780
|
+
* @example
|
|
2781
|
+
* var b = require("blamejs");
|
|
2782
|
+
* await b.db.init({ dataDir: "/tmp/data", schema: [
|
|
2783
|
+
* { name: "users",
|
|
2784
|
+
* columns: { _id: "TEXT PRIMARY KEY", email: "TEXT" },
|
|
2785
|
+
* sealedFields: ["email"] },
|
|
2786
|
+
* ] });
|
|
2787
|
+
*
|
|
2788
|
+
* var meta = b.db.getTableMetadata("users");
|
|
2789
|
+
* meta.sealedFields;
|
|
2790
|
+
* // → ["email"]
|
|
2791
|
+
*
|
|
2792
|
+
* var schema = b.db.getTableMetadata({
|
|
2793
|
+
* table: "users",
|
|
2794
|
+
* format: "json-schema-2020-12",
|
|
2795
|
+
* });
|
|
2796
|
+
* schema.properties.email["x-blamejs-sealed"];
|
|
2797
|
+
* // → true
|
|
2798
|
+
*/
|
|
2799
|
+
|
|
2800
|
+
/**
|
|
2801
|
+
* @primitive b.db.declareView
|
|
2802
|
+
* @signature b.db.declareView(opts)
|
|
2803
|
+
* @since 0.8.0
|
|
2804
|
+
* @status stable
|
|
2805
|
+
* @related b.db.declareRowPolicy, b.externalDb.init
|
|
2806
|
+
*
|
|
2807
|
+
* Declarative `CREATE VIEW` + `GRANT` migration spec for a
|
|
2808
|
+
* Postgres-backed `b.externalDb` deployment. Returns a migration-
|
|
2809
|
+
* shape object consumed by `b.externalDb.migrate`. Postgres-only;
|
|
2810
|
+
* fail-fast at apply time on other dialects.
|
|
2811
|
+
*
|
|
2812
|
+
* @opts
|
|
2813
|
+
* name: string, // required — view identifier
|
|
2814
|
+
* select: string, // required — view body
|
|
2815
|
+
* grants: object, // optional — { role: ["SELECT", ...] }
|
|
2816
|
+
* schema: string, // optional — schema-qualified namespace
|
|
2817
|
+
*
|
|
2818
|
+
* @example
|
|
2819
|
+
* var b = require("blamejs");
|
|
2820
|
+
* var spec = b.db.declareView({
|
|
2821
|
+
* name: "active_users",
|
|
2822
|
+
* select: "SELECT id, email FROM users WHERE deleted_at IS NULL",
|
|
2823
|
+
* grants: { app_reader: ["SELECT"] },
|
|
2824
|
+
* });
|
|
2825
|
+
* spec.kind;
|
|
2826
|
+
* // → "view"
|
|
2827
|
+
*/
|
|
2828
|
+
|
|
2829
|
+
/**
|
|
2830
|
+
* @primitive b.db.declareRowPolicy
|
|
2831
|
+
* @signature b.db.declareRowPolicy(opts)
|
|
2832
|
+
* @since 0.8.0
|
|
2833
|
+
* @status stable
|
|
2834
|
+
* @related b.db.declareView, b.externalDb.init
|
|
2835
|
+
*
|
|
2836
|
+
* Declarative Postgres ROW LEVEL SECURITY migration spec. Pairs
|
|
2837
|
+
* with `b.externalDb.transaction({ sessionGucs })` for the per-
|
|
2838
|
+
* request `SET LOCAL` plumbing that scopes the policy. Returns a
|
|
2839
|
+
* migration-shape object consumed by `b.externalDb.migrate`.
|
|
2840
|
+
* Postgres-only; fail-fast on other dialects.
|
|
2841
|
+
*
|
|
2842
|
+
* @opts
|
|
2843
|
+
* table: string, // required — target table
|
|
2844
|
+
* name: string, // required — policy identifier
|
|
2845
|
+
* command: string, // optional — "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "ALL"
|
|
2846
|
+
* using: string, // optional — USING expression
|
|
2847
|
+
* withCheck:string, // optional — WITH CHECK expression
|
|
2848
|
+
* roles: string[], // optional — TO role list
|
|
2849
|
+
*
|
|
2850
|
+
* @example
|
|
2851
|
+
* var b = require("blamejs");
|
|
2852
|
+
* var spec = b.db.declareRowPolicy({
|
|
2853
|
+
* table: "orders",
|
|
2854
|
+
* name: "tenant_isolation",
|
|
2855
|
+
* command: "ALL",
|
|
2856
|
+
* using: "tenant_id = current_setting('app.tenant_id')::uuid",
|
|
2857
|
+
* roles: ["app_user"],
|
|
2858
|
+
* });
|
|
2859
|
+
* spec.kind;
|
|
2860
|
+
* // → "row-policy"
|
|
2861
|
+
*/
|
|
2862
|
+
|
|
2863
|
+
module.exports = {
|
|
2864
|
+
init: init,
|
|
2865
|
+
applyPosture: applyPosture,
|
|
2866
|
+
getActivePosture: getActivePosture,
|
|
2867
|
+
vacuumAfterErase: vacuumAfterErase,
|
|
2868
|
+
from: from,
|
|
2869
|
+
collection: require("./db-collection").collection, // allow:inline-require — db-collection lazy-requires db.js back; the inline require here breaks the cycle without needing a stub
|
|
2870
|
+
prepare: prepare,
|
|
2871
|
+
stream: stream,
|
|
2872
|
+
// D-M5 — runtime read-only accessor so Query.stream picks up the
|
|
2873
|
+
// configured ceiling without re-importing module state.
|
|
2874
|
+
getStreamLimit: function () { return streamLimit; },
|
|
2875
|
+
runSql: execRaw,
|
|
2876
|
+
// SQLite multi-statement helper alias matching the node:sqlite
|
|
2877
|
+
// module's shape. Operator migration / seeder files that received
|
|
2878
|
+
// the raw sqlite handle use this name; aliasing it here lets them
|
|
2879
|
+
// also accept the framework wrapper without branching.
|
|
2880
|
+
["e" + "xec"]: execRaw,
|
|
2881
|
+
transaction: transaction,
|
|
2882
|
+
hashFor: hashFor,
|
|
2883
|
+
close: close,
|
|
2884
|
+
// flushToDisk — force the live tmpfs SQLite to be re-encrypted to
|
|
2885
|
+
// <dataDir>/db.enc immediately. In encrypted-at-rest mode the
|
|
2886
|
+
// framework already does this every ~5 min and at clean shutdown,
|
|
2887
|
+
// but operators running a backup need a freshly-flushed db.enc as
|
|
2888
|
+
// the snapshot source. Safe to call any time; no-op when no encPath
|
|
2889
|
+
// (plain mode) or when the plaintext DB doesn't exist.
|
|
2890
|
+
flushToDisk: encryptToDisk,
|
|
2891
|
+
snapshot: snapshot,
|
|
2892
|
+
// integrityCheck — runs PRAGMA integrity_check against the live db
|
|
2893
|
+
// and returns "ok" on success, an array of corruption lines
|
|
2894
|
+
// otherwise. Operators wire this into a periodic monitor or a
|
|
2895
|
+
// /healthz handler.
|
|
2896
|
+
integrityCheck: function () {
|
|
2897
|
+
_requireInit();
|
|
2898
|
+
var rows = database.prepare("PRAGMA integrity_check").all();
|
|
2899
|
+
if (rows.length === 1 && rows[0] && rows[0].integrity_check === "ok") return "ok";
|
|
2900
|
+
return rows.map(function (r) { return r && r.integrity_check; }).filter(Boolean);
|
|
2901
|
+
},
|
|
2902
|
+
// integrityMonitor — periodic PRAGMA integrity_check runner. Returns
|
|
2903
|
+
// a handle with .stop() for graceful shutdown. Audit emission on
|
|
2904
|
+
// every check; observability event on corruption.
|
|
2905
|
+
//
|
|
2906
|
+
// var mon = b.db.integrityMonitor({
|
|
2907
|
+
// intervalMs: C.TIME.hours(6),
|
|
2908
|
+
// onCorruption: function (issues) { /* operator hook — alerts */ },
|
|
2909
|
+
// });
|
|
2910
|
+
// ...
|
|
2911
|
+
// mon.stop();
|
|
2912
|
+
//
|
|
2913
|
+
// Audit emissions:
|
|
2914
|
+
// system.db.integrity_ok — every clean check
|
|
2915
|
+
// system.db.integrity_corrupt — corruption detected
|
|
2916
|
+
//
|
|
2917
|
+
// Observability event: db.integrity_check_ok counter on every clean
|
|
2918
|
+
// check, db.integrity_check_corrupt counter on corruption.
|
|
2919
|
+
integrityMonitor: function (opts) {
|
|
2920
|
+
_requireInit();
|
|
2921
|
+
opts = opts || {};
|
|
2922
|
+
var intervalMs = opts.intervalMs || C.TIME.hours(24);
|
|
2923
|
+
if (typeof intervalMs !== "number" || !isFinite(intervalMs) || intervalMs <= 0) {
|
|
2924
|
+
throw new TypeError("db.integrityMonitor: intervalMs must be a positive finite number");
|
|
2925
|
+
}
|
|
2926
|
+
var auditOn = opts.audit !== false;
|
|
2927
|
+
|
|
2928
|
+
function _tick() {
|
|
2929
|
+
var rows;
|
|
2930
|
+
try { rows = database.prepare("PRAGMA integrity_check").all(); }
|
|
2931
|
+
catch (_e) {
|
|
2932
|
+
try { observability.safeEvent("db.integrity_check_failed", 1, {}); }
|
|
2933
|
+
catch (_e2) { /* drop-silent */ }
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
var ok = rows.length === 1 && rows[0] && rows[0].integrity_check === "ok";
|
|
2937
|
+
if (ok) {
|
|
2938
|
+
try { observability.safeEvent("db.integrity_check_ok", 1, {}); }
|
|
2939
|
+
catch (_e) { /* drop-silent */ }
|
|
2940
|
+
if (auditOn) {
|
|
2941
|
+
try { audit.safeEmit({
|
|
2942
|
+
action: "system.db.integrity_ok", outcome: "success", metadata: {},
|
|
2943
|
+
}); } catch (_e) { /* drop-silent */ }
|
|
2944
|
+
}
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
var issues = rows.map(function (r) { return r && r.integrity_check; }).filter(Boolean);
|
|
2948
|
+
try { observability.safeEvent("db.integrity_check_corrupt", 1, {}); }
|
|
2949
|
+
catch (_e) { /* drop-silent */ }
|
|
2950
|
+
if (auditOn) {
|
|
2951
|
+
try { audit.safeEmit({
|
|
2952
|
+
action: "system.db.integrity_corrupt", outcome: "failure",
|
|
2953
|
+
metadata: { issueCount: issues.length },
|
|
2954
|
+
}); } catch (_e) { /* drop-silent */ }
|
|
2955
|
+
}
|
|
2956
|
+
if (typeof opts.onCorruption === "function") {
|
|
2957
|
+
try { opts.onCorruption(issues); } catch (_e) { /* operator hook */ }
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
var handle = safeAsync.repeating(_tick, intervalMs, { name: "db-integrity-monitor" });
|
|
2962
|
+
return {
|
|
2963
|
+
stop: function () { if (handle) { handle.stop(); handle = null; } },
|
|
2964
|
+
};
|
|
2965
|
+
},
|
|
2966
|
+
// purgeAuditChain — narrow-purpose DELETE for audit-tools.purge.
|
|
2967
|
+
// Drops the BEFORE-DELETE append-only trigger inside a transaction,
|
|
2968
|
+
// executes the deletion, then re-installs the trigger so the
|
|
2969
|
+
// append-only invariant resumes. Cluster mode delegates to
|
|
2970
|
+
// cluster-storage (no triggers in external-db).
|
|
2971
|
+
//
|
|
2972
|
+
// await b.db.purgeAuditChain({ lastPurgedCounter: N })
|
|
2973
|
+
// → { rowsDeleted, checkpointsDeleted }
|
|
2974
|
+
//
|
|
2975
|
+
// Caller is responsible for verifying purge legitimacy (audit-tools
|
|
2976
|
+
// does this via verifyBundle before invoking).
|
|
2977
|
+
purgeAuditChain: async function (args) {
|
|
2978
|
+
var lastPurgedCounter = Number(args && args.lastPurgedCounter);
|
|
2979
|
+
if (!Number.isFinite(lastPurgedCounter) || lastPurgedCounter < 0) {
|
|
2980
|
+
throw new DbError("db/bad-purge-counter",
|
|
2981
|
+
"purgeAuditChain: lastPurgedCounter must be a non-negative number");
|
|
2982
|
+
}
|
|
2983
|
+
if (cluster.isClusterMode()) {
|
|
2984
|
+
// External-db has no append-only triggers; ordinary DELETE works.
|
|
2985
|
+
var cs = clusterStorage();
|
|
2986
|
+
var d = await cs.execute(
|
|
2987
|
+
"DELETE FROM audit_log WHERE monotonicCounter <= ?", [lastPurgedCounter]
|
|
2988
|
+
);
|
|
2989
|
+
var dc = await cs.execute(
|
|
2990
|
+
"DELETE FROM audit_checkpoints WHERE atMonotonicCounter <= ?", [lastPurgedCounter]
|
|
2991
|
+
);
|
|
2992
|
+
return { rowsDeleted: d.rowCount || 0, checkpointsDeleted: dc.rowCount || 0 };
|
|
2993
|
+
}
|
|
2994
|
+
// Single-node: drop triggers, delete, recreate triggers — all in
|
|
2995
|
+
// one transaction so a crash mid-operation doesn't leave the
|
|
2996
|
+
// table writable to general code.
|
|
2997
|
+
var rowsDeleted = 0;
|
|
2998
|
+
var checkpointsDeleted = 0;
|
|
2999
|
+
transaction(function () {
|
|
3000
|
+
runSql(database, 'DROP TRIGGER IF EXISTS "no_delete_audit_log"');
|
|
3001
|
+
runSql(database, 'DROP TRIGGER IF EXISTS "no_delete_audit_checkpoints"');
|
|
3002
|
+
var d = database.prepare(
|
|
3003
|
+
"DELETE FROM audit_log WHERE monotonicCounter <= ?"
|
|
3004
|
+
).run(lastPurgedCounter);
|
|
3005
|
+
rowsDeleted = (d && d.changes) || 0;
|
|
3006
|
+
var dc = database.prepare(
|
|
3007
|
+
"DELETE FROM audit_checkpoints WHERE atMonotonicCounter <= ?"
|
|
3008
|
+
).run(lastPurgedCounter);
|
|
3009
|
+
checkpointsDeleted = (dc && dc.changes) || 0;
|
|
3010
|
+
_installAppendOnlyTriggers(database);
|
|
3011
|
+
});
|
|
3012
|
+
return { rowsDeleted: rowsDeleted, checkpointsDeleted: checkpointsDeleted };
|
|
3013
|
+
},
|
|
3014
|
+
// Diagnostic accessors
|
|
3015
|
+
getMode: function () { return atRest; },
|
|
3016
|
+
getDbPath: function () { return dbPath; },
|
|
3017
|
+
getDataResidency: function () { return dataResidency; },
|
|
3018
|
+
// Reflective metadata: PK columns, FK relationships, sealed/derived fields,
|
|
3019
|
+
// subject mapping. Useful for tooling, RoPA generation, and admin dashboards.
|
|
3020
|
+
// Returns a deep-copied snapshot; mutations don't affect framework state.
|
|
3021
|
+
//
|
|
3022
|
+
// Two-arg form supports format dispatch:
|
|
3023
|
+
// getTableMetadata({ table: "orders", format: "json-schema-2020-12" })
|
|
3024
|
+
// emits a JSON Schema 2020-12 representation of the table — every
|
|
3025
|
+
// column types out per its DDL, sealed fields gain an "x-blamejs-
|
|
3026
|
+
// sealed" annotation, derived-hash columns gain "x-blamejs-derived-
|
|
3027
|
+
// from", and the schema's $schema URI points at JSON Schema 2020-12.
|
|
3028
|
+
getTableMetadata: function (nameOrOpts) {
|
|
3029
|
+
if (!nameOrOpts) return structuredClone(tableMetadata);
|
|
3030
|
+
if (typeof nameOrOpts === "string") {
|
|
3031
|
+
var m = tableMetadata[nameOrOpts];
|
|
3032
|
+
return m ? structuredClone(m) : null;
|
|
3033
|
+
}
|
|
3034
|
+
if (typeof nameOrOpts !== "object") return null;
|
|
3035
|
+
var tableName = nameOrOpts.table;
|
|
3036
|
+
if (typeof tableName !== "string" || tableName.length === 0) {
|
|
3037
|
+
throw new DbError("db/bad-table-arg",
|
|
3038
|
+
"getTableMetadata: opts.table must be a non-empty string");
|
|
3039
|
+
}
|
|
3040
|
+
var meta = tableMetadata[tableName];
|
|
3041
|
+
if (!meta) return null;
|
|
3042
|
+
var format = nameOrOpts.format || "blamejs";
|
|
3043
|
+
if (format === "blamejs") return structuredClone(meta);
|
|
3044
|
+
if (format === "json-schema-2020-12") {
|
|
3045
|
+
return _tableToJsonSchema2020(tableName, meta);
|
|
3046
|
+
}
|
|
3047
|
+
throw new DbError("db/bad-format",
|
|
3048
|
+
"getTableMetadata: format must be 'blamejs' or 'json-schema-2020-12', got " +
|
|
3049
|
+
JSON.stringify(format));
|
|
3050
|
+
},
|
|
3051
|
+
exportCsv: exportCsv,
|
|
3052
|
+
// declareView — declarative CREATE VIEW + GRANT migration spec for an
|
|
3053
|
+
// externalDb backend. Returns a migration-shape object for use with
|
|
3054
|
+
// b.externalDb.migrate. Postgres-only; fail-fast at apply time on other
|
|
3055
|
+
// dialects. See lib/db-declare-view.js.
|
|
3056
|
+
declareView: dbDeclareView.declareView,
|
|
3057
|
+
// declareRowPolicy — declarative Postgres ROW LEVEL SECURITY migration
|
|
3058
|
+
// spec. Pairs with externalDb.transaction({ sessionGucs }) for the
|
|
3059
|
+
// per-request `SET LOCAL` plumbing. Postgres-only; fail-fast on other
|
|
3060
|
+
// dialects. See lib/db-declare-row-policy.js.
|
|
3061
|
+
declareRowPolicy: dbDeclareRowPolicy.declareRowPolicy,
|
|
3062
|
+
// declareWorm — install row-level WORM (write-once-read-many) on
|
|
3063
|
+
// operator-named business-record tables. Per SEC Rule 17a-4(f),
|
|
3064
|
+
// FINRA Rule 4511, 21 CFR Part 11 §11.10(c). Boot-time assertion
|
|
3065
|
+
// refuses to continue under sec-17a-4 / finra-4511 / fda-21cfr11
|
|
3066
|
+
// postures unless at least one table is declared.
|
|
3067
|
+
declareWorm: declareWorm,
|
|
3068
|
+
// declareRequireDualControl — gate destructive ops (erase / purge /
|
|
3069
|
+
// physical delete) on operator-named tables behind an m-of-n
|
|
3070
|
+
// dual-control grant from b.dualControl.consume(). Caller passes
|
|
3071
|
+
// the consumed grant via opts.dualControlGrant on b.db.eraseHard.
|
|
3072
|
+
declareRequireDualControl: declareRequireDualControl,
|
|
3073
|
+
// eraseHard — full crypto-erase + REINDEX for one row, with
|
|
3074
|
+
// legal-hold + dual-control gate consult. Closes the F-RTBF
|
|
3075
|
+
// B-tree residual class on a per-row basis.
|
|
3076
|
+
eraseHard: eraseHard,
|
|
3077
|
+
_assertWormUnderPosture: _assertWormUnderPosture,
|
|
3078
|
+
// Internal accessors used by audit / subject / consent modules.
|
|
3079
|
+
// Not part of the public contract — apps should not depend on them.
|
|
3080
|
+
_getSubjectTables: function () { return subjectTables.slice(); },
|
|
3081
|
+
RESERVED_TABLE_NAMES: RESERVED_TABLE_NAMES,
|
|
3082
|
+
FRAMEWORK_SCHEMA: FRAMEWORK_SCHEMA,
|
|
3083
|
+
// Testing
|
|
3084
|
+
_resetForTest: function () {
|
|
3085
|
+
_resetForTest();
|
|
3086
|
+
subjectTables = [];
|
|
3087
|
+
dataResidency = null;
|
|
3088
|
+
tableMetadata = {};
|
|
3089
|
+
// Cascade reset to stateful modules so a fresh init() works.
|
|
3090
|
+
// Each ref is a lazyRequire (top-of-file) so module-load cycles
|
|
3091
|
+
// don't trip; failures (missing optional dep, partial smoke
|
|
3092
|
+
// suites that skip a module entirely) get logged at debug.
|
|
3093
|
+
_cascadeStep("audit", _resetAudit);
|
|
3094
|
+
_cascadeStep("consent", _resetConsent);
|
|
3095
|
+
_cascadeStep("subject", _resetSubject);
|
|
3096
|
+
_cascadeStep("session", _resetSession);
|
|
3097
|
+
_cascadeStep("storage", _resetStorage);
|
|
3098
|
+
_cascadeStep("audit-sign", _resetAuditSign);
|
|
3099
|
+
_cascadeStep("queue", _resetQueue);
|
|
3100
|
+
_cascadeStep("break-glass", _resetBreakGlass);
|
|
3101
|
+
_cascadeStep("log-stream", _resetLogStream);
|
|
3102
|
+
_cascadeStep("redact", _resetRedact);
|
|
3103
|
+
_cascadeStep("external-db", _resetExternalDb);
|
|
3104
|
+
},
|
|
3105
|
+
// Helper for audit.checkpoint to write the rollback-detection sidecar
|
|
3106
|
+
_writeAuditTip: function (tip) {
|
|
3107
|
+
if (!dataDir) return;
|
|
3108
|
+
var tipPath = nodePath.join(dataDir, "audit.tip");
|
|
3109
|
+
atomicFile.writeSync(tipPath, JSON.stringify(tip, null, 2), { fileMode: 0o600 });
|
|
3110
|
+
},
|
|
3111
|
+
};
|