@blamejs/core 0.14.27 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +2 -2
  3. package/index.js +4 -0
  4. package/lib/ai-content-detect.js +9 -10
  5. package/lib/api-key.js +107 -74
  6. package/lib/atomic-file.js +29 -1
  7. package/lib/audit-chain.js +47 -11
  8. package/lib/audit-sign.js +77 -2
  9. package/lib/audit-tools.js +79 -51
  10. package/lib/audit.js +218 -100
  11. package/lib/backup/index.js +13 -10
  12. package/lib/break-glass.js +202 -144
  13. package/lib/cache.js +174 -105
  14. package/lib/chain-writer.js +38 -16
  15. package/lib/cli.js +19 -14
  16. package/lib/cluster-provider-db.js +130 -104
  17. package/lib/cluster-storage.js +119 -22
  18. package/lib/cluster.js +119 -71
  19. package/lib/compliance.js +22 -0
  20. package/lib/consent.js +73 -24
  21. package/lib/constants.js +16 -11
  22. package/lib/crypto-field.js +387 -91
  23. package/lib/db-declare-row-policy.js +35 -22
  24. package/lib/db-file-lifecycle.js +3 -2
  25. package/lib/db-query.js +497 -255
  26. package/lib/db-schema.js +209 -44
  27. package/lib/db.js +176 -95
  28. package/lib/external-db-migrate.js +229 -139
  29. package/lib/external-db.js +25 -15
  30. package/lib/framework-error.js +11 -0
  31. package/lib/framework-files.js +73 -0
  32. package/lib/framework-schema.js +695 -394
  33. package/lib/gate-contract.js +596 -1
  34. package/lib/guard-agent-registry.js +26 -44
  35. package/lib/guard-all.js +1 -0
  36. package/lib/guard-auth.js +42 -112
  37. package/lib/guard-cidr.js +33 -154
  38. package/lib/guard-csv.js +46 -113
  39. package/lib/guard-domain.js +34 -157
  40. package/lib/guard-dsn.js +27 -43
  41. package/lib/guard-email.js +47 -69
  42. package/lib/guard-envelope.js +19 -32
  43. package/lib/guard-event-bus-payload.js +24 -42
  44. package/lib/guard-event-bus-topic.js +25 -43
  45. package/lib/guard-filename.js +42 -106
  46. package/lib/guard-graphql.js +42 -123
  47. package/lib/guard-html.js +53 -108
  48. package/lib/guard-idempotency-key.js +24 -42
  49. package/lib/guard-image.js +46 -103
  50. package/lib/guard-imap-command.js +18 -32
  51. package/lib/guard-jmap.js +16 -30
  52. package/lib/guard-json.js +38 -108
  53. package/lib/guard-jsonpath.js +38 -171
  54. package/lib/guard-jwt.js +49 -179
  55. package/lib/guard-list-id.js +25 -41
  56. package/lib/guard-list-unsubscribe.js +27 -43
  57. package/lib/guard-mail-compose.js +24 -42
  58. package/lib/guard-mail-move.js +26 -44
  59. package/lib/guard-mail-query.js +28 -46
  60. package/lib/guard-mail-reply.js +24 -42
  61. package/lib/guard-mail-sieve.js +24 -42
  62. package/lib/guard-managesieve-command.js +17 -31
  63. package/lib/guard-markdown.js +37 -104
  64. package/lib/guard-message-id.js +26 -45
  65. package/lib/guard-mime.js +39 -151
  66. package/lib/guard-oauth.js +54 -135
  67. package/lib/guard-pdf.js +45 -101
  68. package/lib/guard-pop3-command.js +21 -31
  69. package/lib/guard-posture-chain.js +24 -42
  70. package/lib/guard-regex.js +33 -107
  71. package/lib/guard-saga-config.js +24 -42
  72. package/lib/guard-shell.js +42 -172
  73. package/lib/guard-smtp-command.js +48 -54
  74. package/lib/guard-snapshot-envelope.js +24 -42
  75. package/lib/guard-sql.js +1491 -0
  76. package/lib/guard-stream-args.js +24 -43
  77. package/lib/guard-svg.js +47 -65
  78. package/lib/guard-template.js +35 -172
  79. package/lib/guard-tenant-id.js +26 -45
  80. package/lib/guard-time.js +32 -154
  81. package/lib/guard-trace-context.js +25 -44
  82. package/lib/guard-uuid.js +32 -153
  83. package/lib/guard-xml.js +38 -113
  84. package/lib/guard-yaml.js +51 -163
  85. package/lib/http-client.js +14 -0
  86. package/lib/inbox.js +120 -107
  87. package/lib/legal-hold.js +107 -50
  88. package/lib/log-stream-cloudwatch.js +47 -31
  89. package/lib/log-stream-otlp.js +32 -18
  90. package/lib/mail-crypto-smime.js +2 -6
  91. package/lib/mail-greylist.js +2 -6
  92. package/lib/mail-helo.js +2 -6
  93. package/lib/mail-journal.js +85 -64
  94. package/lib/mail-rbl.js +2 -6
  95. package/lib/mail-scan.js +2 -6
  96. package/lib/mail-spam-score.js +2 -6
  97. package/lib/mail-store.js +287 -154
  98. package/lib/middleware/fetch-metadata.js +17 -7
  99. package/lib/middleware/idempotency-key.js +54 -38
  100. package/lib/middleware/rate-limit.js +102 -32
  101. package/lib/middleware/security-headers.js +21 -5
  102. package/lib/migrations.js +108 -66
  103. package/lib/network-heartbeat.js +7 -0
  104. package/lib/nonce-store.js +31 -9
  105. package/lib/object-store/azure-blob-bucket-ops.js +9 -4
  106. package/lib/object-store/azure-blob.js +31 -3
  107. package/lib/object-store/sigv4.js +10 -0
  108. package/lib/outbox.js +136 -82
  109. package/lib/pqc-agent.js +44 -0
  110. package/lib/pubsub-cluster.js +42 -20
  111. package/lib/queue-local.js +202 -139
  112. package/lib/queue-redis.js +9 -1
  113. package/lib/queue-sqs.js +6 -0
  114. package/lib/retention.js +82 -39
  115. package/lib/safe-dns.js +29 -45
  116. package/lib/safe-ical.js +18 -33
  117. package/lib/safe-icap.js +27 -43
  118. package/lib/safe-sieve.js +21 -40
  119. package/lib/safe-sql.js +124 -3
  120. package/lib/safe-vcard.js +18 -33
  121. package/lib/scheduler.js +35 -12
  122. package/lib/seeders.js +122 -74
  123. package/lib/session-stores.js +42 -14
  124. package/lib/session.js +109 -72
  125. package/lib/sql.js +3885 -0
  126. package/lib/static.js +45 -7
  127. package/lib/subject.js +55 -17
  128. package/lib/vault/index.js +3 -2
  129. package/lib/vault/passphrase-ops.js +3 -2
  130. package/lib/vault/rotate.js +104 -64
  131. package/lib/vendor-data.js +2 -0
  132. package/lib/websocket.js +16 -0
  133. package/package.json +1 -1
  134. package/sbom.cdx.json +6 -6
@@ -50,7 +50,7 @@ var bCrypto = require("./crypto");
50
50
  var lazyRequire = require("./lazy-require");
51
51
  var safeAsync = require("./safe-async");
52
52
  var validateOpts = require("./validate-opts");
53
- var { GateContractError } = require("./framework-error");
53
+ var { GateContractError, defineClass } = require("./framework-error");
54
54
 
55
55
  var observability = lazyRequire(function () { return require("./observability"); });
56
56
  var compliance = lazyRequire(function () { return require("./compliance"); });
@@ -1102,6 +1102,153 @@ function lookupCompliancePosture(name, postures, errorFactory, codePrefix) {
1102
1102
  return Object.assign({}, postures[name]);
1103
1103
  }
1104
1104
 
1105
+ // "GuardCidrError" -> "guardCidr" — the guard's audit/message identity, derived
1106
+ // once from its error class name. Used for the default gate's audit/metric
1107
+ // prefix AND the profile resolver's error message, so neither re-cases the name.
1108
+ function _guardLabelFromError(ErrorClass) {
1109
+ var n = String(ErrorClass.name).replace(/Error$/, "");
1110
+ return n.charAt(0).toLowerCase() + n.slice(1);
1111
+ }
1112
+
1113
+ /**
1114
+ * @primitive b.gateContract.makeProfileResolver
1115
+ * @signature b.gateContract.makeProfileResolver(cfg)
1116
+ * @since 0.15.0
1117
+ * @status stable
1118
+ * @related b.gateContract.makeProfileBuilder, b.gateContract.lookupCompliancePosture
1119
+ *
1120
+ * Closes over a guard's profile config and returns a `resolveProfile(opts)`
1121
+ * function: maps `opts.posture` through the compliance-posture table, else
1122
+ * falls back to `opts.profile || cfg.defaults`, validates the name against
1123
+ * `cfg.profiles`, and throws `cfg.errorClass.factory(cfg.codePrefix +
1124
+ * "/bad-profile")` on an unknown name. The sibling of `makeProfileBuilder` /
1125
+ * `makeRulePackLoader` / `lookupCompliancePosture` for the resolution step —
1126
+ * every `defineParser`-shaped line-protocol / mail / agent guard reuses it
1127
+ * instead of re-declaring an identical `_resolveProfile`.
1128
+ *
1129
+ * @opts
1130
+ * profiles: object, // the guard's PROFILES map; required
1131
+ * postures: object, // COMPLIANCE_POSTURES (posture -> profile name)
1132
+ * defaults: string, // fallback profile name when no posture/profile given
1133
+ * errorClass: function, // the guard's FrameworkError subclass
1134
+ * codePrefix: string, // error-code namespace (e.g. "mail-compose")
1135
+ * byObject: boolean, // true -> return the profile config object, not its name
1136
+ *
1137
+ * @example
1138
+ * var resolveProfile = b.gateContract.makeProfileResolver({
1139
+ * profiles: PROFILES, postures: COMPLIANCE_POSTURES,
1140
+ * defaults: "strict", errorClass: GuardMailComposeError,
1141
+ * codePrefix: "mail-compose",
1142
+ * });
1143
+ * resolveProfile({ posture: "hipaa" }); // → "strict"
1144
+ */
1145
+ function makeProfileResolver(cfg) {
1146
+ var profiles = cfg.profiles;
1147
+ var postures = cfg.postures;
1148
+ var dft = cfg.defaults;
1149
+ var ErrorClass = cfg.errorClass;
1150
+ var codePrefix = cfg.codePrefix;
1151
+ var byObject = cfg.byObject === true;
1152
+ var label = _guardLabelFromError(ErrorClass);
1153
+ return function resolveProfile(opts) {
1154
+ opts = opts || {};
1155
+ if (opts.posture && postures && postures[opts.posture]) {
1156
+ var pn = postures[opts.posture];
1157
+ return byObject ? profiles[pn] : pn;
1158
+ }
1159
+ var p = opts.profile || dft;
1160
+ if (!profiles[p]) {
1161
+ throw ErrorClass.factory(codePrefix + "/bad-profile",
1162
+ label + ": unknown profile '" + p + "' (use " +
1163
+ Object.keys(profiles).join(" / ") + ")");
1164
+ }
1165
+ return byObject ? profiles[p] : p;
1166
+ };
1167
+ }
1168
+
1169
+ /**
1170
+ * @primitive b.gateContract.throwOnRefusalSeverity
1171
+ * @signature b.gateContract.throwOnRefusalSeverity(issues, cfg)
1172
+ * @since 0.15.0
1173
+ * @status stable
1174
+ * @related b.gateContract.aggregateIssues, b.gateContract.makeProfileResolver
1175
+ *
1176
+ * Throw on the first critical/high-severity issue in a detector's issue
1177
+ * list — the refusal step every guard `sanitize` runs after detection
1178
+ * (sanitize can serve a clean value but never repair a critical/high
1179
+ * finding). Builds the guard's error via `cfg.errorClass.factory` with code
1180
+ * `issue.ruleId || (cfg.codePrefix + ".refused")` and message
1181
+ * `guard<Name>.<op>: <issue.snippet>` (op default `"sanitize"`; the guard
1182
+ * identity derives from the error class name). The throw sibling of
1183
+ * `aggregateIssues` (which returns `{ ok, issues }` instead of throwing) —
1184
+ * replaces the per-guard hand-rolled severity-gating loop.
1185
+ *
1186
+ * @opts
1187
+ * errorClass: function, // the guard's FrameworkError subclass; required
1188
+ * codePrefix: string, // error-code namespace; the `.refused` fallback code
1189
+ * op: string, // operation name in the message (default "sanitize")
1190
+ * severities: string[], // refusal severities (default ["critical","high"])
1191
+ *
1192
+ * @example
1193
+ * var issues = detect(input, opts);
1194
+ * b.gateContract.throwOnRefusalSeverity(issues, {
1195
+ * errorClass: GuardCidrError, codePrefix: "cidr",
1196
+ * });
1197
+ * // throws GuardCidrError(ruleId || "cidr.refused", "guardCidr.sanitize: " + snippet)
1198
+ * // on the first critical/high issue
1199
+ */
1200
+ function throwOnRefusalSeverity(issues, cfg) {
1201
+ var errFactory = cfg.errorClass.factory;
1202
+ var prefix = _guardLabelFromError(cfg.errorClass) + "." + (cfg.op || "sanitize");
1203
+ var fallback = cfg.codePrefix + ".refused";
1204
+ // Default refuses critical + high; cfg.severities narrows it (e.g.
1205
+ // ["critical"] for guards that strip high-severity findings but refuse
1206
+ // only unrepairable critical shapes — email / markdown / xml / yaml).
1207
+ var severities = cfg.severities || ["critical", "high"];
1208
+ for (var i = 0; i < issues.length; i += 1) {
1209
+ var iss = issues[i];
1210
+ if (severities.indexOf(iss.severity) !== -1) {
1211
+ throw errFactory(iss.ruleId || fallback, prefix + ": " + iss.snippet);
1212
+ }
1213
+ }
1214
+ }
1215
+
1216
+ /**
1217
+ * @primitive b.gateContract.ALL_STRICT_POSTURES
1218
+ * @signature b.gateContract.ALL_STRICT_POSTURES
1219
+ * @since 0.15.0
1220
+ * @status stable
1221
+ * @compliance hipaa, pci-dss, gdpr, soc2
1222
+ * @related b.gateContract.lookupCompliancePosture, b.gateContract.makeProfileBuilder
1223
+ *
1224
+ * Canonical strict-all `COMPLIANCE_POSTURES` map every command/parser
1225
+ * guard composes. Maps each of the four baseline regulatory postures —
1226
+ * `hipaa` / `pci-dss` / `gdpr` / `soc2` — onto the guard's `strict`
1227
+ * profile name. Guards whose four postures all resolve to `strict`
1228
+ * (the command/protocol validators: POP3 / IMAP / SMTP / ManageSieve
1229
+ * commands, mail-compose / query / sieve / move / reply, the envelope
1230
+ * and event-bus shapes, the mail pipeline scorers, and the
1231
+ * `safe-*` line-protocol parsers) reference this single frozen object
1232
+ * instead of re-declaring it. Guards that overlay per-posture
1233
+ * byte-limits or redaction flags (the content guards: CSV / HTML /
1234
+ * JSON / XML / YAML / JWT / OAuth / template, etc.) keep their own
1235
+ * posture map and do not compose this.
1236
+ *
1237
+ * Frozen once and shared by reference: every consumer reads it through
1238
+ * its own `COMPLIANCE_POSTURES` binding and never mutates it.
1239
+ *
1240
+ * @example
1241
+ * var COMPLIANCE_POSTURES = b.gateContract.ALL_STRICT_POSTURES;
1242
+ * COMPLIANCE_POSTURES.hipaa; // → "strict"
1243
+ * Object.isFrozen(COMPLIANCE_POSTURES); // → true
1244
+ */
1245
+ var ALL_STRICT_POSTURES = Object.freeze({
1246
+ hipaa: "strict",
1247
+ "pci-dss": "strict",
1248
+ gdpr: "strict",
1249
+ soc2: "strict",
1250
+ });
1251
+
1105
1252
  /**
1106
1253
  * @primitive b.gateContract.makeRulePackLoader
1107
1254
  * @signature b.gateContract.makeRulePackLoader(errorClass, codePrefix)
@@ -1680,8 +1827,453 @@ function composeHooks(hooks) {
1680
1827
  };
1681
1828
  }
1682
1829
 
1830
+ // ---- Guard-module factories ----
1831
+ //
1832
+ // Every b.guard* primitive of the gate-bearing kinds (content / filename
1833
+ // / identifier) hand-wires the SAME export surface: an error class, a
1834
+ // resolveProfileAndPosture-backed _resolveOpts, a buildGuardGate-backed
1835
+ // gate, a makeProfileBuilder-backed buildProfile, a
1836
+ // lookupCompliancePosture-backed compliancePosture, a makeRulePackLoader-
1837
+ // backed loadRulePack, and a frozen module.exports carrying the
1838
+ // guard-* registry fields (NAME / KIND / MIME_TYPES / EXTENSIONS /
1839
+ // PROFILES / DEFAULTS / COMPLIANCE_POSTURES / INTEGRATION_FIXTURES) plus
1840
+ // the per-guard inspection surface (validate / sanitize / gate). They
1841
+ // differ only in the per-guard inspection LOGIC + the PROFILES /
1842
+ // COMPLIANCE_POSTURES / DEFAULTS tables. `defineGuard` assembles the
1843
+ // boilerplate; the spec injects the logic and the tables.
1844
+ //
1845
+ // `defineParser` is the sibling for the minimal command / line-protocol
1846
+ // / safe-* parser shape — the guards whose four postures all resolve to
1847
+ // `strict` (ALL_STRICT_POSTURES) and whose surface is a self-contained
1848
+ // `validate` / `parse` plus a `compliancePosture(name)` that returns the
1849
+ // effective PROFILE NAME (or null) rather than an overlay clone. Those
1850
+ // guards carry no gate / buildProfile / loadRulePack, so forcing them
1851
+ // through `defineGuard` would be a leaky abstraction.
1852
+
1853
+ // _KIND_CTX_FIELDS — per-KIND ordered list of ctx field names a
1854
+ // buildGuardGate-backed default gate reads, mirroring the hand-written
1855
+ // gate bodies: filename reads ctx.filename || ctx.name, identifier reads
1856
+ // ctx.identifier || ctx.token || ctx.jwt, command reads ctx.line ||
1857
+ // ctx.command. content has no entry — it falls through to
1858
+ // extractBytesAsText (the ctx.bytes string/Buffer normalizer).
1859
+ var _KIND_CTX_FIELDS = Object.freeze({
1860
+ filename: ["filename", "name"],
1861
+ identifier: ["identifier", "token", "jwt"],
1862
+ command: ["line", "command"],
1863
+ });
1864
+
1865
+ // override (when given) replaces the per-KIND field table — lets a guard whose
1866
+ // gate is the standard chain but reads a custom ctx field take the default gate.
1867
+ function _ctxValueForKind(kind, ctx, override) {
1868
+ ctx = ctx || {};
1869
+ var fields = override || _KIND_CTX_FIELDS[kind];
1870
+ if (!fields) return extractBytesAsText(ctx); // content (default)
1871
+ for (var i = 0; i < fields.length; i += 1) {
1872
+ if (ctx[fields[i]]) return ctx[fields[i]];
1873
+ }
1874
+ return "";
1875
+ }
1876
+
1877
+ /**
1878
+ * @primitive b.gateContract.defineGuard
1879
+ * @signature b.gateContract.defineGuard(spec)
1880
+ * @since 0.15.0
1881
+ * @status stable
1882
+ * @related b.gateContract.defineParser, b.gateContract.buildGuardGate, b.gateContract.resolveProfileAndPosture
1883
+ *
1884
+ * Assemble a complete `b.guard*` module from a spec. Mints the per-guard
1885
+ * error class (via `framework-error.defineClass`, or accepts a supplied
1886
+ * `errorClass`), wires `resolveProfileAndPosture` / `buildGuardGate` /
1887
+ * `makeProfileBuilder` / `lookupCompliancePosture` / `makeRulePackLoader`,
1888
+ * and returns the frozen module.exports object every guard ships —
1889
+ * `NAME` / `KIND` / `PROFILES` / `DEFAULTS` / `COMPLIANCE_POSTURES` /
1890
+ * `INTEGRATION_FIXTURES` / `validate` / `sanitize?` / `gate?` /
1891
+ * `buildProfile` / `compliancePosture` / `loadRulePack` plus the spec's
1892
+ * `extra` exports (verb tables, `escapeCell`, `schema`, `kidSafe`, …) and
1893
+ * the error class under its own name.
1894
+ *
1895
+ * The per-guard inspection logic is INJECTED, not abstracted: `validate`
1896
+ * / `sanitize` / `gate` are spec functions that close over the resolved
1897
+ * opts. A guard whose `gate` body is the standard
1898
+ * serve→audit-only→sanitize→refuse chain can omit `spec.gate` and take
1899
+ * the factory default (built from `spec.validate` + `spec.sanitize` per
1900
+ * KIND); a guard with a bespoke gate (CSV's sanitize-reparse-reserialize,
1901
+ * filename's per-policy canSanitize matrix) passes its own. Behavior is
1902
+ * preserved byte-for-byte because the genuinely-divergent code stays
1903
+ * verbatim in the spec — the factory only removes the wiring every guard
1904
+ * copies.
1905
+ *
1906
+ * @opts
1907
+ * name: string, // NAME (e.g. "csv"); required
1908
+ * kind: string, // "content"|"filename"|"identifier"|"command" for the default gate; any non-empty label with a bespoke spec.gate; required
1909
+ * errCodePrefix: string, // error-code namespace (default name)
1910
+ * errorName: string, // defineClass name (mutually exclusive with errorClass)
1911
+ * errorClass: function, // pre-built FrameworkError subclass
1912
+ * profiles: object, // PROFILES (must include strict/balanced/permissive); required
1913
+ * defaults: object, // DEFAULTS baseline (default profiles.strict)
1914
+ * postures: object, // COMPLIANCE_POSTURES (default ALL_STRICT_POSTURES)
1915
+ * mimeTypes: string[], // content guards only
1916
+ * extensions: string[], // content guards only
1917
+ * integrationFixtures: object, // INTEGRATION_FIXTURES (consumed by host harness)
1918
+ * validate: function, // (input, resolvedOpts) -> { ok, issues }; required
1919
+ * sanitize: function, // (input, resolvedOpts) -> cleaned (optional)
1920
+ * gate: function, // (resolvedOpts) -> async (ctx) -> decision (optional; default built per kind)
1921
+ * ctxFields: string[], // ordered ctx field names the default gate reads (overrides the per-KIND table; e.g. ["identifier","cidr"])
1922
+ * defaultGateCheck: function, // override the default gate's per-ctx check
1923
+ * extra: object, // additional exports merged verbatim into module.exports
1924
+ *
1925
+ * @example
1926
+ * module.exports = b.gateContract.defineGuard({
1927
+ * name: "csv", kind: "content", errorClass: GuardCsvError,
1928
+ * profiles: PROFILES, defaults: DEFAULTS, postures: COMPLIANCE_POSTURES,
1929
+ * mimeTypes: ["text/csv"], extensions: [".csv"],
1930
+ * integrationFixtures: INTEGRATION_FIXTURES,
1931
+ * validate: validate, sanitize: sanitize, gate: gate,
1932
+ * extra: { serialize: serialize, escapeCell: escapeCell, schema: schema },
1933
+ * });
1934
+ */
1935
+ function defineGuard(spec) {
1936
+ validateOpts.requireObject(spec, "gateContract.defineGuard", GateContractError);
1937
+ validateOpts.requireNonEmptyString(spec.name, "gateContract.defineGuard: name",
1938
+ GateContractError, "gate-contract/bad-opt");
1939
+ validateOpts.requireNonEmptyString(spec.kind, "gateContract.defineGuard: kind",
1940
+ GateContractError, "gate-contract/bad-opt");
1941
+ // The four known kinds drive the default gate's ctx-field dispatch
1942
+ // (_ctxValueForKind). A guard with a bespoke spec.gate reads its own ctx
1943
+ // fields, so any non-empty kind is allowed there — the kind is then just
1944
+ // the KIND export label (e.g. "oauth-flow" / "graphql-request" / "sql" /
1945
+ // "metadata"). A custom kind WITHOUT a bespoke gate is refused, because
1946
+ // the default gate could not dispatch it to the right ctx field.
1947
+ if (["content", "filename", "identifier", "command"].indexOf(spec.kind) === -1 &&
1948
+ typeof spec.gate !== "function") {
1949
+ throw _err("gate-contract/bad-opt",
1950
+ "defineGuard: kind must be content|filename|identifier|command for the " +
1951
+ "default gate, got " + JSON.stringify(spec.kind) +
1952
+ " — pass spec.gate for a custom kind (the bespoke gate reads its own ctx fields)");
1953
+ }
1954
+ validateOpts.requireObject(spec.profiles, "gateContract.defineGuard: profiles",
1955
+ GateContractError);
1956
+ if (typeof spec.validate !== "function") {
1957
+ throw _err("gate-contract/bad-opt", "defineGuard: validate must be a function");
1958
+ }
1959
+ if (spec.errorClass && spec.errorName) {
1960
+ throw _err("gate-contract/bad-opt",
1961
+ "defineGuard: pass errorClass OR errorName, not both");
1962
+ }
1963
+
1964
+ var prefix = spec.errCodePrefix || spec.name;
1965
+ var ErrorClass = spec.errorClass ||
1966
+ defineClass(spec.errorName || ("Guard" +
1967
+ spec.name.charAt(0).toUpperCase() + spec.name.slice(1) + "Error"),
1968
+ { alwaysPermanent: true });
1969
+ var profiles = spec.profiles;
1970
+ var defaults = spec.defaults || profiles.strict || {};
1971
+ var postures = spec.postures || ALL_STRICT_POSTURES;
1972
+
1973
+ var buildProfileFn = makeProfileBuilder(profiles);
1974
+ function compliancePostureFn(name) {
1975
+ return lookupCompliancePosture(name, postures, ErrorClass.factory, prefix);
1976
+ }
1977
+ var rulePacks = makeRulePackLoader(ErrorClass, prefix);
1978
+
1979
+ // spec.ctxFields (ordered field names) overrides the per-KIND table that
1980
+ // the default gate's _ctxValueForKind reads — lets a guard whose gate is the
1981
+ // standard chain but reads a custom ctx field (e.g. ctx.cidr) drop its
1982
+ // bespoke gate and take the default. null -> _ctxValueForKind uses the
1983
+ // per-KIND table.
1984
+ var ctxFields = Array.isArray(spec.ctxFields) ? spec.ctxFields.slice() : null;
1985
+ // Gate identity is surfaced in audit events / metric counters / cache keys.
1986
+ // Preserve the "guard<Name>:profile" naming the hand-written gates used so
1987
+ // moving a guard onto the default gate does not rename its audit/metric
1988
+ // stream (e.g. "guardCidr:strict"), via the shared error-name derivation.
1989
+ var gateNamePrefix = _guardLabelFromError(ErrorClass);
1990
+
1991
+ // Default gate — the standard serve→audit-only→refuse chain, dispatched
1992
+ // to the right ctx field by KIND (or spec.ctxFields). Guards with a bespoke
1993
+ // gate pass spec.gate; guards whose gate is the standard chain take this
1994
+ // default.
1995
+ // Raw opts pass straight through to spec.validate and buildGuardGate —
1996
+ // matching the hand-written gates, whose validate resolves profile/posture
1997
+ // internally (validate calls its own _resolveOpts). Pre-resolving here
1998
+ // would double-resolve.
1999
+ function defaultGate(opts) {
2000
+ opts = opts || {};
2001
+ var perCtx = spec.defaultGateCheck || function (ctx) {
2002
+ var value = _ctxValueForKind(spec.kind, ctx, ctxFields);
2003
+ if (!value) return { ok: true, action: "serve" };
2004
+ var rv = spec.validate(value, opts);
2005
+ if (!rv.issues || rv.issues.length === 0) return { ok: true, action: "serve" };
2006
+ var hasBlocking = rv.issues.some(function (i) {
2007
+ return i.severity === "critical" || i.severity === "high";
2008
+ });
2009
+ if (!hasBlocking) return { ok: true, action: "audit-only", issues: rv.issues };
2010
+ return { ok: false, action: "refuse", issues: rv.issues };
2011
+ };
2012
+ return buildGuardGate(
2013
+ opts.name || (gateNamePrefix + ":" + (opts.profile || "default")),
2014
+ opts,
2015
+ async function (ctx) { return perCtx(ctx, opts); });
2016
+ }
2017
+
2018
+ var gateFn = spec.gate || defaultGate;
2019
+
2020
+ var out = {
2021
+ NAME: spec.name,
2022
+ KIND: spec.kind,
2023
+ validate: spec.validate,
2024
+ buildProfile: buildProfileFn,
2025
+ compliancePosture: compliancePostureFn,
2026
+ loadRulePack: rulePacks.load,
2027
+ PROFILES: profiles,
2028
+ DEFAULTS: defaults,
2029
+ COMPLIANCE_POSTURES: postures,
2030
+ };
2031
+ if (spec.kind === "content") {
2032
+ out.MIME_TYPES = Object.freeze((spec.mimeTypes || []).slice());
2033
+ out.EXTENSIONS = Object.freeze((spec.extensions || []).slice());
2034
+ }
2035
+ if (spec.integrationFixtures) out.INTEGRATION_FIXTURES = spec.integrationFixtures;
2036
+ if (typeof spec.sanitize === "function") out.sanitize = spec.sanitize;
2037
+ out.gate = gateFn;
2038
+ // Error class exported under its own constructor name (GuardCsvError etc.)
2039
+ out[ErrorClass.name] = ErrorClass;
2040
+ // Per-guard extras (verb tables, escapeCell, schema, kidSafe, …) merged
2041
+ // verbatim via the prototype-safe own-enumerable copy (no computed-name
2042
+ // write; __proto__/constructor/prototype are skipped). Extras win over
2043
+ // factory defaults only when the guard explicitly re-exports a shared
2044
+ // name (rare; documented per guard).
2045
+ if (spec.extra) validateOpts.assignOwnEnumerable(out, spec.extra);
2046
+ return out;
2047
+ }
2048
+
2049
+ /**
2050
+ * @primitive b.gateContract.defineParser
2051
+ * @signature b.gateContract.defineParser(spec)
2052
+ * @since 0.15.0
2053
+ * @status stable
2054
+ * @related b.gateContract.defineGuard, b.gateContract.ALL_STRICT_POSTURES
2055
+ *
2056
+ * Assemble the minimal command / line-protocol / `safe-*` parser module
2057
+ * shape — guards whose four compliance postures all resolve to `strict`
2058
+ * (composing `ALL_STRICT_POSTURES`) and whose surface is a single
2059
+ * self-contained `validate` / `parse` entry point plus a
2060
+ * `compliancePosture(name)` that returns the effective PROFILE NAME (or
2061
+ * `null` for unknown names) rather than an overlay clone. These guards
2062
+ * carry no `gate` / `buildProfile` / `loadRulePack`, so `defineGuard`'s
2063
+ * full assembly would be wrong for them.
2064
+ *
2065
+ * Mints the error class (or accepts one), exposes the spec's primary
2066
+ * entry point under `spec.entryName` (default `"validate"`), and returns
2067
+ * the frozen module.exports with `PROFILES` / `COMPLIANCE_POSTURES` /
2068
+ * `compliancePosture` plus the spec's `extra` exports and the error
2069
+ * class.
2070
+ *
2071
+ * @opts
2072
+ * name: string, // module identity / error-name stem; required
2073
+ * entry: function, // the validate/parse entry point; required
2074
+ * entryName: string, // export key for the entry (default "validate")
2075
+ * profiles: object, // PROFILES; required
2076
+ * postures: object, // COMPLIANCE_POSTURES (default ALL_STRICT_POSTURES)
2077
+ * errorClass: function, // pre-built FrameworkError subclass
2078
+ * errorName: string, // defineClass name (mutually exclusive with errorClass)
2079
+ * extra: object, // additional exports (verb tables, KNOWN_*, …)
2080
+ *
2081
+ * @example
2082
+ * module.exports = b.gateContract.defineParser({
2083
+ * name: "pop3-command", entry: validate,
2084
+ * errorClass: GuardPop3CommandError,
2085
+ * profiles: PROFILES, postures: COMPLIANCE_POSTURES,
2086
+ * extra: { KNOWN_VERBS: KNOWN_VERBS, ZERO_ARG_VERBS: ZERO_ARG_VERBS },
2087
+ * });
2088
+ */
2089
+ function defineParser(spec) {
2090
+ validateOpts.requireObject(spec, "gateContract.defineParser", GateContractError);
2091
+ validateOpts.requireNonEmptyString(spec.name, "gateContract.defineParser: name",
2092
+ GateContractError, "gate-contract/bad-opt");
2093
+ if (typeof spec.entry !== "function") {
2094
+ throw _err("gate-contract/bad-opt", "defineParser: entry must be a function");
2095
+ }
2096
+ validateOpts.requireObject(spec.profiles, "gateContract.defineParser: profiles",
2097
+ GateContractError);
2098
+ if (spec.errorClass && spec.errorName) {
2099
+ throw _err("gate-contract/bad-opt",
2100
+ "defineParser: pass errorClass OR errorName, not both");
2101
+ }
2102
+ var ErrorClass = spec.errorClass ||
2103
+ defineClass(spec.errorName || ("Guard" +
2104
+ spec.name.charAt(0).toUpperCase() + spec.name.slice(1) + "Error"),
2105
+ { alwaysPermanent: true });
2106
+ var postures = spec.postures || ALL_STRICT_POSTURES;
2107
+
2108
+ function compliancePostureFn(name) {
2109
+ return postures[name] || null;
2110
+ }
2111
+
2112
+ var out = {
2113
+ compliancePosture: compliancePostureFn,
2114
+ PROFILES: spec.profiles,
2115
+ COMPLIANCE_POSTURES: postures,
2116
+ };
2117
+ out[spec.entryName || "validate"] = spec.entry;
2118
+ out[ErrorClass.name] = ErrorClass;
2119
+ if (spec.extra) validateOpts.assignOwnEnumerable(out, spec.extra);
2120
+ return out;
2121
+ }
2122
+
2123
+ // ---- ABI doc templates (single-sourced; rendered per guard) ----
2124
+ //
2125
+ // Every guard built through `defineGuard` / `defineParser` exposes the
2126
+ // SAME factory-generated ABI methods (`compliancePosture` and, for
2127
+ // `defineGuard`, `buildProfile` / `loadRulePack` / a default `gate`).
2128
+ // Those methods have no per-guard `function` declaration — the factory
2129
+ // wires them — so a refactored guard that wants its wiki page to keep
2130
+ // listing them used to carry a floating `@primitive` block per method,
2131
+ // duplicating the same prose across every member of the family.
2132
+ //
2133
+ // The `@abiTemplate` blocks below are the ONE copy of that prose. The
2134
+ // wiki parser (`examples/wiki/lib/source-doc-parser.js`) collects them
2135
+ // into a per-factory template bucket (keyed `defineGuard` / `defineParser`)
2136
+ // instead of the gateContract primitive list, and the page generator
2137
+ // (`examples/wiki/lib/page-generator.js`) instantiates them per guard —
2138
+ // substituting `{NS}` (the guard namespace, e.g. `guardCsv`) and `{ERR}`
2139
+ // (its error class, e.g. `GuardCsvError`) and filling `@since` from the
2140
+ // guard's own `@module` / first-primitive metadata — so each guard's page
2141
+ // renders every ABI method with usage correct for THAT guard. The
2142
+ // duplicated prose collapses to a single source; the rendered surface is
2143
+ // unchanged. A guard that keeps a bespoke per-method block (a custom
2144
+ // `gate`, or a guard that documents its own `compliancePosture`) wins —
2145
+ // the page generator skips the template for any method already present.
2146
+ //
2147
+ // These blocks intentionally carry the placeholder primitive form
2148
+ // `b.{NS}.<method>` and placeholder-bearing `@example` bodies; the
2149
+ // validator routes them through its template-shape pass, not the
2150
+ // resolvable-primitive pass.
2151
+
2152
+ /**
2153
+ * @abiTemplate defineGuard
2154
+ * @method compliancePosture
2155
+ * @signature b.{NS}.compliancePosture(name)
2156
+ * @status stable
2157
+ * @compliance hipaa, pci-dss, gdpr, soc2
2158
+ * @related b.{NS}.gate, b.{NS}.buildProfile
2159
+ *
2160
+ * Look up a compliance-posture overlay by name (one of `"hipaa"` /
2161
+ * `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a fresh clone of the
2162
+ * posture overlay so the caller may mutate it freely without disturbing
2163
+ * the shared table. Throws `{ERR}` with code `"{CODE}.bad-posture"` when
2164
+ * the name is not one this guard maps. Wired by `gateContract.defineGuard`
2165
+ * through `gateContract.lookupCompliancePosture`, so the clone semantics
2166
+ * and error code are identical across every guard in the family.
2167
+ *
2168
+ * @example
2169
+ * var posture = b.{NS}.compliancePosture("hipaa");
2170
+ * posture; // → overlay clone (mutable)
2171
+ *
2172
+ * try {
2173
+ * b.{NS}.compliancePosture("not-a-regime");
2174
+ * } catch (e) {
2175
+ * e.code; // → "{CODE}.bad-posture"
2176
+ * }
2177
+ */
2178
+
2179
+ /**
2180
+ * @abiTemplate defineGuard
2181
+ * @method buildProfile
2182
+ * @signature b.{NS}.buildProfile(opts)
2183
+ * @status stable
2184
+ * @related b.{NS}.gate, b.{NS}.compliancePosture
2185
+ *
2186
+ * Compose a derived profile from one or more named bases plus inline
2187
+ * overrides, resolving names through this guard's own `PROFILES` table.
2188
+ * `opts.extends` is a base profile name (`"strict"` / `"balanced"` /
2189
+ * `"permissive"`) or an array of names — later entries shadow earlier
2190
+ * ones, and inline `opts` keys win last. Wired by
2191
+ * `gateContract.defineGuard` through `gateContract.makeProfileBuilder`,
2192
+ * so operator-defined profiles stay traceable to a baseline instead of a
2193
+ * hand-typed dictionary.
2194
+ *
2195
+ * @opts
2196
+ * extends: string|string[], // base profile name(s) to compose
2197
+ * ...: any guard key, // inline override of resolved keys
2198
+ *
2199
+ * @example
2200
+ * var custom = b.{NS}.buildProfile({ extends: "strict" });
2201
+ * custom; // → composed profile object
2202
+ */
2203
+
2204
+ /**
2205
+ * @abiTemplate defineGuard
2206
+ * @method loadRulePack
2207
+ * @signature b.{NS}.loadRulePack(pack)
2208
+ * @status stable
2209
+ * @related b.{NS}.gate
2210
+ *
2211
+ * Register an operator-supplied rule pack with this guard's rule-pack
2212
+ * registry. The pack is identified by `pack.id` (a non-empty string) and
2213
+ * stored for later dispatch by gates that opt in via `opts.rulePackId`.
2214
+ * Returns the pack unchanged on success; throws `{ERR}` with code
2215
+ * `"{CODE}.bad-opt"` when `pack` is missing or `pack.id` is not a non-empty
2216
+ * string. Wired by `gateContract.defineGuard` through
2217
+ * `gateContract.makeRulePackLoader`, so storage shape and validation are
2218
+ * identical across the family.
2219
+ *
2220
+ * @example
2221
+ * var pack = b.{NS}.loadRulePack({ id: "tenant-policy", rules: [] });
2222
+ * pack.id; // → "tenant-policy"
2223
+ */
2224
+
2225
+ /**
2226
+ * @abiTemplate defineGuard
2227
+ * @method gate
2228
+ * @signature b.{NS}.gate(opts?)
2229
+ * @status stable
2230
+ * @related b.{NS}.validate, b.gateContract.buildGuardGate
2231
+ *
2232
+ * Build the guard's request-boundary gate — a contract-shaped object
2233
+ * exposing `check(ctx)` that host primitives call at their byte moment.
2234
+ * This is the factory default chain: `serve` when no issue, `audit-only`
2235
+ * for `info` / `warn` issues, and `refuse` for any `high` / `critical`
2236
+ * issue, dispatched to the right `ctx` field by the guard's KIND. Wired
2237
+ * by `gateContract.defineGuard` through `gateContract.buildGuardGate`; a
2238
+ * guard whose gate diverges (a bespoke sanitize-and-reserialize chain,
2239
+ * for example) ships its own `gate` block instead of this template.
2240
+ *
2241
+ * @opts
2242
+ * profile: string, // one of PROFILES; default this guard's default
2243
+ * compliancePosture: string, // overlay one of hipaa/pci-dss/gdpr/soc2
2244
+ * mode: string, // one of gateContract MODES; default "enforce"
2245
+ *
2246
+ * @example
2247
+ * var gate = b.{NS}.gate({ profile: "strict" });
2248
+ * var decision = await gate.check({ bytes: Buffer.from("...") });
2249
+ * decision.action; // → "serve" | "refuse" | …
2250
+ */
2251
+
2252
+ /**
2253
+ * @abiTemplate defineParser
2254
+ * @method compliancePosture
2255
+ * @signature b.{NS}.compliancePosture(name)
2256
+ * @status stable
2257
+ * @compliance hipaa, pci-dss, gdpr, soc2
2258
+ * @related b.{NS}.validate, b.gateContract.ALL_STRICT_POSTURES
2259
+ *
2260
+ * Return the effective profile NAME for a compliance posture, or `null`
2261
+ * for a name this parser does not map. Unlike the content-guard variant
2262
+ * this returns the resolved profile string (every line-protocol parser
2263
+ * composes `gateContract.ALL_STRICT_POSTURES`, so `"hipaa"` / `"pci-dss"`
2264
+ * / `"gdpr"` / `"soc2"` all resolve to `"strict"`) and never throws —
2265
+ * the parser shape carries no overlay-clone, no `buildProfile`, and no
2266
+ * `loadRulePack`. Wired by `gateContract.defineParser`.
2267
+ *
2268
+ * @example
2269
+ * b.{NS}.compliancePosture("hipaa"); // → "strict"
2270
+ * b.{NS}.compliancePosture("not-a-regime"); // → null
2271
+ */
2272
+
1683
2273
  module.exports = {
1684
2274
  defineGate: defineGate,
2275
+ defineGuard: defineGuard,
2276
+ defineParser: defineParser,
1685
2277
  validateGateShape: validateGateShape,
1686
2278
  runGate: runGate,
1687
2279
  composeGates: composeGates,
@@ -1700,8 +2292,11 @@ module.exports = {
1700
2292
  buildGuardGate: buildGuardGate,
1701
2293
  extractBytesAsText: extractBytesAsText,
1702
2294
  lookupCompliancePosture: lookupCompliancePosture,
2295
+ ALL_STRICT_POSTURES: ALL_STRICT_POSTURES,
1703
2296
  makeRulePackLoader: makeRulePackLoader,
1704
2297
  makeProfileBuilder: makeProfileBuilder,
2298
+ makeProfileResolver: makeProfileResolver,
2299
+ throwOnRefusalSeverity: throwOnRefusalSeverity,
1705
2300
  badInputResultIfNotStringOrBuffer: badInputResultIfNotStringOrBuffer,
1706
2301
  aggregateIssues: aggregateIssues,
1707
2302
  composeHooks: composeHooks,