@jantstack/adonis-authz 2.0.0-alpha.1 → 2.4.0-alpha.10

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 (224) hide show
  1. package/README.md +934 -40
  2. package/build/commands/authz_catalog_diff.d.ts.map +1 -1
  3. package/build/commands/authz_catalog_diff.js +1 -2
  4. package/build/commands/authz_catalog_diff.js.map +1 -1
  5. package/build/commands/authz_catalog_prune_orphans.d.ts +78 -0
  6. package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -0
  7. package/build/commands/authz_catalog_prune_orphans.js +135 -0
  8. package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
  9. package/build/commands/authz_catalog_sync.d.ts +17 -0
  10. package/build/commands/authz_catalog_sync.d.ts.map +1 -1
  11. package/build/commands/authz_catalog_sync.js +27 -4
  12. package/build/commands/authz_catalog_sync.js.map +1 -1
  13. package/build/commands/authz_freeze.d.ts +44 -0
  14. package/build/commands/authz_freeze.d.ts.map +1 -0
  15. package/build/commands/authz_freeze.js +94 -0
  16. package/build/commands/authz_freeze.js.map +1 -0
  17. package/build/commands/authz_reconcile.d.ts +111 -0
  18. package/build/commands/authz_reconcile.d.ts.map +1 -0
  19. package/build/commands/authz_reconcile.js +417 -0
  20. package/build/commands/authz_reconcile.js.map +1 -0
  21. package/build/commands/authz_relations_reconcile.d.ts +144 -0
  22. package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
  23. package/build/commands/authz_relations_reconcile.js +438 -0
  24. package/build/commands/authz_relations_reconcile.js.map +1 -0
  25. package/build/commands/authz_scopes_relay.d.ts +97 -0
  26. package/build/commands/authz_scopes_relay.d.ts.map +1 -0
  27. package/build/commands/authz_scopes_relay.js +344 -0
  28. package/build/commands/authz_scopes_relay.js.map +1 -0
  29. package/build/commands/authz_scopes_requeue.d.ts +80 -0
  30. package/build/commands/authz_scopes_requeue.d.ts.map +1 -0
  31. package/build/commands/authz_scopes_requeue.js +230 -0
  32. package/build/commands/authz_scopes_requeue.js.map +1 -0
  33. package/build/commands/authz_unfreeze.d.ts +45 -0
  34. package/build/commands/authz_unfreeze.d.ts.map +1 -0
  35. package/build/commands/authz_unfreeze.js +157 -0
  36. package/build/commands/authz_unfreeze.js.map +1 -0
  37. package/build/commands/main.d.ts +16 -8
  38. package/build/commands/main.d.ts.map +1 -1
  39. package/build/commands/main.js +59 -6
  40. package/build/commands/main.js.map +1 -1
  41. package/build/commands/openfga_provision.d.ts +92 -4
  42. package/build/commands/openfga_provision.d.ts.map +1 -1
  43. package/build/commands/openfga_provision.js +155 -7
  44. package/build/commands/openfga_provision.js.map +1 -1
  45. package/build/configure.d.ts +11 -0
  46. package/build/configure.d.ts.map +1 -1
  47. package/build/configure.js +37 -1
  48. package/build/configure.js.map +1 -1
  49. package/build/index.d.ts +50 -11
  50. package/build/index.d.ts.map +1 -1
  51. package/build/index.js +49 -7
  52. package/build/index.js.map +1 -1
  53. package/build/providers/authz_provider.d.ts +31 -2
  54. package/build/providers/authz_provider.d.ts.map +1 -1
  55. package/build/providers/authz_provider.js +65 -2
  56. package/build/providers/authz_provider.js.map +1 -1
  57. package/build/services/relations.d.ts +14 -0
  58. package/build/services/relations.d.ts.map +1 -0
  59. package/build/services/relations.js +17 -0
  60. package/build/services/relations.js.map +1 -0
  61. package/build/src/{catalog.d.ts → catalog/catalog.d.ts} +41 -2
  62. package/build/src/catalog/catalog.d.ts.map +1 -0
  63. package/build/src/{catalog.js → catalog/catalog.js} +120 -14
  64. package/build/src/catalog/catalog.js.map +1 -0
  65. package/build/src/{catalog_cache.d.ts → catalog/catalog_cache.d.ts} +46 -22
  66. package/build/src/catalog/catalog_cache.d.ts.map +1 -0
  67. package/build/src/{catalog_cache.js → catalog/catalog_cache.js} +53 -43
  68. package/build/src/catalog/catalog_cache.js.map +1 -0
  69. package/build/src/define_config.d.ts +206 -3
  70. package/build/src/define_config.d.ts.map +1 -1
  71. package/build/src/define_config.js +11 -0
  72. package/build/src/define_config.js.map +1 -1
  73. package/build/src/drivers/database_driver.d.ts +128 -7
  74. package/build/src/drivers/database_driver.d.ts.map +1 -1
  75. package/build/src/drivers/database_driver.js +510 -24
  76. package/build/src/drivers/database_driver.js.map +1 -1
  77. package/build/src/drivers/database_relations_driver.d.ts +140 -0
  78. package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
  79. package/build/src/drivers/database_relations_driver.js +748 -0
  80. package/build/src/drivers/database_relations_driver.js.map +1 -0
  81. package/build/src/drivers/openfga_driver.d.ts +777 -122
  82. package/build/src/drivers/openfga_driver.d.ts.map +1 -1
  83. package/build/src/drivers/openfga_driver.js +2228 -477
  84. package/build/src/drivers/openfga_driver.js.map +1 -1
  85. package/build/src/drivers/openfga_facts.d.ts +384 -0
  86. package/build/src/drivers/openfga_facts.d.ts.map +1 -0
  87. package/build/src/drivers/openfga_facts.js +836 -0
  88. package/build/src/drivers/openfga_facts.js.map +1 -0
  89. package/build/src/drivers/openfga_relations_driver.d.ts +186 -0
  90. package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
  91. package/build/src/drivers/openfga_relations_driver.js +724 -0
  92. package/build/src/drivers/openfga_relations_driver.js.map +1 -0
  93. package/build/src/errors.d.ts +430 -6
  94. package/build/src/errors.d.ts.map +1 -1
  95. package/build/src/errors.js +453 -6
  96. package/build/src/errors.js.map +1 -1
  97. package/build/src/freeze.d.ts +200 -0
  98. package/build/src/freeze.d.ts.map +1 -0
  99. package/build/src/freeze.js +306 -0
  100. package/build/src/freeze.js.map +1 -0
  101. package/build/src/http/app_access_middleware.d.ts.map +1 -0
  102. package/build/src/http/app_access_middleware.js.map +1 -0
  103. package/build/src/http/resource_access_middleware.d.ts +105 -0
  104. package/build/src/http/resource_access_middleware.d.ts.map +1 -0
  105. package/build/src/http/resource_access_middleware.js +81 -0
  106. package/build/src/http/resource_access_middleware.js.map +1 -0
  107. package/build/src/identity.d.ts +74 -1
  108. package/build/src/identity.d.ts.map +1 -1
  109. package/build/src/identity.js +100 -2
  110. package/build/src/identity.js.map +1 -1
  111. package/build/src/manager.d.ts +549 -6
  112. package/build/src/manager.d.ts.map +1 -1
  113. package/build/src/manager.js +2243 -193
  114. package/build/src/manager.js.map +1 -1
  115. package/build/src/models/authz_assignment.d.ts +7 -7
  116. package/build/src/models/authz_assignment.d.ts.map +1 -1
  117. package/build/src/models/authz_deny.d.ts +7 -7
  118. package/build/src/models/authz_deny.d.ts.map +1 -1
  119. package/build/src/models/authz_permission.d.ts +7 -7
  120. package/build/src/models/authz_permission.d.ts.map +1 -1
  121. package/build/src/models/authz_role.d.ts +7 -7
  122. package/build/src/models/authz_role.d.ts.map +1 -1
  123. package/build/src/models/authz_role_permission.d.ts +7 -7
  124. package/build/src/models/authz_role_permission.d.ts.map +1 -1
  125. package/build/src/openfga.d.ts +18 -2
  126. package/build/src/openfga.d.ts.map +1 -1
  127. package/build/src/openfga.js +15 -1
  128. package/build/src/openfga.js.map +1 -1
  129. package/build/src/reconcile.d.ts +37 -0
  130. package/build/src/reconcile.d.ts.map +1 -0
  131. package/build/src/reconcile.js +69 -0
  132. package/build/src/reconcile.js.map +1 -0
  133. package/build/src/relation_partition_trigger.d.ts +8 -0
  134. package/build/src/relation_partition_trigger.d.ts.map +1 -0
  135. package/build/src/relation_partition_trigger.js +93 -0
  136. package/build/src/relation_partition_trigger.js.map +1 -0
  137. package/build/src/relations/define_relations_config.d.ts +109 -0
  138. package/build/src/relations/define_relations_config.d.ts.map +1 -0
  139. package/build/src/relations/define_relations_config.js +211 -0
  140. package/build/src/relations/define_relations_config.js.map +1 -0
  141. package/build/src/relations/manager.d.ts +138 -0
  142. package/build/src/relations/manager.d.ts.map +1 -0
  143. package/build/src/relations/manager.js +494 -0
  144. package/build/src/relations/manager.js.map +1 -0
  145. package/build/src/relations/reconcile.d.ts +137 -0
  146. package/build/src/relations/reconcile.d.ts.map +1 -0
  147. package/build/src/relations/reconcile.js +294 -0
  148. package/build/src/relations/reconcile.js.map +1 -0
  149. package/build/src/relations_config_store.d.ts +22 -0
  150. package/build/src/relations_config_store.d.ts.map +1 -0
  151. package/build/src/relations_config_store.js +74 -0
  152. package/build/src/relations_config_store.js.map +1 -0
  153. package/build/src/scope_outbox.d.ts +123 -0
  154. package/build/src/scope_outbox.d.ts.map +1 -0
  155. package/build/src/scope_outbox.js +532 -0
  156. package/build/src/scope_outbox.js.map +1 -0
  157. package/build/src/{drivers → shared}/backend_guard.d.ts +42 -0
  158. package/build/src/shared/backend_guard.d.ts.map +1 -0
  159. package/build/src/{drivers → shared}/backend_guard.js +78 -1
  160. package/build/src/shared/backend_guard.js.map +1 -0
  161. package/build/src/{drivers → shared}/sql_expiry.d.ts +2 -0
  162. package/build/src/shared/sql_expiry.d.ts.map +1 -0
  163. package/build/src/{drivers → shared}/sql_expiry.js +4 -0
  164. package/build/src/shared/sql_expiry.js.map +1 -0
  165. package/build/src/shared/transaction_guard.d.ts +53 -0
  166. package/build/src/shared/transaction_guard.d.ts.map +1 -0
  167. package/build/src/shared/transaction_guard.js +74 -0
  168. package/build/src/shared/transaction_guard.js.map +1 -0
  169. package/build/src/sql_descendants.d.ts +55 -1
  170. package/build/src/sql_descendants.d.ts.map +1 -1
  171. package/build/src/sql_descendants.js +75 -1
  172. package/build/src/sql_descendants.js.map +1 -1
  173. package/build/src/testing/contract.d.ts +129 -4
  174. package/build/src/testing/contract.d.ts.map +1 -1
  175. package/build/src/testing/contract.js +915 -177
  176. package/build/src/testing/contract.js.map +1 -1
  177. package/build/src/testing/main.d.ts +10 -2
  178. package/build/src/testing/main.d.ts.map +1 -1
  179. package/build/src/testing/main.js +5 -1
  180. package/build/src/testing/main.js.map +1 -1
  181. package/build/src/testing/migration_contract.d.ts +293 -0
  182. package/build/src/testing/migration_contract.d.ts.map +1 -0
  183. package/build/src/testing/migration_contract.js +589 -0
  184. package/build/src/testing/migration_contract.js.map +1 -0
  185. package/build/src/testing/relations_contract.d.ts +73 -0
  186. package/build/src/testing/relations_contract.d.ts.map +1 -0
  187. package/build/src/testing/relations_contract.js +1238 -0
  188. package/build/src/testing/relations_contract.js.map +1 -0
  189. package/build/src/testing/relations_reconcile_contract.d.ts +24 -0
  190. package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -0
  191. package/build/src/testing/relations_reconcile_contract.js +220 -0
  192. package/build/src/testing/relations_reconcile_contract.js.map +1 -0
  193. package/build/src/testing/scope_outbox_contract.d.ts +98 -0
  194. package/build/src/testing/scope_outbox_contract.d.ts.map +1 -0
  195. package/build/src/testing/scope_outbox_contract.js +677 -0
  196. package/build/src/testing/scope_outbox_contract.js.map +1 -0
  197. package/build/src/traits/authz_scopes.js +1 -1
  198. package/build/src/traits/authz_scopes.js.map +1 -1
  199. package/build/src/traits/has_uuid.d.ts +8 -8
  200. package/build/src/traits/has_uuid.d.ts.map +1 -1
  201. package/build/src/types.d.ts +1537 -84
  202. package/build/src/types.d.ts.map +1 -1
  203. package/build/src/types.js +65 -0
  204. package/build/src/types.js.map +1 -1
  205. package/build/stubs/config/authorization.stub +149 -5
  206. package/build/stubs/migration.stub +136 -0
  207. package/build/stubs/scopes_outbox_migration.stub +57 -0
  208. package/package.json +4 -2
  209. package/build/commands/openfga_import.d.ts +0 -34
  210. package/build/commands/openfga_import.d.ts.map +0 -1
  211. package/build/commands/openfga_import.js +0 -97
  212. package/build/commands/openfga_import.js.map +0 -1
  213. package/build/src/catalog.d.ts.map +0 -1
  214. package/build/src/catalog.js.map +0 -1
  215. package/build/src/catalog_cache.d.ts.map +0 -1
  216. package/build/src/catalog_cache.js.map +0 -1
  217. package/build/src/drivers/backend_guard.d.ts.map +0 -1
  218. package/build/src/drivers/backend_guard.js.map +0 -1
  219. package/build/src/drivers/sql_expiry.d.ts.map +0 -1
  220. package/build/src/drivers/sql_expiry.js.map +0 -1
  221. package/build/src/middleware/app_access_middleware.d.ts.map +0 -1
  222. package/build/src/middleware/app_access_middleware.js.map +0 -1
  223. /package/build/src/{middleware → http}/app_access_middleware.d.ts +0 -0
  224. /package/build/src/{middleware → http}/app_access_middleware.js +0 -0
@@ -0,0 +1,438 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { BaseCommand, flags } from '@adonisjs/core/ace';
11
+ import { AuthorizationConfigError, StaleSourceRefusedError } from '../src/errors.js';
12
+ /**
13
+ * Límites DECLARADOS de la identidad (cierre-2 de alpha.10 · C1 y C6):
14
+ * - la identidad es la REFERENCIA de la factory: dos factories DISTINTAS que
15
+ * construyen el mismo backend (`fga: () => new OpenFgaRelationsDriver(…mismo
16
+ * store…)`) NO se detectan — el alias pasa el guard. No se construye nada antes
17
+ * del guard, así que no hay backend que comparar; identidad declarada por el
18
+ * driver ⇒ roadmap 2.5. Registra un alias con la MISMA referencia
19
+ * (`const fga = () => …; drivers: { openfga: fga, alias: fga }`) o no lo registres.
20
+ * - `authorization` es estructural (`Pick<…>`): un objeto que FALSEA
21
+ * `relationsDriverKey`/`relationsDriverFactory` decide el activo y las factories.
22
+ * Pásale el manager del servicio, nunca uno construido o envuelto a mano.
23
+ */
24
+ export async function runRelationsReconcile(authorization, options) {
25
+ const { reconcileRelations } = await import('../src/relations/reconcile.js');
26
+ const raw = options;
27
+ // Entradas que ya no gobiernan nada (L5: una opción inerte no cae en un
28
+ // default en silencio). `from`/`to` como instancias y `activeKey` eran
29
+ // justo las etiquetas desalineables del R4.
30
+ const retiredOptions = ['from', 'to', 'activeKey'].filter((name) => raw[name] !== undefined);
31
+ if (retiredOptions.length > 0) {
32
+ throw new AuthorizationConfigError(`runRelationsReconcile: ${retiredOptions.map((n) => `\`${n}\``).join(', ')} ya no se acepta(n) (cierre de 2.4.0-alpha.10): ` +
33
+ 'el origen y el destino se resuelven por `fromKey`/`toKey` desde el config del manager, y el driver activo lo ' +
34
+ 'dice el manager (`relations.default ?? default`). Pasar instancias o un activo propio permitía que la etiqueta ' +
35
+ 'contradijera al driver y la dirección rancia pasara.');
36
+ }
37
+ if (typeof options.toKey !== 'string' || typeof options.fromKey !== 'string') {
38
+ throw new AuthorizationConfigError('runRelationsReconcile: hacen falta `toKey` y `fromKey` (las claves de relations.drivers del destino y del ORIGEN) ' +
39
+ 'desde 2.4.0-alpha.10 — sin ellas no se puede negar la dirección rancia (re-escribir en el driver de relaciones ' +
40
+ 'ACTIVO lo que el origen dejó de ver en el cutover).');
41
+ }
42
+ const { toKey, fromKey, allowStaleSource, ...rest } = options;
43
+ const toFactory = authorization.relationsDriverFactory(toKey);
44
+ const fromFactory = authorization.relationsDriverFactory(fromKey);
45
+ for (const [role, key, factory] of [['destino', toKey, toFactory], ['origen', fromKey, fromFactory]]) {
46
+ if (typeof factory !== 'function') {
47
+ throw new AuthorizationConfigError(`runRelationsReconcile: el ${role} '${key}' no es una clave de relations.drivers en el config del manager.`);
48
+ }
49
+ }
50
+ const active = authorization.relationsDriverKey();
51
+ /** Mismo driver ⇔ misma clave o misma factory resuelta (un alias es el mismo driver). */
52
+ const sameDriver = (a, b) => a === b || authorization.relationsDriverFactory(a) === authorization.relationsDriverFactory(b);
53
+ if (sameDriver(fromKey, toKey)) {
54
+ throw new AuthorizationConfigError(`runRelationsReconcile: el origen '${fromKey}' y el destino '${toKey}' son el MISMO driver ` +
55
+ `(${fromKey === toKey ? 'misma clave' : 'la misma factory bajo dos claves'}): no hay nada que migrar y un --prune ` +
56
+ 'se compararía consigo mismo. Nombra el otro driver.');
57
+ }
58
+ const stale = sameDriver(toKey, active);
59
+ const allowStale = allowStaleSource === true;
60
+ if (stale && !allowStale && options.dryRun !== true) {
61
+ const alias = toKey === active ? '' : ` (la clave '${toKey}' resuelve la MISMA factory que el activo '${active}')`;
62
+ throw new StaleSourceRefusedError(`authz:relations:reconcile --to=${toKey} (origen '${fromKey}'): '${toKey}' es el driver de ` +
63
+ `relaciones ACTIVO (relations.default ?? default)${alias} y el origen no lo es. Esa dirección re-escribiría en el driver ` +
64
+ `activo tuplas que el origen ya no refleja: resucitaría las comparticiones retiradas desde el cutover (y con ` +
65
+ `--prune borraría las creadas después). No se ha construido, leído, congelado ni escrito nada. Tras el cutover el driver ` +
66
+ `activo ES la fuente de verdad de las relaciones: no hay nada que migrar hacia él; para volver atrás, migra en ` +
67
+ `la otra dirección (--to=${fromKey} --from=${toKey}) ANTES de cambiar relations.default/default. ` +
68
+ `Si de verdad el origen sigue siendo la fuente de verdad, la decisión humana es --allow-stale-source ` +
69
+ `(allowStaleSource: true).`);
70
+ }
71
+ const from = await fromFactory();
72
+ const to = await toFactory();
73
+ const pass = () => reconcileRelations({ ...rest, from, to });
74
+ const mark = (report) => {
75
+ if (stale)
76
+ report.staleSource = { to: toKey, from: fromKey, allowed: allowStale };
77
+ return report;
78
+ };
79
+ if (options.dryRun === true)
80
+ return mark(await pass());
81
+ const reason = `authz:relations:reconcile --to=${toKey}`;
82
+ return authorization.withFrozenWrites(reason, async (window) => {
83
+ const report = mark(await pass());
84
+ report.frozen = {
85
+ durable: true,
86
+ lapsed: await window.lapsed(),
87
+ leaseMs: window.leaseMs,
88
+ fence: window.fence,
89
+ ...(window.untilMs === undefined ? {} : { untilMs: window.untilMs }),
90
+ ...(window.window === undefined ? {} : { window: window.window }),
91
+ };
92
+ return report;
93
+ }, { kind: 'reconcile', operatorAsContext: true });
94
+ }
95
+ /**
96
+ * Cómo se nombra la ventana en la que corrió la pasada (2.4.0-alpha.10 · 3):
97
+ * la del OPERADOR dice si caduca y cuándo — hasta alpha.9 una ventana abierta
98
+ * con `--lease-ms` salía como «ventana del operador» a secas, sin caducidad.
99
+ */
100
+ export function frozenWindowLabel(frozen) {
101
+ // alpha.10 · S2: la ventana PROPIA se dice como tal — «no había ventana de
102
+ // operador viva» es justo lo que el operador de un cutover necesita leer.
103
+ if (frozen.window === 'own') {
104
+ return `, ventana PROPIA de la pasada (lease ${frozen.leaseMs} ms): NO había ventana de operador viva`;
105
+ }
106
+ if (frozen.window === 'enclosing')
107
+ return `, ventana que ya sostenía este proceso`;
108
+ if (frozen.untilMs !== undefined) {
109
+ return frozen.untilMs === null
110
+ ? ', ventana del operador sin caducidad'
111
+ : `, ventana del operador que CADUCA el ${new Date(frozen.untilMs).toISOString()} (le quedaban ${frozen.leaseMs} ms)`;
112
+ }
113
+ return frozen.leaseMs === null ? ', ventana del operador' : `, lease ${frozen.leaseMs} ms`;
114
+ }
115
+ /**
116
+ * Las líneas del reporte de `authz:relations:reconcile` y si la pasada es
117
+ * «limpia», en una función PURA (mismo patrón que `reconcileLines` de roles):
118
+ * es lo único del comando que DECIDE algo, y así tiene su caso sin montar un
119
+ * ace.
120
+ *
121
+ * Qué cuenta como deriva —y por tanto como exit ≠ 0—:
122
+ * - **`modelDrift`** (siempre): un tipo de relación presente en el ORIGEN que
123
+ * la config del DESTINO no declara. No cabe en su modelo fusionado, así que
124
+ * esas tuplas nunca resolverían allí — es la deriva del presupuesto de
125
+ * modelo compartido, no un detalle cosmético;
126
+ * - en `--dry-run`, **cualquier cosa que la pasada haría** (escribir, borrar o
127
+ * dejar `extra` sin `--prune`): es el verificador, y para eso está en CI.
128
+ *
129
+ * `extra` en una pasada que SÍ escribe (sin `--prune`) se AVISA —«repite con
130
+ * --prune»— pero no tumba: el operador eligió no podar. Es la misma cortesía
131
+ * que `authz:reconcile` de roles con la ventana del relay.
132
+ */
133
+ export function relationsReconcileLines(report) {
134
+ const lines = [];
135
+ lines.push({
136
+ level: 'log',
137
+ message: `relaciones: escritas ${report.written}, actualizadas ${report.updated}, borradas ${report.deleted}, ` +
138
+ `iguales ${report.unchanged}, sobran ${report.extra}`,
139
+ });
140
+ // R-15: la caducada del origen es la pérdida DECLARADA de la migración (como
141
+ // `expired` en roles, 3b-8 · B2): se dice, no es deriva.
142
+ if (report.skipped.expired > 0) {
143
+ lines.push({
144
+ level: 'log',
145
+ message: `${report.skipped.expired} tupla(s) del origen estaban CADUCADAS al instante de la pasada y no se ` +
146
+ 'migran (skipped.expired): no conceden nada y el destino no las necesita.',
147
+ });
148
+ }
149
+ // L-0: la tupla VIVA que el destino no declara (tipo o relación, F-05) no se
150
+ // escribió y se contó. No es una pérdida declarada como `expired` (que no
151
+ // concede nada): es una relación que no llegó ⇒ deriva, exit ≠ 0.
152
+ if (report.skipped.undeclared > 0) {
153
+ lines.push({
154
+ level: 'error',
155
+ message: `${report.skipped.undeclared} tupla(s) del origen NO se escribieron porque su tipo de objeto o su relación ` +
156
+ 'no están declarados en defineRelationsConfig del DESTINO (skipped.undeclared, F-05 en el driver): ' +
157
+ 'decláralos en el destino (y republica su modelo) y repite la pasada.',
158
+ });
159
+ }
160
+ for (const type of report.modelDrift) {
161
+ lines.push({
162
+ level: 'error',
163
+ message: `El tipo de objeto '${type}' está en el ORIGEN pero NO en la config de relaciones del DESTINO: ` +
164
+ 'no cabe en su modelo fusionado y esas tuplas no resolverían allí. Declara el tipo en ' +
165
+ 'defineRelationsConfig del destino (y republica su modelo) antes de migrarlas.',
166
+ });
167
+ }
168
+ // R-17: el seguro de borrado masivo. En una pasada REAL sin --allow-mass-delete
169
+ // esto ya habría lanzado 500 antes de llegar aquí; en --dry-run NO lanza y lo
170
+ // marca, así que el verificador tiene que sacarlo como deriva (exit ≠ 0).
171
+ if (report.massDelete) {
172
+ lines.push({
173
+ level: 'error',
174
+ message: `--prune borraría ${report.extra || report.deleted} tupla(s) del destino y el ORIGEN no aportó NI UNA ` +
175
+ 'utilizable: la firma de un --from equivocado o de un store vacío. En una pasada real es 500 ' +
176
+ 'E_AUTHZ_MASS_RECONCILE_REFUSED; si de verdad quieres vaciar el destino, --allow-mass-delete.',
177
+ });
178
+ }
179
+ if (report.extra > 0 && !report.dryRun) {
180
+ lines.push({
181
+ level: 'warning',
182
+ message: `${report.extra} tupla(s) del destino que el origen ya no respalda siguen ahí (no pasaste --prune): ` +
183
+ 'repite con --prune si quieres borrarlas.',
184
+ });
185
+ }
186
+ // L-1 · J1: la garantía del freeze se DEMUESTRA (paridad con roles, 3b-7,
187
+ // juez C4). Un lease perdido a mitad = escrituras pudieron entrar durante la
188
+ // pasada y no aparecen en ningún contador ⇒ la pasada NO se certifica.
189
+ if (report.frozen) {
190
+ if (report.frozen.lapsed) {
191
+ lines.push({
192
+ level: 'error',
193
+ message: `El LEASE del freeze se perdió a MITAD de la pasada (fence ${report.frozen.fence}): otras escrituras de ` +
194
+ 'relaciones o de roles pudieron entrar mientras se reconciliaba y no están en estos contadores. ' +
195
+ 'La pasada NO se certifica: repite authz:relations:reconcile.',
196
+ });
197
+ }
198
+ else {
199
+ lines.push({
200
+ level: 'log',
201
+ message: `Escrituras congeladas durante la pasada (fence ${report.frozen.fence}` +
202
+ `${frozenWindowLabel(report.frozen)}): ` +
203
+ 'la ventana aguantó entera.',
204
+ });
205
+ }
206
+ }
207
+ // alpha.10 · S1: la dirección rancia se dice siempre; en el verificador es deriva.
208
+ if (report.staleSource !== undefined) {
209
+ const { to, from, allowed } = report.staleSource;
210
+ lines.push({
211
+ level: 'warning',
212
+ message: `dirección RANCIA: '${to}' es el driver de relaciones ACTIVO y las tuplas salen de '${from}', que dejó de verlas ` +
213
+ 'en el cutover — esta pasada resucita las comparticiones retiradas desde entonces (y con --prune borra las ' +
214
+ 'nuevas). ' +
215
+ (allowed
216
+ ? 'Levantada a sabiendas con --allow-stale-source.'
217
+ : 'Sin --allow-stale-source la pasada de verdad se niega (E_AUTHZ_STALE_SOURCE_REFUSED).'),
218
+ });
219
+ }
220
+ const cambios = report.written + report.updated + report.deleted + (report.dryRun ? report.extra : 0);
221
+ const clean = !(report.dryRun && report.staleSource !== undefined && !report.staleSource.allowed) &&
222
+ report.modelDrift.length === 0 &&
223
+ report.skipped.undeclared === 0 &&
224
+ !report.massDelete &&
225
+ !(report.frozen?.lapsed === true) &&
226
+ (!report.dryRun || cambios === 0);
227
+ return { lines, clean };
228
+ }
229
+ /**
230
+ * **`authz:relations:reconcile`** (Fase 4, lote 4-6) — migra las tuplas de
231
+ * RELACIÓN de un `RelationsDriver` a otro, idempotente y bidireccional, nunca
232
+ * silenciosa. Es el comando de plataforma que envuelve `reconcileRelations`
233
+ * (lote 4-5), análogo a `authz:reconcile` de roles pero para el puerto
234
+ * `RelationsDriver`.
235
+ *
236
+ * node ace authz:relations:reconcile --to=openfga --dry-run # el VERIFICADOR
237
+ * node ace authz:relations:reconcile --to=openfga # migra
238
+ * node ace authz:relations:reconcile --to=openfga --prune # y borra lo que el origen ya no respalda
239
+ *
240
+ * `--to`/`--from` nombran claves de `relations.drivers` en
241
+ * `config/authorization.ts` (NO las de `drivers` de roles): las relaciones
242
+ * tienen su propio puerto y sus propios drivers. La factory del driver
243
+ * `openfga` de relaciones entra por el subpath `/openfga` DENTRO de ella, así
244
+ * que este comando NUNCA importa el SDK (pureza).
245
+ *
246
+ * **Solo migra HECHOS de relación**: no hay árbol ni catálogo en `relations/`,
247
+ * así que —a diferencia de `authz:reconcile` de roles— no hay fases de marcador
248
+ * de raíz, proyección ni árbol. La migración es **POR partición**
249
+ * (mono-tenant: `APP_SCOPE`, el default); para migrar otra partición se pasan
250
+ * `--partition-type`/`--partition-uuid`, y migrar TODAS es iterar por el
251
+ * consumidor (una migración de tenant es un caso natural).
252
+ *
253
+ * **`--dry-run` es el verificador y es read-only por contrato** (como el de
254
+ * roles, S18): mismo recorrido, cero escrituras, los mismos números; NO hay
255
+ * `--fix`. Además vigila la **deriva del modelo fusionado** (`modelDrift`): un
256
+ * tipo del origen que el destino no declara sale como error.
257
+ *
258
+ * Es una operación de PLATAFORMA: no lleva actor, no se expone por HTTP.
259
+ */
260
+ export default class AuthzRelationsReconcile extends BaseCommand {
261
+ static commandName = 'authz:relations:reconcile';
262
+ static description = 'Rebuild (or verify, with --dry-run) the relation tuples of one RelationsDriver from another';
263
+ static options = {
264
+ startApp: true,
265
+ };
266
+ async run() {
267
+ if (!this.to) {
268
+ this.logger.error("Falta --to: 'node ace authz:relations:reconcile --to=openfga'. Es la clave del driver DESTINO en " +
269
+ '`relations.drivers` de config/authorization.ts.');
270
+ this.exitCode = 1;
271
+ return;
272
+ }
273
+ // Cierre del auditor de alpha.10 · R4: las claves, las factories y el driver
274
+ // ACTIVO salen del MISMO sitio — el manager de servicio (construido desde
275
+ // config/authorization.ts) —, que es también quien toma la ventana. Hasta el
276
+ // cierre el comando resolvía los drivers de `this.app.config` y le pasaba a
277
+ // `runRelationsReconcile` las instancias y, aparte, las etiquetas.
278
+ const { default: authorization } = await import('../services/main.js');
279
+ const keys = authorization.relationsDriverKeys();
280
+ if (keys.length === 0) {
281
+ this.logger.error('No hay `relations.drivers` en config/authorization.ts: declara los drivers de relaciones (por clave) ' +
282
+ 'para poder migrar sus tuplas.');
283
+ this.exitCode = 1;
284
+ return;
285
+ }
286
+ // El ORIGEN: `--from` si se pasa; si no, el ÚNICO que no es el destino.
287
+ let fromKey = this.from;
288
+ if (!fromKey) {
289
+ const candidates = keys.filter((k) => k !== this.to);
290
+ if (candidates.length !== 1) {
291
+ this.logger.error(`No se puede elegir el ORIGEN solo: hay ${candidates.length} candidatos (${candidates.join(', ') || 'ninguno'}). ` +
292
+ 'Nómbralo con --from=<clave>: de dónde salen las tuplas decide lo que queda escrito.');
293
+ this.exitCode = 1;
294
+ return;
295
+ }
296
+ fromKey = candidates[0];
297
+ }
298
+ if (authorization.relationsDriverFactory(this.to) === undefined) {
299
+ this.logger.error(`El destino '${this.to}' no es una clave de relations.drivers (${keys.join(', ')}).`);
300
+ this.exitCode = 1;
301
+ return;
302
+ }
303
+ if (authorization.relationsDriverFactory(fromKey) === undefined) {
304
+ this.logger.error(`El origen '${fromKey}' no es una clave de relations.drivers (${keys.join(', ')}).`);
305
+ this.exitCode = 1;
306
+ return;
307
+ }
308
+ // La partición (mono-tenant por defecto). El reconcile es POR partición.
309
+ // Las DOS banderas van juntas o no va ninguna (cierre de alpha.9, 🟡 B del
310
+ // auditor): media pareja reconciliaba `app` en silencio, escribía CERO y
311
+ // salía `[success]` — la misma forma del 🔴 «migra 0 y reporta éxito» que
312
+ // este lote cierra, sobreviviendo a una errata del runbook de cutover.
313
+ if (this.partitionUuid && !this.partitionType) {
314
+ this.logger.error('--partition-uuid sin --partition-type: las dos banderas van juntas. ' +
315
+ 'Sin --partition-type se reconciliaría la partición RAÍZ (app) y tus tuplas por tenant no se migrarían.');
316
+ this.exitCode = 1;
317
+ return;
318
+ }
319
+ // alpha.10 · 5 (sexto informe de COGNITIV): la variante que el cierre de
320
+ // alpha.9 no cazó — pareja COMPLETA con `app` también reconciliaba la raíz
321
+ // en silencio, tirando el uuid (una errata del runbook: `app` por el tipo).
322
+ if (this.partitionType === 'app' && this.partitionUuid) {
323
+ this.logger.error(`--partition-type=app con --partition-uuid=${this.partitionUuid}: la partición raíz no lleva uuid. ` +
324
+ 'O quitas --partition-uuid (reconciliar la raíz) o pones el tipo del tenant en --partition-type.');
325
+ this.exitCode = 1;
326
+ return;
327
+ }
328
+ if (this.partitionType && this.partitionType !== 'app' && !this.partitionUuid) {
329
+ this.logger.error(`--partition-type=${this.partitionType} exige --partition-uuid (el tenant concreto a reconciliar).`);
330
+ this.exitCode = 1;
331
+ return;
332
+ }
333
+ const partition = this.partitionType && this.partitionType !== 'app'
334
+ ? { type: this.partitionType, uuid: this.partitionUuid }
335
+ : { type: 'app', uuid: null };
336
+ // La config del DESTINO, para vigilar la deriva del modelo fusionado en
337
+ // --dry-run: es la persistida (una sola en el store compartido).
338
+ const { readRelationsConfig } = await import('../src/relations_config_store.js');
339
+ const toConfig = await readRelationsConfig().catch(() => undefined);
340
+ // L-1 · J1: la pasada que ESCRIBE corre bajo la ventana durable del
341
+ // manager de roles (la misma fila `id = 2`, toda la flota); `--dry-run` no.
342
+ // Los drivers los construye `runRelationsReconcile`, DESPUÉS del guard.
343
+ let report;
344
+ try {
345
+ report = await runRelationsReconcile(authorization, {
346
+ partition,
347
+ dryRun: this.dryRun === true,
348
+ prune: this.prune === true,
349
+ allowMassDelete: this.allowMassDelete === true,
350
+ allowStaleSource: this.allowStaleSource === true,
351
+ toKey: this.to,
352
+ fromKey,
353
+ ...(toConfig ? { toConfig } : {}),
354
+ });
355
+ }
356
+ catch (error) {
357
+ // R-17: el seguro de borrado masivo (500) sale como error de comando, no
358
+ // como stack crudo. `--allow-mass-delete` es la salida humana.
359
+ if (error?.code === 'E_AUTHZ_MASS_RECONCILE_REFUSED' || error?.code === 'E_AUTHZ_STALE_SOURCE_REFUSED') {
360
+ this.logger.error(String(error.message ?? error));
361
+ this.exitCode = 1;
362
+ return;
363
+ }
364
+ // L-1 · J1: un freeze vivo de OTRA pasada (423): dos migraciones no se
365
+ // pisan; el mensaje nombra al dueño y cómo se levanta.
366
+ if (error?.code === 'E_AUTHZ_FREEZE_HELD' || error?.code === 'E_AUTHZ_OPERATOR_WINDOW_EXPIRED') {
367
+ this.logger.error(String(error.message ?? error));
368
+ this.exitCode = 1;
369
+ return;
370
+ }
371
+ // Cierre R4: origen y destino que resuelven el MISMO driver (misma clave o
372
+ // alias de la misma factory) es un error de configuración de la pasada.
373
+ if (error?.code === 'E_AUTHZ_CONFIG') {
374
+ this.logger.error(String(error.message ?? error));
375
+ this.exitCode = 1;
376
+ return;
377
+ }
378
+ throw error;
379
+ }
380
+ const { lines, clean } = relationsReconcileLines(report);
381
+ for (const { level, message } of lines)
382
+ this.logger[level](message);
383
+ const resumen = `escritas ${report.written}, actualizadas ${report.updated}, borradas ${report.deleted}, ` +
384
+ `iguales ${report.unchanged}, sobran ${report.extra}, caducadas omitidas ${report.skipped.expired}, ` +
385
+ `no declaradas en el destino ${report.skipped.undeclared}`;
386
+ if (report.dryRun) {
387
+ if (clean) {
388
+ this.logger.success(`Sin deriva: '${this.to}' coincide con '${fromKey}' (${resumen}).`);
389
+ return;
390
+ }
391
+ this.logger.error(`DERIVA con '${this.to}': ${resumen}. No se ha escrito nada (--dry-run).`);
392
+ this.exitCode = 1;
393
+ return;
394
+ }
395
+ this.logger.success(`Relaciones de '${this.to}' reconciliadas desde '${fromKey}': ${resumen}.`);
396
+ if (!clean) {
397
+ this.logger.warning('Queda algo que esta pasada no arregla sola (mira las líneas de arriba).');
398
+ this.exitCode = 1;
399
+ }
400
+ }
401
+ }
402
+ __decorate([
403
+ flags.string({ description: 'Destination driver: a key of `relations.drivers` in config/authorization.ts' }),
404
+ __metadata("design:type", Object)
405
+ ], AuthzRelationsReconcile.prototype, "to", void 0);
406
+ __decorate([
407
+ flags.string({ description: 'Source driver (a key of `relations.drivers`); required when more than one is registered' }),
408
+ __metadata("design:type", Object)
409
+ ], AuthzRelationsReconcile.prototype, "from", void 0);
410
+ __decorate([
411
+ flags.boolean({ description: 'Report what it would do and write nothing (this is the verifier)' }),
412
+ __metadata("design:type", Object)
413
+ ], AuthzRelationsReconcile.prototype, "dryRun", void 0);
414
+ __decorate([
415
+ flags.boolean({ description: 'Also delete the tuples the source no longer backs (leftovers)' }),
416
+ __metadata("design:type", Object)
417
+ ], AuthzRelationsReconcile.prototype, "prune", void 0);
418
+ __decorate([
419
+ flags.boolean({
420
+ description: 'Allow --prune to empty the destination when the source has no usable tuple (mass-delete safeguard)',
421
+ }),
422
+ __metadata("design:type", Object)
423
+ ], AuthzRelationsReconcile.prototype, "allowMassDelete", void 0);
424
+ __decorate([
425
+ flags.boolean({
426
+ description: 'Allow --to=<ACTIVE relations driver> from another source: rewrites into the serving driver tuples the source no longer reflects',
427
+ }),
428
+ __metadata("design:type", Object)
429
+ ], AuthzRelationsReconcile.prototype, "allowStaleSource", void 0);
430
+ __decorate([
431
+ flags.string({ description: 'Partition (tenant) scope type to migrate; default: app (mono-tenant)' }),
432
+ __metadata("design:type", Object)
433
+ ], AuthzRelationsReconcile.prototype, "partitionType", void 0);
434
+ __decorate([
435
+ flags.string({ description: 'Partition (tenant) scope uuid; required with --partition-type' }),
436
+ __metadata("design:type", Object)
437
+ ], AuthzRelationsReconcile.prototype, "partitionUuid", void 0);
438
+ //# sourceMappingURL=authz_relations_reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz_relations_reconcile.js","sourceRoot":"","sources":["../../commands/authz_relations_reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { CommandOptions } from '@adonisjs/core/types/ace';
3
+ import type { ScopeRelayQueue } from '../src/types.js';
4
+ /**
5
+ * **La edad de la cola en el comando** (2.4.0-alpha.8; panel relay 2,
6
+ * cuestión 5), en una función PURA (patrón `requeueLines`): qué se imprime y
7
+ * si `--max-age` la convierte en salida ≠ 0.
8
+ *
9
+ * - Sin cabeza con fecha (`pendingAgeMs === null`: cola vacía, solo
10
+ * aparcadas, o una outbox de terceros sin `createdAt`) no se dice nada y
11
+ * nunca se alarma.
12
+ * - Con edad, se INFORMA siempre (es lo que faltaba en el B2 de COGNITIV:
13
+ * diez pasadas con el store caído y nadie sabía desde cuándo).
14
+ * - **Solo con umbral** (`--max-age=<ms>`) y edad POR ENCIMA es `exceeded`
15
+ * (exit ≠ 0): sin umbral un cron no puede quedarse en rojo para siempre
16
+ * por una cola que simplemente tiene backlog. Un umbral inválido lanza.
17
+ */
18
+ export declare function queueAgeLines(queue: ScopeRelayQueue, maxAgeMs: number | undefined, remaining?: boolean): {
19
+ lines: Array<{
20
+ level: 'info' | 'warning' | 'error';
21
+ message: string;
22
+ }>;
23
+ exceeded: boolean;
24
+ };
25
+ /**
26
+ * Drena la OUTBOX del árbol de scopes y aplica los cambios al driver
27
+ * (3b-2d; panel 2, cruce 4 · S5).
28
+ *
29
+ * Con `scopes.outbox` declarada, `authorization.scopes.attached/moved/
30
+ * detached` no escriben en el backend: encolan el cambio DENTRO de la
31
+ * transacción del consumidor. Este comando es quien lo propaga.
32
+ *
33
+ * node ace authz:scopes:relay # drena
34
+ * node ace authz:scopes:relay --dry-run # solo dice qué haría
35
+ * node ace authz:scopes:relay --limit 500 # una pasada acotada
36
+ * node ace authz:scopes:relay --only=12,13 # SOLO esas entradas (2.4.0-alpha.5)
37
+ * node ace authz:scopes:relay --wait-lease-ms=5000 # espera el lease hasta 5 s en vez de salir `busy` (2.4.0-alpha.8)
38
+ * node ace authz:scopes:relay --max-age=300000 # exit ≠ 0 si la pendiente más antigua lleva > 5 min (2.4.0-alpha.8)
39
+ *
40
+ * **`--max-age`** (alpha.8, cuestión 5 del panel relay 2): el reporte lleva
41
+ * SIEMPRE la edad de la cabeza global de la cola (`queue.pendingAgeMs`, con
42
+ * el reloj de quien encoló) y se imprime; con umbral, superarlo es salida ≠ 0
43
+ * — la alarma que faltaba cuando el store está caído (un 503 no consume
44
+ * intento: `attempts` y `dead` se quedan a cero). Sin umbral solo informa.
45
+ *
46
+ * **`--only`** (alpha.5 · R1): releva solo los ids del recibo que devolvió
47
+ * `scopes.attached/moved/detached` (`{ queued }`). El orden por clave no se
48
+ * relaja: una entrada pedida que dependa de una anterior sin aplicar y no
49
+ * pedida sale «aplazada» (`depends-on:<id>`), nunca fuera de orden; `--only`
50
+ * vacío es un error, no «todo». La salida es POR ENTRADA (`entries`), y los
51
+ * ids que no estaban ni pendientes ni aparcados se dicen (`missing`).
52
+ *
53
+ * **Lo aparcado tapona por clave** (alpha.5 · R2a): un `attached`/`moved`
54
+ * que nombre un scope de una entrada APARCADA sale «aplazado» con
55
+ * `parked:<id>` en todas las pasadas hasta que alguien mire la aparcada; un
56
+ * `detached` de esa clave se aplica igual (una purga nunca queda bloqueada).
57
+ * Y un 503 del backend NO consume intento: la caída del store es común a
58
+ * todas las entradas y no dice nada de ninguna.
59
+ *
60
+ * Pensado para un supervisor (un bucle, un cron corto, un worker): la pasada
61
+ * es REANUDABLE —lo que no entra sigue pendiente—. Un cambio que falla
62
+ * **aplaza lo que depende de él** (todo cambio posterior que nombre alguno de
63
+ * sus scopes) y deja pasar el resto: hasta 3b-2h paraba la pasada entera, y
64
+ * eso convertía una entrada irreparable en un tapón permanente para todos los
65
+ * tenants (3b-2h · 🔴 2). Sale con código ≠ 0 si algo falló o hay entradas
66
+ * APARCADAS, para que el supervisor se entere.
67
+ *
68
+ * **Escritor ÚNICO**: si la outbox sabe dar lease (`sqlScopeOutbox` lo hace),
69
+ * una segunda pasada simultánea no hace nada y lo dice. Sin lease, no lances
70
+ * dos a la vez: la rezagada re-aplica cambios viejos sobre el árbol nuevo.
71
+ *
72
+ * **Lo que este comando no puede arreglar**: entre el commit del consumidor
73
+ * y la pasada hay un lag (segundos) en el que el backend decide con el árbol
74
+ * VIEJO. Es un fail-open temporal —el tenant antiguo conserva acceso tras un
75
+ * `moved`, los denies heredados no aplican tras un `attached`—. No hay 2PC.
76
+ * Un ciclo más corto acorta esa ventana **mientras la cola avance**: lo que
77
+ * falla o se aparca no está acotado por el ciclo y hay que mirarlo.
78
+ *
79
+ * Es una operación de PLATAFORMA (como `authz:catalog:prune-orphans`): se
80
+ * salta `requireActor`/`requireWithin` porque la policy ya se juzgó al
81
+ * ENCOLAR, con el árbol y la sesión de aquel momento. No se expone por HTTP.
82
+ */
83
+ export default class AuthzScopesRelay extends BaseCommand {
84
+ static commandName: string;
85
+ static description: string;
86
+ static options: CommandOptions;
87
+ dryRun: boolean | undefined;
88
+ limit: number | undefined;
89
+ batchSize: number | undefined;
90
+ only: string[] | undefined;
91
+ waitLeaseMs: number | undefined;
92
+ maxAge: number | undefined;
93
+ /** `--only=12,13` o `--only=12 --only=13`: ids numéricos como número, el resto como cadena. */
94
+ static parseOnly(values: string[] | undefined): Array<string | number> | undefined;
95
+ run(): Promise<void>;
96
+ }
97
+ //# sourceMappingURL=authz_scopes_relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz_scopes_relay.d.ts","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,GAAE,OAAe,GACzB;IAAE,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAoC/F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,MAAM,CAAC,WAAW,SAAuB;IACzC,MAAM,CAAC,WAAW,SAAiF;IAEnG,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAKO,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAG3B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAGzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAK7B,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAK1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAK/B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAElC,+FAA+F;IAC/F,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS;IAS5E,GAAG;CAgMV"}