@drawbridge/drawbridge-utils 0.0.82 → 0.0.84

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/dist/billing.cjs CHANGED
@@ -435,6 +435,7 @@ var action = {
435
435
  valid.push({ type, amount: amount2, key, meta, traceId });
436
436
  }
437
437
  if (valid.length === 0) return;
438
+ const date = /* @__PURE__ */ new Date();
438
439
  const result = await controller.bulk({
439
440
  collection: "action",
440
441
  pipeline: valid.map(({ type, amount: amount2, key, meta, traceId }) => ({
@@ -448,6 +449,8 @@ var action = {
448
449
  units: amount2,
449
450
  key,
450
451
  meta,
452
+ createdAt: date,
453
+ updatedAt: date,
451
454
  ...traceId && { traceId }
452
455
  }
453
456
  },
@@ -485,6 +485,15 @@ const action = {
485
485
 
486
486
  if( valid.length === 0 ) return;
487
487
 
488
+ // controller.bulk is a RAW bulkWrite — unlike record()'s controller.update,
489
+ // nothing injects insert timestamps. createdAt is what the per-period billing
490
+ // aggregation (and the ledger's TTL index) key on, so a row inserted without
491
+ // it is invisible to every invoice window forever: sweep-billed segment
492
+ // joins were counted on the usage display but never billed (2026-07-21,
493
+ // usage 12 vs invoice qty 3). No `id` is minted here (no ObjectId dep);
494
+ // ledger rows are only ever queried by key/usage/organization/createdAt.
495
+ const date = new Date();
496
+
488
497
  const result = await controller.bulk({
489
498
  collection : 'action',
490
499
  pipeline : valid.map( ({ type, amount, key, meta, traceId }) => ({
@@ -498,6 +507,8 @@ const action = {
498
507
  units : amount,
499
508
  key,
500
509
  meta,
510
+ createdAt : date,
511
+ updatedAt : date,
501
512
  ...( traceId && { traceId })
502
513
  }
503
514
  },
package/dist/billing.d.ts CHANGED
@@ -485,6 +485,15 @@ const action = {
485
485
 
486
486
  if( valid.length === 0 ) return;
487
487
 
488
+ // controller.bulk is a RAW bulkWrite — unlike record()'s controller.update,
489
+ // nothing injects insert timestamps. createdAt is what the per-period billing
490
+ // aggregation (and the ledger's TTL index) key on, so a row inserted without
491
+ // it is invisible to every invoice window forever: sweep-billed segment
492
+ // joins were counted on the usage display but never billed (2026-07-21,
493
+ // usage 12 vs invoice qty 3). No `id` is minted here (no ObjectId dep);
494
+ // ledger rows are only ever queried by key/usage/organization/createdAt.
495
+ const date = new Date();
496
+
488
497
  const result = await controller.bulk({
489
498
  collection : 'action',
490
499
  pipeline : valid.map( ({ type, amount, key, meta, traceId }) => ({
@@ -498,6 +507,8 @@ const action = {
498
507
  units : amount,
499
508
  key,
500
509
  meta,
510
+ createdAt : date,
511
+ updatedAt : date,
501
512
  ...( traceId && { traceId })
502
513
  }
503
514
  },
package/dist/billing.js CHANGED
@@ -405,6 +405,7 @@ var action = {
405
405
  valid.push({ type, amount: amount2, key, meta, traceId });
406
406
  }
407
407
  if (valid.length === 0) return;
408
+ const date = /* @__PURE__ */ new Date();
408
409
  const result = await controller.bulk({
409
410
  collection: "action",
410
411
  pipeline: valid.map(({ type, amount: amount2, key, meta, traceId }) => ({
@@ -418,6 +419,8 @@ var action = {
418
419
  units: amount2,
419
420
  key,
420
421
  meta,
422
+ createdAt: date,
423
+ updatedAt: date,
421
424
  ...traceId && { traceId }
422
425
  }
423
426
  },
@@ -361,6 +361,19 @@ var createOAuthModel = ({ controller }) => ({
361
361
  const granted = intersect(scope, token.scope ?? []);
362
362
  return granted.length > 0;
363
363
  },
364
+ // Scope enforcement at AUTHORIZE time. Without this, @node-oauth's
365
+ // AuthorizeHandler grants whatever scope the request asks for — the client
366
+ // registry's scopes list was only advisory (reachable from any logged-in
367
+ // browser via the dashboard's authorize BFF). Every requested scope must be
368
+ // registered on the client; an empty request falls back to the client's
369
+ // full registered grant. Returning false → invalid_scope.
370
+ validateScope: (user, client, scope) => {
371
+ const requested = Array.isArray(scope) ? scope : scope ? String(scope).split(/\s+/).filter(Boolean) : [];
372
+ const allowed = (client == null ? void 0 : client.scopes) ?? [];
373
+ if (!requested.length) return allowed;
374
+ const invalid = requested.filter((entry) => !allowed.includes(entry));
375
+ return invalid.length ? false : requested;
376
+ },
364
377
  // client_credentials grant: treat the client as the resource owner.
365
378
  getUserFromClient: (client) => ({ id: client.id, type: "client" })
366
379
  });
@@ -304,6 +304,28 @@ const createOAuthModel = ({ controller }) => ({
304
304
 
305
305
  },
306
306
 
307
+ // Scope enforcement at AUTHORIZE time. Without this, @node-oauth's
308
+ // AuthorizeHandler grants whatever scope the request asks for — the client
309
+ // registry's scopes list was only advisory (reachable from any logged-in
310
+ // browser via the dashboard's authorize BFF). Every requested scope must be
311
+ // registered on the client; an empty request falls back to the client's
312
+ // full registered grant. Returning false → invalid_scope.
313
+ validateScope : ( user, client, scope ) => {
314
+
315
+ const requested = Array.isArray( scope )
316
+ ? scope
317
+ : ( scope ? String( scope ).split( /\s+/ ).filter( Boolean ) : [] );
318
+
319
+ const allowed = client?.scopes ?? [];
320
+
321
+ if( ! requested.length ) return allowed;
322
+
323
+ const invalid = requested.filter( ( entry ) => ! allowed.includes( entry ) );
324
+
325
+ return invalid.length ? false : requested;
326
+
327
+ },
328
+
307
329
  // client_credentials grant: treat the client as the resource owner.
308
330
  getUserFromClient : ( client ) => ({ id : client.id, type : 'client' })
309
331
 
@@ -304,6 +304,28 @@ const createOAuthModel = ({ controller }) => ({
304
304
 
305
305
  },
306
306
 
307
+ // Scope enforcement at AUTHORIZE time. Without this, @node-oauth's
308
+ // AuthorizeHandler grants whatever scope the request asks for — the client
309
+ // registry's scopes list was only advisory (reachable from any logged-in
310
+ // browser via the dashboard's authorize BFF). Every requested scope must be
311
+ // registered on the client; an empty request falls back to the client's
312
+ // full registered grant. Returning false → invalid_scope.
313
+ validateScope : ( user, client, scope ) => {
314
+
315
+ const requested = Array.isArray( scope )
316
+ ? scope
317
+ : ( scope ? String( scope ).split( /\s+/ ).filter( Boolean ) : [] );
318
+
319
+ const allowed = client?.scopes ?? [];
320
+
321
+ if( ! requested.length ) return allowed;
322
+
323
+ const invalid = requested.filter( ( entry ) => ! allowed.includes( entry ) );
324
+
325
+ return invalid.length ? false : requested;
326
+
327
+ },
328
+
307
329
  // client_credentials grant: treat the client as the resource owner.
308
330
  getUserFromClient : ( client ) => ({ id : client.id, type : 'client' })
309
331
 
@@ -326,6 +326,19 @@ var createOAuthModel = ({ controller }) => ({
326
326
  const granted = intersect(scope, token.scope ?? []);
327
327
  return granted.length > 0;
328
328
  },
329
+ // Scope enforcement at AUTHORIZE time. Without this, @node-oauth's
330
+ // AuthorizeHandler grants whatever scope the request asks for — the client
331
+ // registry's scopes list was only advisory (reachable from any logged-in
332
+ // browser via the dashboard's authorize BFF). Every requested scope must be
333
+ // registered on the client; an empty request falls back to the client's
334
+ // full registered grant. Returning false → invalid_scope.
335
+ validateScope: (user, client, scope) => {
336
+ const requested = Array.isArray(scope) ? scope : scope ? String(scope).split(/\s+/).filter(Boolean) : [];
337
+ const allowed = (client == null ? void 0 : client.scopes) ?? [];
338
+ if (!requested.length) return allowed;
339
+ const invalid = requested.filter((entry) => !allowed.includes(entry));
340
+ return invalid.length ? false : requested;
341
+ },
329
342
  // client_credentials grant: treat the client as the resource owner.
330
343
  getUserFromClient: (client) => ({ id: client.id, type: "client" })
331
344
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "dependencies": {
4
- "@drawbridge/drawbridge-agents": "0.0.10",
4
+ "@drawbridge/drawbridge-agents": "0.0.11",
5
5
  "@drawbridge/drawbridge-telemetry": "0.0.15",
6
6
  "@google/genai": "1.30.0",
7
7
  "axios": "1.16.0",
@@ -164,5 +164,5 @@
164
164
  "test": "node --test test/"
165
165
  },
166
166
  "types": "dist/index.d.ts",
167
- "version": "0.0.82"
167
+ "version": "0.0.84"
168
168
  }