@blamejs/core 0.8.42 → 0.8.49
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 +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/forms.js
CHANGED
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.forms
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title Forms
|
|
4
6
|
*
|
|
5
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* HTML form rendering with CSRF token injection, accessible labels,
|
|
9
|
+
* field-type dispatch, and shared-spec server-side validation.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* `b.forms.render(spec)` emits a complete `<form>` element with
|
|
12
|
+
* auto-escaped attributes, a hidden CSRF input, and per-field
|
|
13
|
+
* markup for text / email / password / number / checkbox / radio /
|
|
14
|
+
* textarea / select / hidden / submit. Every attribute value is
|
|
15
|
+
* forced through `escapeAttribute` so a hostile field name or value
|
|
16
|
+
* can't break out of the double-quoted attribute context.
|
|
12
17
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* `b.forms.validate(spec, body)` walks the same field spec the
|
|
19
|
+
* renderer accepts and returns `{ valid, errors, values }` —
|
|
20
|
+
* coerced types, required-field checks, length bounds, regex
|
|
21
|
+
* pattern, enum membership. Sharing the spec is the point: the
|
|
22
|
+
* operator's "this is what the form looks like" and "this is what
|
|
23
|
+
* the form expects" stay in lock-step, eliminating the drift class
|
|
24
|
+
* where a field gets added to the renderer but not the validator.
|
|
19
25
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
26
|
+
* CSRF tokens are 32-byte hex strings from
|
|
27
|
+
* `b.crypto.generateToken`; `verifyCsrfToken` is constant-time.
|
|
28
|
+
* The middleware in `b.middleware.csrfProtect` does the actual
|
|
29
|
+
* request-time gating — this module supplies the issue / verify
|
|
30
|
+
* primitives.
|
|
24
31
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* what the form expects." A change to the spec adjusts both.
|
|
28
|
-
*
|
|
29
|
-
* Public API:
|
|
30
|
-
*
|
|
31
|
-
* forms.generateCsrfToken() → "<64 hex chars>"
|
|
32
|
-
* forms.verifyCsrfToken(submitted, expected) → boolean (timing-safe)
|
|
33
|
-
* forms.render(spec) → string (complete <form>…</form>)
|
|
34
|
-
* forms.validate(spec, body) → { valid, errors, values }
|
|
35
|
-
* forms.escapeAttribute(value) → string (double-quoted-attr context)
|
|
36
|
-
* forms.escapeHtml = template.escapeHtml (re-export for convenience)
|
|
32
|
+
* @card
|
|
33
|
+
* HTML form rendering with CSRF token injection, accessible labels, field-type dispatch, and shared-spec server-side validation.
|
|
37
34
|
*/
|
|
38
35
|
var C = require("./constants");
|
|
39
36
|
var { generateToken, timingSafeEqual } = require("./crypto");
|
|
@@ -55,10 +52,44 @@ var MAX_EMAIL_LENGTH = 254;
|
|
|
55
52
|
// (and what most servers / proxies enforce) is 8 KiB.
|
|
56
53
|
var MAX_URL_LENGTH = C.BYTES.kib(8);
|
|
57
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @primitive b.forms.generateCsrfToken
|
|
57
|
+
* @signature b.forms.generateCsrfToken()
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
* @status stable
|
|
60
|
+
* @related b.forms.verifyCsrfToken, b.middleware.csrfProtect
|
|
61
|
+
*
|
|
62
|
+
* Returns a 32-byte (64 hex char) random token suitable for embedding
|
|
63
|
+
* in a hidden form field. Entropy comes from `b.crypto.generateToken`,
|
|
64
|
+
* which routes through Node's `crypto.randomBytes`. The token is
|
|
65
|
+
* opaque to the framework — operators store it in the session and
|
|
66
|
+
* compare via `verifyCsrfToken` on submit.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* var token = b.forms.generateCsrfToken();
|
|
70
|
+
* // → "8f3a1c4b...e7d9" (64 hex chars)
|
|
71
|
+
*/
|
|
58
72
|
function generateCsrfToken() {
|
|
59
73
|
return generateToken(CSRF_TOKEN_BYTES);
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @primitive b.forms.verifyCsrfToken
|
|
78
|
+
* @signature b.forms.verifyCsrfToken(submitted, expected)
|
|
79
|
+
* @since 0.1.0
|
|
80
|
+
* @status stable
|
|
81
|
+
* @related b.forms.generateCsrfToken, b.middleware.csrfProtect
|
|
82
|
+
*
|
|
83
|
+
* Constant-time comparison of a submitted token against the expected
|
|
84
|
+
* value. Returns `false` for any non-string input, mismatched length,
|
|
85
|
+
* or empty submitted token — never throws. Routes through
|
|
86
|
+
* `b.crypto.timingSafeEqual` so an attacker can't probe character
|
|
87
|
+
* positions via response-time differences.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* var ok = b.forms.verifyCsrfToken(req.body.csrf, req.session.csrf);
|
|
91
|
+
* // → true (when both strings are non-empty and byte-identical)
|
|
92
|
+
*/
|
|
62
93
|
function verifyCsrfToken(submitted, expected) {
|
|
63
94
|
if (typeof submitted !== "string" || typeof expected !== "string") return false;
|
|
64
95
|
if (submitted.length === 0 || submitted.length !== expected.length) return false;
|
|
@@ -82,6 +113,25 @@ var ATTR_ESCAPE_MAP = {
|
|
|
82
113
|
};
|
|
83
114
|
var ATTR_ESCAPE_RE = /[&<>"'`=]/g;
|
|
84
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @primitive b.forms.escapeAttribute
|
|
118
|
+
* @signature b.forms.escapeAttribute(value)
|
|
119
|
+
* @since 0.1.0
|
|
120
|
+
* @status stable
|
|
121
|
+
* @related b.forms.render, b.template.escapeHtml
|
|
122
|
+
*
|
|
123
|
+
* Escapes a value for safe interpolation into a double-quoted HTML
|
|
124
|
+
* attribute context. Stricter than `b.template.escapeHtml`: also
|
|
125
|
+
* escapes backtick (some browsers parse `` ` `` as an attribute
|
|
126
|
+
* delimiter under quirks mode) and `=` (defense-in-depth for any
|
|
127
|
+
* unquoted-attribute slips). `null` / `undefined` become the empty
|
|
128
|
+
* string. Used internally by `b.forms.render` for every attribute
|
|
129
|
+
* value; exported for operators rendering their own form fragments.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* var safe = b.forms.escapeAttribute('a"b<c>d');
|
|
133
|
+
* // → "a"b<c>d"
|
|
134
|
+
*/
|
|
85
135
|
function escapeAttribute(value) {
|
|
86
136
|
if (value === null || value === undefined) return "";
|
|
87
137
|
var s = typeof value === "string" ? value : String(value);
|
|
@@ -214,6 +264,34 @@ function _renderField(field) {
|
|
|
214
264
|
return control;
|
|
215
265
|
}
|
|
216
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @primitive b.forms.render
|
|
269
|
+
* @signature b.forms.render(spec)
|
|
270
|
+
* @since 0.1.0
|
|
271
|
+
* @status stable
|
|
272
|
+
* @related b.forms.validate, b.forms.generateCsrfToken, b.template.escapeHtml
|
|
273
|
+
*
|
|
274
|
+
* Renders a complete `<form>` element from a typed spec — method,
|
|
275
|
+
* action, fields, optional CSRF token. Each field's `type` selects
|
|
276
|
+
* the input widget (text / email / password / number / checkbox /
|
|
277
|
+
* radio / textarea / select / hidden / submit). All attribute values
|
|
278
|
+
* pass through `escapeAttribute`; `spec.csrfToken` (if present) is
|
|
279
|
+
* embedded as a hidden `_csrf` input. Throws when `spec.action` is
|
|
280
|
+
* missing / empty or `spec.fields` isn't an array.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* var html = b.forms.render({
|
|
284
|
+
* action: "/login",
|
|
285
|
+
* method: "POST",
|
|
286
|
+
* csrfToken: "8f3a1c4b...e7d9",
|
|
287
|
+
* fields: [
|
|
288
|
+
* { type: "email", name: "email", label: "Email", required: true },
|
|
289
|
+
* { type: "password", name: "password", label: "Password", required: true },
|
|
290
|
+
* { type: "submit", name: "submit", value: "Sign in" },
|
|
291
|
+
* ],
|
|
292
|
+
* });
|
|
293
|
+
* // → "<form method=\"POST\" action=\"/login\">...<input type=\"hidden\" name=\"_csrf\" .../></form>"
|
|
294
|
+
*/
|
|
217
295
|
function render(spec) {
|
|
218
296
|
if (!spec || typeof spec.action !== "string" || spec.action.length === 0) {
|
|
219
297
|
throw new Error("forms.render: spec.action is required");
|
|
@@ -296,6 +374,31 @@ function _isEmpty(v) {
|
|
|
296
374
|
return v === undefined || v === null || v === "";
|
|
297
375
|
}
|
|
298
376
|
|
|
377
|
+
/**
|
|
378
|
+
* @primitive b.forms.validate
|
|
379
|
+
* @signature b.forms.validate(spec, body)
|
|
380
|
+
* @since 0.1.0
|
|
381
|
+
* @status stable
|
|
382
|
+
* @related b.forms.render, b.safeSchema
|
|
383
|
+
*
|
|
384
|
+
* Walks the same spec the renderer accepts and validates a submitted
|
|
385
|
+
* body. Per field: required-field check, type coercion (string /
|
|
386
|
+
* number / boolean / email / url), `minLength` / `maxLength` bounds,
|
|
387
|
+
* regex `pattern`, `enum` membership. Returns
|
|
388
|
+
* `{ valid: boolean, errors: { field: msg, ... }, values: { ... } }`.
|
|
389
|
+
* The `values` object holds coerced values keyed by field name —
|
|
390
|
+
* route handlers consume `result.values` directly without re-parsing.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* var result = b.forms.validate(
|
|
394
|
+
* { fields: [
|
|
395
|
+
* { type: "email", name: "email", required: true },
|
|
396
|
+
* { type: "number", name: "age", minLength: 1 },
|
|
397
|
+
* ] },
|
|
398
|
+
* { email: "ada@example.com", age: "37" }
|
|
399
|
+
* );
|
|
400
|
+
* // → { valid: true, errors: {}, values: { email: "ada@example.com", age: 37 } }
|
|
401
|
+
*/
|
|
299
402
|
function validate(spec, body) {
|
|
300
403
|
if (!spec || !Array.isArray(spec.fields)) {
|
|
301
404
|
throw new Error("forms.validate: spec.fields must be an array");
|
package/lib/framework-error.js
CHANGED
|
@@ -407,6 +407,152 @@ var A2aError = defineClass("A2aError", { alwaysPermane
|
|
|
407
407
|
// missing or malformed router-token, replay (nonce already seen),
|
|
408
408
|
// unauthorized SDL probe. Permanent.
|
|
409
409
|
var GraphqlFederationError = defineClass("GraphqlFederationError", { alwaysPermanent: true });
|
|
410
|
+
// Fda21Cfr11Error covers FDA 21 CFR Part 11 §11.10(e) audit-content
|
|
411
|
+
// shape + §11.50/§11.70 electronic-signature shape violations: missing
|
|
412
|
+
// printedName / dateTimeUtc / signatureMeaning / predicateRule / signed
|
|
413
|
+
// record bind, before/after pair missing on a GxP audit row, signature-
|
|
414
|
+
// algorithm allowlist drift, posture interceptor refusal. Permanent —
|
|
415
|
+
// every case is operator-supplied data shape, not transient.
|
|
416
|
+
var Fda21Cfr11Error = defineClass("Fda21Cfr11Error", { alwaysPermanent: true });
|
|
417
|
+
// AuditDailyReviewError covers PCI DSS 4.0 Req 10.4.1.1 daily-review
|
|
418
|
+
// misconfiguration: bad cron / lookback / severity threshold, missing
|
|
419
|
+
// notify callback under threshold-bearing posture, audit-source not
|
|
420
|
+
// queryable. Permanent — config-time errors.
|
|
421
|
+
var AuditDailyReviewError = defineClass("AuditDailyReviewError", { alwaysPermanent: true });
|
|
422
|
+
// AuditSegregationError covers SOX §404 / SOC 2 CC1.3 actor-binding
|
|
423
|
+
// violations: bound-actor mismatch on emit, missing db-role context,
|
|
424
|
+
// trigger-installation failure when sox-404 / soc2 posture demands it.
|
|
425
|
+
// Permanent — operator-misconfig or in-flight identity mismatch.
|
|
426
|
+
var AuditSegregationError = defineClass("AuditSegregationError", { alwaysPermanent: true });
|
|
427
|
+
// DdlChangeControlError covers SOX §404 / PCI-DSS DDL change-control
|
|
428
|
+
// violations: insufficient approvers, approval window violation,
|
|
429
|
+
// signature-mismatch on apply, duplicate approval, application of an
|
|
430
|
+
// already-applied or rejected change. Permanent.
|
|
431
|
+
var DdlChangeControlError = defineClass("DdlChangeControlError", { alwaysPermanent: true });
|
|
432
|
+
// LegalHoldError covers subject-level legal-hold registry violations:
|
|
433
|
+
// missing subjectId, malformed reason/citation, duplicate placement,
|
|
434
|
+
// release-without-placement, bad opts. Permanent — config / API
|
|
435
|
+
// shape errors, not transient.
|
|
436
|
+
var LegalHoldError = defineClass("LegalHoldError", { alwaysPermanent: true });
|
|
437
|
+
// WormViolationError covers operator-declared WORM (write-once-read-
|
|
438
|
+
// many) trigger-installation failures and posture-asserted boot
|
|
439
|
+
// gates: declareWorm called on a non-existent table, table requires
|
|
440
|
+
// WORM under sec-17a-4 / finra-4511 / fda-21cfr11 but none declared,
|
|
441
|
+
// operator attempted to drop the WORM trigger outside a sanctioned
|
|
442
|
+
// retention.purge flow. Permanent.
|
|
443
|
+
var WormViolationError = defineClass("WormViolationError", { alwaysPermanent: true });
|
|
444
|
+
// SandboxError covers operator-supplied transform-source isolation
|
|
445
|
+
// failures: bad opts at create() (non-string source, non-finite
|
|
446
|
+
// timeoutMs / maxBytes, allowed-list contains a non-allowlisted
|
|
447
|
+
// global), worker-thread spawn failure, timeout exceeded, peak-bytes
|
|
448
|
+
// overrun, non-allowlisted-global access, output-shape-too-large,
|
|
449
|
+
// runtime exceptions inside the transform. Permanent — every case is
|
|
450
|
+
// either operator-misconfig or a transform that the host should
|
|
451
|
+
// refuse rather than retry. Operator decides at the call site whether
|
|
452
|
+
// to surface the refusal as a 4xx or to fall back to a default value.
|
|
453
|
+
var SandboxError = defineClass("SandboxError", { alwaysPermanent: true });
|
|
454
|
+
// DlpError — outbound DLP scanner refusal raised by
|
|
455
|
+
// b.redact.installOutboundDlp's interceptors when the classifier verdict
|
|
456
|
+
// is "refuse". Permanent; the request body must be operator-corrected
|
|
457
|
+
// before re-attempt rather than retried as-is.
|
|
458
|
+
var DlpError = defineClass("DlpError", { alwaysPermanent: true });
|
|
459
|
+
// AuthBotChallengeError — challenge / escalation refusal raised by
|
|
460
|
+
// b.authBotChallenge when the operator-supplied challengeFn is
|
|
461
|
+
// missing, returns a non-boolean verdict, or throws. Permanent.
|
|
462
|
+
var AuthBotChallengeError = defineClass("AuthBotChallengeError", { alwaysPermanent: true });
|
|
463
|
+
// SessionDeviceBindingError — fingerprint-drift refusal raised by
|
|
464
|
+
// b.sessionDeviceBinding when create-time opts are malformed or the
|
|
465
|
+
// boundKeyResolver returns a non-Buffer. Permanent.
|
|
466
|
+
var SessionDeviceBindingError = defineClass("SessionDeviceBindingError", { alwaysPermanent: true });
|
|
467
|
+
// AcmeError — RFC 8555 ACME + RFC 9773 ACME Renewal Information
|
|
468
|
+
// (ARI) protocol violations raised by b.acme: bad opts at create
|
|
469
|
+
// (non-https directory URL, missing accountKey, malformed audit hook),
|
|
470
|
+
// directory-fetch failure shape, newOrder/finalize/retrieveCert
|
|
471
|
+
// HTTP-status / response-shape errors, ARI window parse failures,
|
|
472
|
+
// retrieveCert returning non-PEM bytes, renewIfDue called before
|
|
473
|
+
// retrieveCert / before ARI URL is reachable. Permanent — every case
|
|
474
|
+
// is operator-misconfig or a CA-side response shape the framework
|
|
475
|
+
// refuses to coerce. withStatusCode so HTTP-shaped failures from the
|
|
476
|
+
// CA surface as a typed status for retry classification.
|
|
477
|
+
var AcmeError = defineClass("AcmeError", { withStatusCode: true });
|
|
478
|
+
|
|
479
|
+
// HpkeError — RFC 9180 Hybrid Public-Key Encryption (lib/crypto-hpke.js).
|
|
480
|
+
// Bad opts at the call site, KEM encap/decap failures, AEAD tag failures.
|
|
481
|
+
var HpkeError = defineClass("HpkeError", { alwaysPermanent: true });
|
|
482
|
+
// TlsExporterError — RFC 9266 TLS-Exporter channel binding
|
|
483
|
+
// (lib/tls-exporter.js). Non-TLS sockets, TLS<1.3 sessions, short
|
|
484
|
+
// exporter outputs.
|
|
485
|
+
var TlsExporterError = defineClass("TlsExporterError", { alwaysPermanent: true });
|
|
486
|
+
// HttpSigError — RFC 9421 HTTP Message Signatures (lib/http-message-
|
|
487
|
+
// signature.js). Bad opts, missing covered components, unsupported alg.
|
|
488
|
+
var HttpSigError = defineClass("HttpSigError", { alwaysPermanent: true });
|
|
489
|
+
// HttpClientError — outbound httpClient streaming primitives
|
|
490
|
+
// (b.httpClient.downloadStream / b.httpClient.uploadMultipartStream).
|
|
491
|
+
// withStatusCode so HTTP-shaped failures (404, 500, 503) carry the
|
|
492
|
+
// upstream status for retry classification. Codes follow the
|
|
493
|
+
// "httpclient/<reason>" shape: hash-mismatch, dest-not-writable,
|
|
494
|
+
// missing-file, http-error, etc.
|
|
495
|
+
var HttpClientError = defineClass("HttpClientError", { withStatusCode: true });
|
|
496
|
+
// KeychainError — b.keychain (lib/keychain.js). Bad opts at config time,
|
|
497
|
+
// native-tool exec failure (security / secret-tool / PowerShell
|
|
498
|
+
// CredentialManager), file-fallback unseal / shape failure, oversized
|
|
499
|
+
// native-tool output. alwaysPermanent — every case is operator-misconfig
|
|
500
|
+
// or a host-environment condition the framework refuses to coerce.
|
|
501
|
+
var KeychainError = defineClass("KeychainError", { alwaysPermanent: true });
|
|
502
|
+
// WatcherError — b.watcher recursive-fs.watch wrapper (lib/watcher.js).
|
|
503
|
+
// Bad opts at create (non-string root, missing root, bad ignore pattern,
|
|
504
|
+
// non-finite debounceMs, non-function hook), recursive-watch unsupported
|
|
505
|
+
// on the host platform/kernel, fs.watch start failure, pending-event
|
|
506
|
+
// queue overflow under runaway-directory pressure. alwaysPermanent —
|
|
507
|
+
// every case is config-misuse or a host-environment refusal the
|
|
508
|
+
// framework will not coerce.
|
|
509
|
+
var WatcherError = defineClass("WatcherError", { alwaysPermanent: true });
|
|
510
|
+
// LocalDbThinError — b.localDb.thin lightweight node:sqlite wrapper
|
|
511
|
+
// (lib/local-db-thin.js). Bad opts at create, node:sqlite unavailable
|
|
512
|
+
// on the host Node build, integrity_check failure under recovery:
|
|
513
|
+
// "refuse", recovery-rename I/O failure, post-close handle reuse, bad
|
|
514
|
+
// SQL passed to prepare/run/query. alwaysPermanent — every case is
|
|
515
|
+
// caller-shape misuse or an irrecoverable on-disk condition.
|
|
516
|
+
var LocalDbThinError = defineClass("LocalDbThinError", { alwaysPermanent: true });
|
|
517
|
+
// RouterError covers operator-shape violations on the router primitive:
|
|
518
|
+
// invalid `allowedRedirectOrigins` opt at create time, and cross-origin
|
|
519
|
+
// `res.redirect()` targets that are not on the allowlist. alwaysPermanent
|
|
520
|
+
// — every case is config-time programming bug or an outbound-redirect
|
|
521
|
+
// shape error that retry will not recover.
|
|
522
|
+
var RouterError = defineClass("RouterError", { alwaysPermanent: true });
|
|
523
|
+
// WorkerPoolError — b.workerPool (lib/worker-pool.js). Bad opts at
|
|
524
|
+
// config (size / maxQueueDepth / taskTimeoutMs out of range, non-
|
|
525
|
+
// absolute scriptPath, non-function onExit), runtime queue-full,
|
|
526
|
+
// per-task timeout, worker spawn / error / non-zero exit, malformed
|
|
527
|
+
// reply envelope, terminate-aborted tasks. alwaysPermanent — every
|
|
528
|
+
// case is operator-misconfig or worker-script bug; retry without a
|
|
529
|
+
// fix would just repeat the failure.
|
|
530
|
+
var WorkerPoolError = defineClass("WorkerPoolError", { alwaysPermanent: true });
|
|
531
|
+
// ArgParserError — b.argParser declarative CLI argument parser
|
|
532
|
+
// (lib/arg-parser.js). Bad opts at create time (unsupported flag type,
|
|
533
|
+
// duplicate flag/alias, malformed flag/command name, prototype-polluting
|
|
534
|
+
// name like __proto__/constructor/prototype), bad parse-time argv (not
|
|
535
|
+
// an array, non-string elements, unknown flag/command, missing required
|
|
536
|
+
// flag, unparseable number/boolean coercion, missing flag value).
|
|
537
|
+
// alwaysPermanent — every case is operator-shape misuse the framework
|
|
538
|
+
// will not coerce; the operator fixes the spec or the argv source.
|
|
539
|
+
var ArgParserError = defineClass("ArgParserError", { alwaysPermanent: true });
|
|
540
|
+
// DaemonError — b.daemon (lib/daemon.js). Bad opts at start/stop, pidfile
|
|
541
|
+
// already held by a live PID, spawn failure for detached-fork mode,
|
|
542
|
+
// log-file open failure, kill() failure outside ESRCH. alwaysPermanent —
|
|
543
|
+
// every case is operator-misconfig or a host-environment condition the
|
|
544
|
+
// framework refuses to coerce; transient-shaped failures (process
|
|
545
|
+
// already exited between read and kill) are surfaced as a non-error
|
|
546
|
+
// "stopped: false, reason: stale" return.
|
|
547
|
+
var DaemonError = defineClass("DaemonError", { alwaysPermanent: true });
|
|
548
|
+
// SelfUpdateError — b.selfUpdate (lib/self-update.js). Bad opts at
|
|
549
|
+
// poll/verify/swap/rollback, non-2xx releases-feed response, malformed
|
|
550
|
+
// JSON, missing tag_name, signature verify mismatch, atomic-swap or
|
|
551
|
+
// rollback failure, cross-device install failure. alwaysPermanent —
|
|
552
|
+
// every case is operator-misconfig or a release-feed shape the
|
|
553
|
+
// framework refuses to coerce. Operators wrap the call in their own
|
|
554
|
+
// retry policy when polling against a flaky CDN.
|
|
555
|
+
var SelfUpdateError = defineClass("SelfUpdateError", { alwaysPermanent: true });
|
|
410
556
|
|
|
411
557
|
module.exports = {
|
|
412
558
|
FrameworkError: FrameworkError,
|
|
@@ -472,4 +618,27 @@ module.exports = {
|
|
|
472
618
|
AiInputError: AiInputError,
|
|
473
619
|
A2aError: A2aError,
|
|
474
620
|
GraphqlFederationError: GraphqlFederationError,
|
|
621
|
+
Fda21Cfr11Error: Fda21Cfr11Error,
|
|
622
|
+
AuditDailyReviewError: AuditDailyReviewError,
|
|
623
|
+
AuditSegregationError: AuditSegregationError,
|
|
624
|
+
DdlChangeControlError: DdlChangeControlError,
|
|
625
|
+
LegalHoldError: LegalHoldError,
|
|
626
|
+
WormViolationError: WormViolationError,
|
|
627
|
+
SandboxError: SandboxError,
|
|
628
|
+
DlpError: DlpError,
|
|
629
|
+
AuthBotChallengeError: AuthBotChallengeError,
|
|
630
|
+
SessionDeviceBindingError: SessionDeviceBindingError,
|
|
631
|
+
AcmeError: AcmeError,
|
|
632
|
+
HpkeError: HpkeError,
|
|
633
|
+
TlsExporterError: TlsExporterError,
|
|
634
|
+
HttpSigError: HttpSigError,
|
|
635
|
+
HttpClientError: HttpClientError,
|
|
636
|
+
KeychainError: KeychainError,
|
|
637
|
+
WatcherError: WatcherError,
|
|
638
|
+
LocalDbThinError: LocalDbThinError,
|
|
639
|
+
RouterError: RouterError,
|
|
640
|
+
WorkerPoolError: WorkerPoolError,
|
|
641
|
+
ArgParserError: ArgParserError,
|
|
642
|
+
DaemonError: DaemonError,
|
|
643
|
+
SelfUpdateError: SelfUpdateError,
|
|
475
644
|
};
|
package/lib/framework-schema.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.frameworkSchema
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title Framework Schema
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* later release) call to create them.
|
|
7
|
+
* @intro
|
|
8
|
+
* Framework-defined SQL schema (audit / sessions / api_keys / cache /
|
|
9
|
+
* break-glass / scheduler-ticks / pubsub / rate-limit / seeders /
|
|
10
|
+
* etc.) — declarative, migration-aware, and dialect-portable across
|
|
11
|
+
* Postgres and SQLite.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* When cluster mode is active the framework's audit chain, consent
|
|
14
|
+
* log, audit checkpoints, audit tip, scheduler ticks, rate-limit
|
|
15
|
+
* counters, pubsub fan-out, sessions, jobs, cache, seeders, and
|
|
16
|
+
* break-glass policies/grants live in the operator's external
|
|
17
|
+
* database (configured via `b.externalDb.init`). This module owns
|
|
18
|
+
* the DDL for those tables and exposes a single idempotent entry
|
|
19
|
+
* point — `b.frameworkSchema.ensureSchema` — that operators (or the
|
|
20
|
+
* framework's leader-acquire hook in a later release) call to create
|
|
21
|
+
* them at boot.
|
|
14
22
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
23
|
+
* External-db tables are prefixed with `_blamejs_` so they never
|
|
24
|
+
* collide with the operator's application tables:
|
|
17
25
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* dialect-aware ensureSchema fans out the DDL to either the local-
|
|
21
|
-
* SQLite (db.js's FRAMEWORK_SCHEMA) or the external-db backend.
|
|
26
|
+
* audit_log — local-SQLite name
|
|
27
|
+
* _blamejs_audit_log — external-db name
|
|
22
28
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* dialects until a MySQL adapter ships.
|
|
29
|
+
* `b.frameworkSchema.tableName` exposes the mapping so write-
|
|
30
|
+
* dispatch code (`cluster-storage.js`) can use a single name
|
|
31
|
+
* reference. `b.frameworkSchema.LOCAL_TO_EXTERNAL` is the frozen
|
|
32
|
+
* read-only mapping object.
|
|
28
33
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* but they're not load-bearing for the threat model. Operators
|
|
38
|
-
* who want triggers add them per their dialect's syntax.
|
|
34
|
+
* Append-only WORM enforcement: `ensureSchema` installs BEFORE
|
|
35
|
+
* DELETE / BEFORE UPDATE triggers on `audit_log`, `consent_log`,
|
|
36
|
+
* and `audit_checkpoints` — Postgres via plpgsql RAISE EXCEPTION
|
|
37
|
+
* functions, SQLite via `RAISE(ABORT, ...)`. Idempotent across
|
|
38
|
+
* reboots; any operator-applied DROP TRIGGER is restored on the
|
|
39
|
+
* next ensureSchema pass. MySQL is not currently supported —
|
|
40
|
+
* operators on MySQL must run on Postgres or SQLite until a MySQL
|
|
41
|
+
* adapter ships.
|
|
39
42
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* frameworkSchema.tableName(localName) external table name lookup
|
|
43
|
-
* frameworkSchema.LOCAL_TO_EXTERNAL mapping (read-only)
|
|
44
|
-
* frameworkSchema.FrameworkSchemaError error class
|
|
43
|
+
* @card
|
|
44
|
+
* Framework-defined SQL schema (audit / sessions / api_keys / cache / break-glass / scheduler-ticks / pubsub / rate-limit / seeders / etc.) — declarative, migration-aware, and dialect-portable across Postgres and SQLite.
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
var externalDb = require("./external-db");
|
|
@@ -138,6 +138,29 @@ var LOCAL_TO_EXTERNAL = Object.freeze({
|
|
|
138
138
|
_blamejs_break_glass_grants: "_blamejs_break_glass_grants",
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* @primitive b.frameworkSchema.tableName
|
|
143
|
+
* @signature b.frameworkSchema.tableName(localName)
|
|
144
|
+
* @since 0.5.0
|
|
145
|
+
* @status stable
|
|
146
|
+
* @related b.frameworkSchema.ensureSchema
|
|
147
|
+
*
|
|
148
|
+
* Translate a local-SQLite table name into the external-db name. The
|
|
149
|
+
* mapping is the frozen `LOCAL_TO_EXTERNAL` object — tables that already
|
|
150
|
+
* carry the `_blamejs_` prefix locally pass through unchanged. Cluster
|
|
151
|
+
* write-dispatch code uses this lookup so the same SQL works against
|
|
152
|
+
* both backends without per-call branching.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* b.frameworkSchema.tableName("audit_log");
|
|
156
|
+
* // → "_blamejs_audit_log"
|
|
157
|
+
*
|
|
158
|
+
* b.frameworkSchema.tableName("_blamejs_sessions");
|
|
159
|
+
* // → "_blamejs_sessions"
|
|
160
|
+
*
|
|
161
|
+
* b.frameworkSchema.tableName("operator_app_table");
|
|
162
|
+
* // → "operator_app_table"
|
|
163
|
+
*/
|
|
141
164
|
function tableName(localName) {
|
|
142
165
|
if (Object.prototype.hasOwnProperty.call(LOCAL_TO_EXTERNAL, localName)) {
|
|
143
166
|
return LOCAL_TO_EXTERNAL[localName];
|
|
@@ -660,6 +683,42 @@ function _breakGlassGrantsDDL(dialect) {
|
|
|
660
683
|
|
|
661
684
|
// ---- ensureSchema ----
|
|
662
685
|
|
|
686
|
+
/**
|
|
687
|
+
* @primitive b.frameworkSchema.ensureSchema
|
|
688
|
+
* @signature b.frameworkSchema.ensureSchema(opts)
|
|
689
|
+
* @since 0.5.0
|
|
690
|
+
* @status stable
|
|
691
|
+
* @related b.frameworkSchema.tableName, b.externalDb.init, b.audit
|
|
692
|
+
*
|
|
693
|
+
* Create every framework-owned table + index in the operator's
|
|
694
|
+
* external database, then install append-only WORM triggers on
|
|
695
|
+
* `_blamejs_audit_log`, `_blamejs_consent_log`, and
|
|
696
|
+
* `_blamejs_audit_checkpoints`. Idempotent: every DDL uses
|
|
697
|
+
* `IF NOT EXISTS` and re-running is safe across reboots.
|
|
698
|
+
*
|
|
699
|
+
* Returns `{ tables }` with the set of CREATE TABLE names emitted
|
|
700
|
+
* so the operator can confirm the expected surface landed.
|
|
701
|
+
*
|
|
702
|
+
* Throws `FrameworkSchemaError("framework-schema/invalid-config")`
|
|
703
|
+
* when `externalDbBackend` is missing and
|
|
704
|
+
* `FrameworkSchemaError("framework-schema/unsupported-dialect")`
|
|
705
|
+
* when `dialect` is anything other than `postgres` or `sqlite`.
|
|
706
|
+
*
|
|
707
|
+
* @opts
|
|
708
|
+
* externalDbBackend: string, // backend name registered with b.externalDb (required)
|
|
709
|
+
* dialect: "postgres"|"sqlite", // default: "postgres"
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* try {
|
|
713
|
+
* var report = await b.frameworkSchema.ensureSchema({
|
|
714
|
+
* externalDbBackend: "primary",
|
|
715
|
+
* dialect: "postgres",
|
|
716
|
+
* });
|
|
717
|
+
* report.tables[0]; // → "_blamejs_audit_log"
|
|
718
|
+
* } catch (e) {
|
|
719
|
+
* e.code; // → "framework-schema/unsupported-dialect"
|
|
720
|
+
* }
|
|
721
|
+
*/
|
|
663
722
|
async function ensureSchema(opts) {
|
|
664
723
|
if (!opts || !opts.externalDbBackend) {
|
|
665
724
|
throw new FrameworkSchemaError(
|
|
@@ -706,9 +765,78 @@ async function ensureSchema(opts) {
|
|
|
706
765
|
}
|
|
707
766
|
created.push(d.create.match(/CREATE TABLE IF NOT EXISTS\s+(\S+)/)[1]);
|
|
708
767
|
}
|
|
768
|
+
|
|
769
|
+
// D-M11 — append-only WORM enforcement on audit_log / consent_log /
|
|
770
|
+
// audit_checkpoints in cluster mode. Local-SQLite path already
|
|
771
|
+
// installs CREATE TRIGGER IF NOT EXISTS via lib/db.js's
|
|
772
|
+
// _installAppendOnlyTriggers; Postgres needs equivalent rules
|
|
773
|
+
// (BEFORE-row triggers raising an exception) so a privileged
|
|
774
|
+
// cluster-side actor with the framework role can't DELETE / UPDATE
|
|
775
|
+
// a row out from under the chain. The chain integrity check still
|
|
776
|
+
// catches it at next boot, but the trigger is the in-band defense.
|
|
777
|
+
await _installWormTriggers(opts.externalDbBackend, dialect);
|
|
778
|
+
|
|
709
779
|
return { tables: created };
|
|
710
780
|
}
|
|
711
781
|
|
|
782
|
+
// D-M11 — WORM enforcement helper. Idempotent: rebuilding triggers
|
|
783
|
+
// per boot is cheap and any operator-applied DROP TRIGGER is restored
|
|
784
|
+
// at the next ensureSchema pass.
|
|
785
|
+
async function _installWormTriggers(backend, dialect) {
|
|
786
|
+
var wormTables = [
|
|
787
|
+
LOCAL_TO_EXTERNAL.audit_log,
|
|
788
|
+
LOCAL_TO_EXTERNAL.consent_log,
|
|
789
|
+
LOCAL_TO_EXTERNAL.audit_checkpoints,
|
|
790
|
+
];
|
|
791
|
+
for (var i = 0; i < wormTables.length; i++) {
|
|
792
|
+
var t = wormTables[i];
|
|
793
|
+
if (dialect === "postgres") {
|
|
794
|
+
// Per-table trigger function. Postgres rejects the statement
|
|
795
|
+
// with a SQLSTATE that bubbles up as a query-failure audit row.
|
|
796
|
+
var fnName = t + "_worm_block";
|
|
797
|
+
await externalDb.query(
|
|
798
|
+
"CREATE OR REPLACE FUNCTION " + fnName + "() RETURNS trigger AS $$ " +
|
|
799
|
+
"BEGIN RAISE EXCEPTION '" + t + " is append-only — % prohibited', TG_OP " +
|
|
800
|
+
"USING ERRCODE = '0A000'; END; $$ LANGUAGE plpgsql",
|
|
801
|
+
[], { backend: backend }
|
|
802
|
+
);
|
|
803
|
+
await externalDb.query(
|
|
804
|
+
"DROP TRIGGER IF EXISTS no_delete_" + t + " ON " + t,
|
|
805
|
+
[], { backend: backend }
|
|
806
|
+
);
|
|
807
|
+
await externalDb.query(
|
|
808
|
+
"CREATE TRIGGER no_delete_" + t + " BEFORE DELETE ON " + t +
|
|
809
|
+
" FOR EACH ROW EXECUTE FUNCTION " + fnName + "()",
|
|
810
|
+
[], { backend: backend }
|
|
811
|
+
);
|
|
812
|
+
await externalDb.query(
|
|
813
|
+
"DROP TRIGGER IF EXISTS no_update_" + t + " ON " + t,
|
|
814
|
+
[], { backend: backend }
|
|
815
|
+
);
|
|
816
|
+
await externalDb.query(
|
|
817
|
+
"CREATE TRIGGER no_update_" + t + " BEFORE UPDATE ON " + t +
|
|
818
|
+
" FOR EACH ROW EXECUTE FUNCTION " + fnName + "()",
|
|
819
|
+
[], { backend: backend }
|
|
820
|
+
);
|
|
821
|
+
} else {
|
|
822
|
+
// SQLite cluster path. CREATE TRIGGER IF NOT EXISTS matches the
|
|
823
|
+
// local-SQLite shape installed by lib/db.js.
|
|
824
|
+
await externalDb.query(
|
|
825
|
+
'CREATE TRIGGER IF NOT EXISTS "no_delete_' + t + '" ' +
|
|
826
|
+
'BEFORE DELETE ON "' + t + '" ' +
|
|
827
|
+
"BEGIN SELECT RAISE(ABORT, '" + t + " is append-only — DELETE prohibited'); END",
|
|
828
|
+
[], { backend: backend }
|
|
829
|
+
);
|
|
830
|
+
await externalDb.query(
|
|
831
|
+
'CREATE TRIGGER IF NOT EXISTS "no_update_' + t + '" ' +
|
|
832
|
+
'BEFORE UPDATE ON "' + t + '" ' +
|
|
833
|
+
"BEGIN SELECT RAISE(ABORT, '" + t + " is append-only — UPDATE prohibited'); END",
|
|
834
|
+
[], { backend: backend }
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
712
840
|
module.exports = {
|
|
713
841
|
ensureSchema: ensureSchema,
|
|
714
842
|
tableName: tableName,
|