@suveren/gateway 0.37.0 → 0.39.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.
@@ -23,7 +23,7 @@ const { normalizeOptions } = require('../utils')
23
23
  module.exports = json
24
24
 
25
25
  /**
26
- * RegExp to match the first non-space in a string.
26
+ * RegExp to match the first non-whitespace character in a string.
27
27
  *
28
28
  * Allowed whitespace is defined in RFC 7159:
29
29
  *
@@ -33,7 +33,7 @@ module.exports = json
33
33
  * %x0A / ; Line feed or New line
34
34
  * %x0D ) ; Carriage return
35
35
  */
36
- const FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
36
+ const FIRST_NON_WHITESPACE_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d]|$)/ // eslint-disable-line no-control-regex
37
37
 
38
38
  const JSON_SYNTAX_CHAR = '#'
39
39
  const JSON_SYNTAX_REGEXP = /#+/g
@@ -80,10 +80,12 @@ function createJsonParser (options) {
80
80
  return {}
81
81
  }
82
82
 
83
- const first = firstchar(body)
84
- if (first !== '{' && first !== '[') {
85
- debug('strict violation')
86
- throw createStrictSyntaxError(body, first)
83
+ if (body[0] !== '{' && body[0] !== '[') {
84
+ const first = firstNonWhitespaceChar(body)
85
+ if (first !== '{' && first !== '[') {
86
+ debug('strict violation')
87
+ throw createStrictSyntaxError(body, first)
88
+ }
87
89
  }
88
90
 
89
91
  try {
@@ -152,12 +154,10 @@ function createStrictSyntaxError (str, char) {
152
154
  * @returns {string|undefined}
153
155
  * @private
154
156
  */
155
- function firstchar (str) {
156
- const match = FIRST_CHAR_REGEXP.exec(str)
157
+ function firstNonWhitespaceChar (str) {
158
+ const match = FIRST_NON_WHITESPACE_REGEXP.exec(str)
157
159
 
158
- return match
159
- ? match[1]
160
- : undefined
160
+ return match && match[1] ? match[1] : undefined
161
161
  }
162
162
 
163
163
  /**
@@ -98,12 +98,12 @@ function createQueryParser (options) {
98
98
  try {
99
99
  return qs.parse(body, {
100
100
  allowPrototypes: true,
101
- arrayLimit: arrayLimit,
102
- depth: depth,
103
- charsetSentinel: charsetSentinel,
104
- interpretNumericEntities: interpretNumericEntities,
101
+ arrayLimit,
102
+ depth,
103
+ charsetSentinel,
104
+ interpretNumericEntities,
105
105
  charset: encoding,
106
- parameterLimit: parameterLimit,
106
+ parameterLimit,
107
107
  strictDepth: true
108
108
  })
109
109
  } catch (err) {
@@ -5,31 +5,16 @@
5
5
  */
6
6
 
7
7
  const bytes = require('bytes')
8
- const contentType = require('content-type')
9
8
  const typeis = require('type-is')
10
9
 
11
10
  /**
12
11
  * Module exports.
13
12
  */
14
13
  module.exports = {
15
- getCharset,
16
14
  normalizeOptions,
17
15
  passthrough
18
16
  }
19
17
 
20
- /**
21
- * Get the charset of a request.
22
- *
23
- * @param {Object} req
24
- * @returns {string | undefined}
25
- * @private
26
- */
27
- function getCharset (req) {
28
- const header = req.headers['content-type']
29
- if (!header) return undefined
30
- return contentType.parse(header).parameters.charset?.toLowerCase()
31
- }
32
-
33
18
  /**
34
19
  * Get the simple type checker.
35
20
  *
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "body-parser",
3
3
  "description": "Node.js body parsing middleware",
4
- "version": "2.3.0",
4
+ "version": "2.4.0",
5
5
  "contributors": [
6
6
  "Douglas Christopher Wilson <doug@somethingdoug.com>",
7
7
  "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
@@ -27,26 +27,21 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "bytes": "^3.1.2",
30
- "content-type": "^2.0.0",
30
+ "content-type": "^2.1.0",
31
31
  "debug": "^4.4.3",
32
32
  "http-errors": "^2.0.1",
33
- "iconv-lite": "^0.7.2",
33
+ "iconv-lite": "^0.7.3",
34
34
  "on-finished": "^2.4.1",
35
- "qs": "^6.15.2",
35
+ "qs": "^6.16.1",
36
36
  "raw-body": "^3.0.2",
37
37
  "type-is": "^2.1.0"
38
38
  },
39
39
  "devDependencies": {
40
- "eslint": "^8.57.1",
41
- "eslint-config-standard": "^14.1.1",
42
- "eslint-plugin-import": "^2.32.0",
43
- "eslint-plugin-markdown": "^3.0.1",
44
- "eslint-plugin-node": "^11.1.0",
45
- "eslint-plugin-promise": "^6.6.0",
46
- "eslint-plugin-standard": "^4.1.0",
40
+ "eslint": "^9.39.5",
47
41
  "mocha": "^11.7.6",
42
+ "neostandard": "^0.13.0",
48
43
  "nyc": "^17.1.0",
49
- "supertest": "^7.2.2"
44
+ "supertest": "7.3.0"
50
45
  },
51
46
  "files": [
52
47
  "lib/",
@@ -1,10 +1,15 @@
1
+ 1.20.9
2
+ ======
3
+
4
+ * Security fix for [GHSA-27r3-jxmh-p828](https://github.com/expressjs/body-parser/security/advisories/GHSA-27r3-jxmh-p828) (CVE-2026-111181)
5
+
1
6
  1.20.8
2
- ===================
7
+ ======
3
8
 
4
9
  * Same code base as 1.20.7. This was created to test the new release process.
5
10
 
6
11
  1.20.7
7
- ===================
12
+ ======
8
13
 
9
14
  * deps: qs@~6.16.0
10
15
 
@@ -98,7 +98,7 @@ argument. You can find more information on this argument
98
98
 
99
99
  ##### strict
100
100
 
101
- When set to `true`, will only accept arrays and objects; when `false` will
101
+ When set to `true`, it will only accept arrays and objects; when `false`, it will
102
102
  accept anything `JSON.parse` accepts. Defaults to `true`.
103
103
 
104
104
  ##### type
@@ -313,7 +313,7 @@ the entity value that failed parsing.
313
313
  ### entity verify failed
314
314
 
315
315
  This error will occur when the request contained an entity that could not be
316
- failed verification by the defined `verify` option. The `status` property is
316
+ verified by the defined `verify` option. The `status` property is
317
317
  set to `403`, the `type` property is set to `'entity.verify.failed'`, and the
318
318
  `body` property is set to the entity value that failed verification.
319
319
 
@@ -380,7 +380,7 @@ property is set to the encoding that is unsupported.
380
380
 
381
381
  ### The input exceeded the depth
382
382
 
383
- This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. When the `depth` option is set to `32` (default value), the error will not be thrown.
383
+ This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. With the default `depth` of `32`, an input with 33 or more levels of nesting gets this error.
384
384
 
385
385
  ## Examples
386
386
 
@@ -51,6 +51,15 @@ function read (req, res, next, parse, debug, options) {
51
51
  : null
52
52
  var verify = opts.verify
53
53
 
54
+ // assert charset is supported before creating the decompression stream, so a
55
+ // 415 cannot leave a decoder piped with no error listener
56
+ if (verify && encoding !== null && !iconv.encodingExists(encoding)) {
57
+ return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
58
+ charset: encoding.toLowerCase(),
59
+ type: 'charset.unsupported'
60
+ }))
61
+ }
62
+
54
63
  try {
55
64
  // get the content stream
56
65
  stream = contentstream(req, debug, opts.inflate)
@@ -66,14 +75,6 @@ function read (req, res, next, parse, debug, options) {
66
75
  ? null
67
76
  : encoding
68
77
 
69
- // assert charset is supported
70
- if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
71
- return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
72
- charset: encoding.toLowerCase(),
73
- type: 'charset.unsupported'
74
- }))
75
- }
76
-
77
78
  // read body
78
79
  debug('read body')
79
80
  getBody(stream, opts, function (error, body) {
@@ -104,6 +105,23 @@ function read (req, res, next, parse, debug, options) {
104
105
  return
105
106
  }
106
107
 
108
+ // detach and destroy the decompression stream to avoid
109
+ // decompression errors on the finished stream
110
+ if (stream !== req) {
111
+ // only detach the decompression stream, keep other consumers piped
112
+ if (typeof req.unpipe === 'function') {
113
+ req.unpipe(stream)
114
+ } else {
115
+ // Node.js 0.8 streams have no unpipe(dest)
116
+ unpipe(req)
117
+ }
118
+ stream.on('error', function () {})
119
+ destroy(stream, true)
120
+
121
+ // consume any remaining bytes so the HTTP request can finish
122
+ req.resume()
123
+ }
124
+
107
125
  // verify
108
126
  if (verify) {
109
127
  try {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "body-parser",
3
3
  "description": "Node.js body parsing middleware",
4
- "version": "1.20.8",
4
+ "version": "1.20.9",
5
5
  "contributors": [
6
6
  "Douglas Christopher Wilson <doug@somethingdoug.com>",
7
7
  "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
@@ -65,10 +65,11 @@ function onFinished (msg, listener) {
65
65
 
66
66
  function isFinished (msg) {
67
67
  var socket = msg.socket
68
+ var finished = writableEnded(msg)
68
69
 
69
- if (typeof msg.finished === 'boolean') {
70
+ if (typeof finished === 'boolean') {
70
71
  // OutgoingMessage
71
- return Boolean(msg.finished || (socket && !socket.writable))
72
+ return Boolean(finished || (socket && !socket.writable))
72
73
  }
73
74
 
74
75
  if (typeof msg.complete === 'boolean') {
@@ -232,3 +233,14 @@ function wrap (fn) {
232
233
  // return bound function
233
234
  return res.runInAsyncScope.bind(res, fn, null)
234
235
  }
236
+
237
+ /**
238
+ * Determines if a writable stream has finished.
239
+ *
240
+ * @param {Object} res
241
+ * @returns {boolean}
242
+ * @private
243
+ */
244
+ function writableEnded (res) {
245
+ return typeof res.writableEnded === 'boolean' ? res.writableEnded : res.finished
246
+ }
@@ -1,38 +1,42 @@
1
1
  {
2
2
  "name": "on-finished",
3
3
  "description": "Execute a callback when a request closes, finishes, or errors",
4
- "version": "2.4.1",
4
+ "version": "2.4.2",
5
5
  "contributors": [
6
6
  "Douglas Christopher Wilson <doug@somethingdoug.com>",
7
7
  "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
8
8
  ],
9
9
  "license": "MIT",
10
10
  "repository": "jshttp/on-finished",
11
+ "funding": {
12
+ "type": "opencollective",
13
+ "url": "https://opencollective.com/express"
14
+ },
15
+ "type": "commonjs",
11
16
  "dependencies": {
12
17
  "ee-first": "1.1.1"
13
18
  },
14
19
  "devDependencies": {
15
- "eslint": "7.32.0",
20
+ "eslint": "8.17.0",
16
21
  "eslint-config-standard": "14.1.1",
17
- "eslint-plugin-import": "2.25.4",
22
+ "eslint-plugin-import": "2.26.0",
18
23
  "eslint-plugin-markdown": "2.2.1",
19
24
  "eslint-plugin-node": "11.1.0",
20
- "eslint-plugin-promise": "5.2.0",
25
+ "eslint-plugin-promise": "6.0.0",
21
26
  "eslint-plugin-standard": "4.1.0",
22
- "mocha": "9.2.1",
27
+ "mocha": "12.0.3",
23
28
  "nyc": "15.1.0"
24
29
  },
25
30
  "engines": {
26
31
  "node": ">= 0.8"
27
32
  },
28
33
  "files": [
29
- "HISTORY.md",
30
34
  "LICENSE",
31
35
  "index.js"
32
36
  ],
33
37
  "scripts": {
34
38
  "lint": "eslint .",
35
- "test": "mocha --reporter spec --bail --check-leaks test/",
39
+ "test": "mocha --reporter spec --check-leaks test/",
36
40
  "test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
37
41
  "test-cov": "nyc --reporter=html --reporter=text npm test"
38
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suveren/gateway",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -55,6 +55,7 @@
55
55
  "github.com/humanagencyprotocol/hap-profiles/invoicing@0.1": "invoicing/0.1.profile.json",
56
56
  "github.com/humanagencyprotocol/hap-profiles/charge@0.7": "charge/0.7.profile.json",
57
57
  "github.com/humanagencyprotocol/hap-profiles/email@0.9": "email/0.9.profile.json",
58
- "github.com/humanagencyprotocol/hap-profiles/delegation@0.6": "delegation/0.6.profile.json"
58
+ "github.com/humanagencyprotocol/hap-profiles/delegation@0.6": "delegation/0.6.profile.json",
59
+ "github.com/humanagencyprotocol/hap-profiles/master-data@0.1": "master-data/0.1.profile.json"
59
60
  }
60
61
  }
@@ -0,0 +1,311 @@
1
+ {
2
+ "id": "github.com/humanagencyprotocol/hap-profiles/master-data@0.1",
3
+ "name": "Master data",
4
+ "version": "0.1",
5
+ "description": "Master data authority — governs the customer records and the catalogue that quotes and invoices are built from: creating and changing customers, and creating and changing catalogue items. A change to a customer's tax data (name, address, country, VAT ID) and a change to an item's price or VAT rate are each a decision of their own. Kept apart from invoicing and sales so that the people who issue documents are not the ones who change the data behind them. A change to a customer's payment data (bank account / IBAN, payment terms, credit limit, dunning or payment method) is its own decision too — the classic entry point for payment fraud; it is governed here wherever a connector offers such a change (ERP Local offers none). Every action in this area is reviewed by a person. No invoices, quotes or payments.",
6
+ "whatsNew": "New area. Customers and catalogue items are created and changed here, each kind of change with its own daily limit; changes to a customer's bank and payment data (for connectors that offer them) have their own daily limit and a cap on the credit limit. Every action is reviewed by a person (no automatic mode).",
7
+ "commitment_modes": [
8
+ "review"
9
+ ],
10
+ "boundsSchema": {
11
+ "actionTypes": [
12
+ "customer_create",
13
+ "customer_update",
14
+ "customer_tax",
15
+ "customer_payment",
16
+ "item_create",
17
+ "item_update",
18
+ "item_price"
19
+ ],
20
+ "keyOrder": [
21
+ "profile",
22
+ "read_access",
23
+ "customer_create_daily_max",
24
+ "customer_update_daily_max",
25
+ "customer_tax_daily_max",
26
+ "customer_payment_daily_max",
27
+ "item_create_daily_max",
28
+ "item_update_daily_max",
29
+ "item_price_daily_max",
30
+ "list_price_max",
31
+ "credit_limit_max"
32
+ ],
33
+ "fields": {
34
+ "profile": {
35
+ "type": "string",
36
+ "required": true
37
+ },
38
+ "read_access": {
39
+ "type": "string",
40
+ "required": true,
41
+ "displayName": "Read access",
42
+ "description": "Allow the agent to look up customers and catalogue items.",
43
+ "boundType": {
44
+ "kind": "enum",
45
+ "values": [
46
+ "unlimited",
47
+ "none"
48
+ ]
49
+ },
50
+ "toolsDescription": "find_customers, get_customer, list_items, get_item",
51
+ "default": "unlimited"
52
+ },
53
+ "customer_create_daily_max": {
54
+ "type": "number",
55
+ "required": true,
56
+ "displayName": "New customers per day",
57
+ "description": "Maximum customers created per day. A new customer's name, address, country and VAT ID decide how invoices to them are taxed.",
58
+ "unit": "count",
59
+ "boundType": {
60
+ "kind": "cumulative_count",
61
+ "window": "daily"
62
+ },
63
+ "appliesTo": [
64
+ "customer_create"
65
+ ]
66
+ },
67
+ "customer_update_daily_max": {
68
+ "type": "number",
69
+ "required": true,
70
+ "displayName": "Customer contact changes per day",
71
+ "description": "Maximum changes per day to a customer's contact data (email, CRM link). Tax data is its own limit.",
72
+ "unit": "count",
73
+ "boundType": {
74
+ "kind": "cumulative_count",
75
+ "window": "daily"
76
+ },
77
+ "appliesTo": [
78
+ "customer_update"
79
+ ]
80
+ },
81
+ "customer_tax_daily_max": {
82
+ "type": "number",
83
+ "required": true,
84
+ "displayName": "Customer tax-data changes per day",
85
+ "description": "Maximum changes per day to a customer's tax data: name, address, country or VAT ID. These decide how future invoices to that customer are taxed.",
86
+ "unit": "count",
87
+ "boundType": {
88
+ "kind": "cumulative_count",
89
+ "window": "daily"
90
+ },
91
+ "appliesTo": [
92
+ "customer_tax"
93
+ ]
94
+ },
95
+ "customer_payment_daily_max": {
96
+ "type": "number",
97
+ "required": true,
98
+ "displayName": "Customer payment-data changes per day",
99
+ "description": "Maximum changes per day to a customer's payment data: bank account / IBAN, payment terms, credit limit, dunning or payment method. A changed bank account is the classic way payments are diverted.",
100
+ "unit": "count",
101
+ "boundType": {
102
+ "kind": "cumulative_count",
103
+ "window": "daily"
104
+ },
105
+ "appliesTo": [
106
+ "customer_payment"
107
+ ]
108
+ },
109
+ "item_create_daily_max": {
110
+ "type": "number",
111
+ "required": true,
112
+ "displayName": "New items per day",
113
+ "description": "Maximum catalogue items created per day.",
114
+ "unit": "count",
115
+ "boundType": {
116
+ "kind": "cumulative_count",
117
+ "window": "daily"
118
+ },
119
+ "appliesTo": [
120
+ "item_create"
121
+ ]
122
+ },
123
+ "item_update_daily_max": {
124
+ "type": "number",
125
+ "required": true,
126
+ "displayName": "Item changes per day",
127
+ "description": "Maximum changes per day to catalogue items other than price and VAT rate (name, SKU, unit, kind, description, active).",
128
+ "unit": "count",
129
+ "boundType": {
130
+ "kind": "cumulative_count",
131
+ "window": "daily"
132
+ },
133
+ "appliesTo": [
134
+ "item_update"
135
+ ]
136
+ },
137
+ "item_price_daily_max": {
138
+ "type": "number",
139
+ "required": true,
140
+ "displayName": "Price changes per day",
141
+ "description": "Maximum price or VAT-rate changes to existing catalogue items per day.",
142
+ "unit": "count",
143
+ "boundType": {
144
+ "kind": "cumulative_count",
145
+ "window": "daily"
146
+ },
147
+ "appliesTo": [
148
+ "item_price"
149
+ ]
150
+ },
151
+ "list_price_max": {
152
+ "type": "number",
153
+ "required": true,
154
+ "displayName": "Max list price per item",
155
+ "description": "Highest net list price a catalogue item may be given, when it is created or its price is changed, in currency units.",
156
+ "unit": "currency:EUR",
157
+ "boundType": {
158
+ "kind": "per_transaction",
159
+ "of": "list_price",
160
+ "requiredFor": [
161
+ "item_create",
162
+ "item_price"
163
+ ]
164
+ }
165
+ },
166
+ "credit_limit_max": {
167
+ "type": "number",
168
+ "required": true,
169
+ "displayName": "Max credit limit per customer",
170
+ "description": "Highest credit limit a customer may be given in one payment-data change, in currency units. Applies whenever the change sets a credit limit; a bank or payment-terms change that sets none is limited by the daily count only.",
171
+ "unit": "currency:EUR",
172
+ "boundType": {
173
+ "kind": "per_transaction",
174
+ "of": "credit_limit"
175
+ }
176
+ }
177
+ }
178
+ },
179
+ "executionContextSchema": {
180
+ "fields": {
181
+ "list_price": {
182
+ "source": "declared",
183
+ "description": "Net list price of the item in currency units (create_item, update_item_price)",
184
+ "required": false,
185
+ "constraint": {
186
+ "type": "number",
187
+ "enforceable": [
188
+ "max"
189
+ ]
190
+ }
191
+ },
192
+ "credit_limit": {
193
+ "source": "declared",
194
+ "description": "Credit limit a payment-data change sets for the customer, in currency units (only when the change sets one)",
195
+ "required": false,
196
+ "constraint": {
197
+ "type": "number",
198
+ "enforceable": [
199
+ "max"
200
+ ]
201
+ }
202
+ },
203
+ "customer_create_count_daily": {
204
+ "source": "cumulative",
205
+ "description": "Running daily count of customers created",
206
+ "required": true,
207
+ "constraint": {
208
+ "type": "number",
209
+ "enforceable": [
210
+ "max"
211
+ ]
212
+ },
213
+ "cumulativeField": "_count",
214
+ "window": "daily"
215
+ },
216
+ "customer_update_count_daily": {
217
+ "source": "cumulative",
218
+ "description": "Running daily count of customer contact-data changes",
219
+ "required": true,
220
+ "constraint": {
221
+ "type": "number",
222
+ "enforceable": [
223
+ "max"
224
+ ]
225
+ },
226
+ "cumulativeField": "_count",
227
+ "window": "daily"
228
+ },
229
+ "customer_tax_count_daily": {
230
+ "source": "cumulative",
231
+ "description": "Running daily count of customer tax-data changes",
232
+ "required": true,
233
+ "constraint": {
234
+ "type": "number",
235
+ "enforceable": [
236
+ "max"
237
+ ]
238
+ },
239
+ "cumulativeField": "_count",
240
+ "window": "daily"
241
+ },
242
+ "customer_payment_count_daily": {
243
+ "source": "cumulative",
244
+ "description": "Running daily count of customer payment-data changes",
245
+ "required": true,
246
+ "constraint": {
247
+ "type": "number",
248
+ "enforceable": [
249
+ "max"
250
+ ]
251
+ },
252
+ "cumulativeField": "_count",
253
+ "window": "daily"
254
+ },
255
+ "item_create_count_daily": {
256
+ "source": "cumulative",
257
+ "description": "Running daily count of catalogue items created",
258
+ "required": true,
259
+ "constraint": {
260
+ "type": "number",
261
+ "enforceable": [
262
+ "max"
263
+ ]
264
+ },
265
+ "cumulativeField": "_count",
266
+ "window": "daily"
267
+ },
268
+ "item_update_count_daily": {
269
+ "source": "cumulative",
270
+ "description": "Running daily count of catalogue item changes (not price)",
271
+ "required": true,
272
+ "constraint": {
273
+ "type": "number",
274
+ "enforceable": [
275
+ "max"
276
+ ]
277
+ },
278
+ "cumulativeField": "_count",
279
+ "window": "daily"
280
+ },
281
+ "item_price_count_daily": {
282
+ "source": "cumulative",
283
+ "description": "Running daily count of item price or VAT-rate changes",
284
+ "required": true,
285
+ "constraint": {
286
+ "type": "number",
287
+ "enforceable": [
288
+ "max"
289
+ ]
290
+ },
291
+ "cumulativeField": "_count",
292
+ "window": "daily"
293
+ }
294
+ }
295
+ },
296
+ "requiredGates": [
297
+ "bounds",
298
+ "intent",
299
+ "commitment",
300
+ "mandate_owner"
301
+ ],
302
+ "ttl": {
303
+ "default": 86400,
304
+ "max": 2592000
305
+ },
306
+ "retention_minimum": 7776000,
307
+ "content_binding": {
308
+ "version": "1",
309
+ "kind": "jcs"
310
+ }
311
+ }