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

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 (210) hide show
  1. package/README.md +462 -35
  2. package/build/commands/authz_catalog_diff.js +1 -1
  3. package/build/commands/authz_catalog_diff.js.map +1 -1
  4. package/build/commands/authz_catalog_prune_orphans.d.ts +78 -0
  5. package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -0
  6. package/build/commands/authz_catalog_prune_orphans.js +136 -0
  7. package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
  8. package/build/commands/authz_catalog_sync.d.ts +17 -0
  9. package/build/commands/authz_catalog_sync.d.ts.map +1 -1
  10. package/build/commands/authz_catalog_sync.js +27 -4
  11. package/build/commands/authz_catalog_sync.js.map +1 -1
  12. package/build/commands/authz_freeze.d.ts +44 -0
  13. package/build/commands/authz_freeze.d.ts.map +1 -0
  14. package/build/commands/authz_freeze.js +95 -0
  15. package/build/commands/authz_freeze.js.map +1 -0
  16. package/build/commands/authz_reconcile.d.ts +102 -0
  17. package/build/commands/authz_reconcile.d.ts.map +1 -0
  18. package/build/commands/authz_reconcile.js +294 -0
  19. package/build/commands/authz_reconcile.js.map +1 -0
  20. package/build/commands/authz_relations_reconcile.d.ts +73 -0
  21. package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
  22. package/build/commands/authz_relations_reconcile.js +225 -0
  23. package/build/commands/authz_relations_reconcile.js.map +1 -0
  24. package/build/commands/authz_scopes_relay.d.ts +47 -0
  25. package/build/commands/authz_scopes_relay.d.ts.map +1 -0
  26. package/build/commands/authz_scopes_relay.js +141 -0
  27. package/build/commands/authz_scopes_relay.js.map +1 -0
  28. package/build/commands/authz_unfreeze.d.ts +37 -0
  29. package/build/commands/authz_unfreeze.d.ts.map +1 -0
  30. package/build/commands/authz_unfreeze.js +92 -0
  31. package/build/commands/authz_unfreeze.js.map +1 -0
  32. package/build/commands/main.d.ts +6 -1
  33. package/build/commands/main.d.ts.map +1 -1
  34. package/build/commands/main.js +6 -1
  35. package/build/commands/main.js.map +1 -1
  36. package/build/commands/openfga_provision.d.ts +46 -4
  37. package/build/commands/openfga_provision.d.ts.map +1 -1
  38. package/build/commands/openfga_provision.js +90 -7
  39. package/build/commands/openfga_provision.js.map +1 -1
  40. package/build/configure.d.ts +11 -0
  41. package/build/configure.d.ts.map +1 -1
  42. package/build/configure.js +37 -1
  43. package/build/configure.js.map +1 -1
  44. package/build/index.d.ts +39 -10
  45. package/build/index.d.ts.map +1 -1
  46. package/build/index.js +35 -6
  47. package/build/index.js.map +1 -1
  48. package/build/providers/authz_provider.d.ts +26 -2
  49. package/build/providers/authz_provider.d.ts.map +1 -1
  50. package/build/providers/authz_provider.js +48 -2
  51. package/build/providers/authz_provider.js.map +1 -1
  52. package/build/services/relations.d.ts +14 -0
  53. package/build/services/relations.d.ts.map +1 -0
  54. package/build/services/relations.js +17 -0
  55. package/build/services/relations.js.map +1 -0
  56. package/build/src/{catalog.d.ts → catalog/catalog.d.ts} +41 -2
  57. package/build/src/catalog/catalog.d.ts.map +1 -0
  58. package/build/src/{catalog.js → catalog/catalog.js} +120 -14
  59. package/build/src/catalog/catalog.js.map +1 -0
  60. package/build/src/{catalog_cache.d.ts → catalog/catalog_cache.d.ts} +46 -22
  61. package/build/src/catalog/catalog_cache.d.ts.map +1 -0
  62. package/build/src/{catalog_cache.js → catalog/catalog_cache.js} +53 -43
  63. package/build/src/catalog/catalog_cache.js.map +1 -0
  64. package/build/src/define_config.d.ts +101 -3
  65. package/build/src/define_config.d.ts.map +1 -1
  66. package/build/src/define_config.js.map +1 -1
  67. package/build/src/drivers/database_driver.d.ts +86 -4
  68. package/build/src/drivers/database_driver.d.ts.map +1 -1
  69. package/build/src/drivers/database_driver.js +425 -11
  70. package/build/src/drivers/database_driver.js.map +1 -1
  71. package/build/src/drivers/database_relations_driver.d.ts +75 -0
  72. package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
  73. package/build/src/drivers/database_relations_driver.js +450 -0
  74. package/build/src/drivers/database_relations_driver.js.map +1 -0
  75. package/build/src/drivers/openfga_driver.d.ts +713 -119
  76. package/build/src/drivers/openfga_driver.d.ts.map +1 -1
  77. package/build/src/drivers/openfga_driver.js +2048 -476
  78. package/build/src/drivers/openfga_driver.js.map +1 -1
  79. package/build/src/drivers/openfga_facts.d.ts +369 -0
  80. package/build/src/drivers/openfga_facts.d.ts.map +1 -0
  81. package/build/src/drivers/openfga_facts.js +813 -0
  82. package/build/src/drivers/openfga_facts.js.map +1 -0
  83. package/build/src/drivers/openfga_relations_driver.d.ts +120 -0
  84. package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
  85. package/build/src/drivers/openfga_relations_driver.js +466 -0
  86. package/build/src/drivers/openfga_relations_driver.js.map +1 -0
  87. package/build/src/errors.d.ts +258 -5
  88. package/build/src/errors.d.ts.map +1 -1
  89. package/build/src/errors.js +238 -7
  90. package/build/src/errors.js.map +1 -1
  91. package/build/src/freeze.d.ts +120 -0
  92. package/build/src/freeze.d.ts.map +1 -0
  93. package/build/src/freeze.js +172 -0
  94. package/build/src/freeze.js.map +1 -0
  95. package/build/src/http/app_access_middleware.d.ts.map +1 -0
  96. package/build/src/http/app_access_middleware.js.map +1 -0
  97. package/build/src/http/resource_access_middleware.d.ts +105 -0
  98. package/build/src/http/resource_access_middleware.d.ts.map +1 -0
  99. package/build/src/http/resource_access_middleware.js +81 -0
  100. package/build/src/http/resource_access_middleware.js.map +1 -0
  101. package/build/src/identity.d.ts +74 -1
  102. package/build/src/identity.d.ts.map +1 -1
  103. package/build/src/identity.js +100 -2
  104. package/build/src/identity.js.map +1 -1
  105. package/build/src/manager.d.ts +315 -4
  106. package/build/src/manager.d.ts.map +1 -1
  107. package/build/src/manager.js +1187 -181
  108. package/build/src/manager.js.map +1 -1
  109. package/build/src/models/authz_assignment.d.ts +6 -6
  110. package/build/src/models/authz_assignment.d.ts.map +1 -1
  111. package/build/src/models/authz_deny.d.ts +6 -6
  112. package/build/src/models/authz_deny.d.ts.map +1 -1
  113. package/build/src/models/authz_permission.d.ts +6 -6
  114. package/build/src/models/authz_permission.d.ts.map +1 -1
  115. package/build/src/models/authz_role.d.ts +6 -6
  116. package/build/src/models/authz_role.d.ts.map +1 -1
  117. package/build/src/models/authz_role_permission.d.ts +6 -6
  118. package/build/src/models/authz_role_permission.d.ts.map +1 -1
  119. package/build/src/openfga.d.ts +18 -2
  120. package/build/src/openfga.d.ts.map +1 -1
  121. package/build/src/openfga.js +15 -1
  122. package/build/src/openfga.js.map +1 -1
  123. package/build/src/reconcile.d.ts +37 -0
  124. package/build/src/reconcile.d.ts.map +1 -0
  125. package/build/src/reconcile.js +69 -0
  126. package/build/src/reconcile.js.map +1 -0
  127. package/build/src/relation_partition_trigger.d.ts +8 -0
  128. package/build/src/relation_partition_trigger.d.ts.map +1 -0
  129. package/build/src/relation_partition_trigger.js +85 -0
  130. package/build/src/relation_partition_trigger.js.map +1 -0
  131. package/build/src/relations/define_relations_config.d.ts +58 -0
  132. package/build/src/relations/define_relations_config.d.ts.map +1 -0
  133. package/build/src/relations/define_relations_config.js +144 -0
  134. package/build/src/relations/define_relations_config.js.map +1 -0
  135. package/build/src/relations/manager.d.ts +38 -0
  136. package/build/src/relations/manager.d.ts.map +1 -0
  137. package/build/src/relations/manager.js +156 -0
  138. package/build/src/relations/manager.js.map +1 -0
  139. package/build/src/relations/reconcile.d.ts +62 -0
  140. package/build/src/relations/reconcile.d.ts.map +1 -0
  141. package/build/src/relations/reconcile.js +138 -0
  142. package/build/src/relations/reconcile.js.map +1 -0
  143. package/build/src/relations_config_store.d.ts +22 -0
  144. package/build/src/relations_config_store.d.ts.map +1 -0
  145. package/build/src/relations_config_store.js +74 -0
  146. package/build/src/relations_config_store.js.map +1 -0
  147. package/build/src/scope_outbox.d.ts +69 -0
  148. package/build/src/scope_outbox.d.ts.map +1 -0
  149. package/build/src/scope_outbox.js +291 -0
  150. package/build/src/scope_outbox.js.map +1 -0
  151. package/build/src/{drivers → shared}/backend_guard.d.ts +14 -0
  152. package/build/src/shared/backend_guard.d.ts.map +1 -0
  153. package/build/src/{drivers → shared}/backend_guard.js +26 -1
  154. package/build/src/shared/backend_guard.js.map +1 -0
  155. package/build/src/shared/sql_expiry.d.ts.map +1 -0
  156. package/build/src/shared/sql_expiry.js.map +1 -0
  157. package/build/src/sql_descendants.d.ts +47 -1
  158. package/build/src/sql_descendants.d.ts.map +1 -1
  159. package/build/src/sql_descendants.js +75 -1
  160. package/build/src/sql_descendants.js.map +1 -1
  161. package/build/src/testing/contract.d.ts +74 -0
  162. package/build/src/testing/contract.d.ts.map +1 -1
  163. package/build/src/testing/contract.js +672 -169
  164. package/build/src/testing/contract.js.map +1 -1
  165. package/build/src/testing/main.d.ts +6 -0
  166. package/build/src/testing/main.d.ts.map +1 -1
  167. package/build/src/testing/main.js +3 -0
  168. package/build/src/testing/main.js.map +1 -1
  169. package/build/src/testing/migration_contract.d.ts +284 -0
  170. package/build/src/testing/migration_contract.d.ts.map +1 -0
  171. package/build/src/testing/migration_contract.js +586 -0
  172. package/build/src/testing/migration_contract.js.map +1 -0
  173. package/build/src/testing/relations_contract.d.ts +51 -0
  174. package/build/src/testing/relations_contract.d.ts.map +1 -0
  175. package/build/src/testing/relations_contract.js +654 -0
  176. package/build/src/testing/relations_contract.js.map +1 -0
  177. package/build/src/testing/relations_reconcile_contract.d.ts +24 -0
  178. package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -0
  179. package/build/src/testing/relations_reconcile_contract.js +172 -0
  180. package/build/src/testing/relations_reconcile_contract.js.map +1 -0
  181. package/build/src/traits/authz_scopes.js +1 -1
  182. package/build/src/traits/authz_scopes.js.map +1 -1
  183. package/build/src/traits/has_uuid.d.ts +7 -7
  184. package/build/src/traits/has_uuid.d.ts.map +1 -1
  185. package/build/src/types.d.ts +865 -82
  186. package/build/src/types.d.ts.map +1 -1
  187. package/build/src/types.js +10 -0
  188. package/build/src/types.js.map +1 -1
  189. package/build/stubs/config/authorization.stub +104 -4
  190. package/build/stubs/migration.stub +126 -0
  191. package/build/stubs/scopes_outbox_migration.stub +57 -0
  192. package/package.json +4 -2
  193. package/build/commands/openfga_import.d.ts +0 -34
  194. package/build/commands/openfga_import.d.ts.map +0 -1
  195. package/build/commands/openfga_import.js +0 -97
  196. package/build/commands/openfga_import.js.map +0 -1
  197. package/build/src/catalog.d.ts.map +0 -1
  198. package/build/src/catalog.js.map +0 -1
  199. package/build/src/catalog_cache.d.ts.map +0 -1
  200. package/build/src/catalog_cache.js.map +0 -1
  201. package/build/src/drivers/backend_guard.d.ts.map +0 -1
  202. package/build/src/drivers/backend_guard.js.map +0 -1
  203. package/build/src/drivers/sql_expiry.d.ts.map +0 -1
  204. package/build/src/drivers/sql_expiry.js.map +0 -1
  205. package/build/src/middleware/app_access_middleware.d.ts.map +0 -1
  206. package/build/src/middleware/app_access_middleware.js.map +0 -1
  207. /package/build/src/{middleware → http}/app_access_middleware.d.ts +0 -0
  208. /package/build/src/{middleware → http}/app_access_middleware.js +0 -0
  209. /package/build/src/{drivers → shared}/sql_expiry.d.ts +0 -0
  210. /package/build/src/{drivers → shared}/sql_expiry.js +0 -0
@@ -0,0 +1,294 @@
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
+ /**
12
+ * Las líneas del reporte y si la pasada es «limpia», en una función PURA
13
+ * (mismo patrón que `orphanLines`): es lo único del comando que decide algo,
14
+ * y así tiene su caso sin montar un ace.
15
+ *
16
+ * Qué cuenta como deriva —y por tanto como exit ≠ 0—:
17
+ * - en `--dry-run`, **cualquier cosa que la pasada haría** (escribir,
18
+ * actualizar o borrar): es el verificador, y para eso está en CI;
19
+ * - siempre, lo que una pasada NO puede arreglar sola: **ciclos** en el
20
+ * árbol del consumidor, **entradas aparcadas** de la outbox y hechos que
21
+ * se quedan fuera (`skipped`) — incluidos los del scope que ya no
22
+ * resuelve, que solo se van con `--prune`.
23
+ *
24
+ * La ventana del relay (`pendingRelay`) se AVISA pero no tumba la pasada: es
25
+ * una ventana de segundos por diseño, no una divergencia. Y **`expired`
26
+ * tampoco tumba** (3b-8 · B2): es la pérdida DECLARADA de la migración, no
27
+ * concede en ningún driver y nada barre las filas caducadas del origen, así
28
+ * que contarla como deriva dejaba el exit 1 clavado para siempre a la
29
+ * primera caducidad real.
30
+ */
31
+ export function reconcileLines(report) {
32
+ const lines = [];
33
+ const fase = (nombre, key) => {
34
+ const c = report.phases[key];
35
+ if (c.written + c.updated + c.unchanged + c.extra + c.deleted === 0)
36
+ return;
37
+ lines.push({
38
+ level: 'log',
39
+ message: `${nombre}: escritas ${c.written}, actualizadas ${c.updated}, iguales ${c.unchanged}, ` +
40
+ `sobran ${c.extra}, borradas ${c.deleted}`,
41
+ });
42
+ };
43
+ // **De dónde salieron los hechos** (3b-5), lo primero: es la diferencia
44
+ // entre una MIGRACIÓN y una pasada de mantenimiento contra el driver
45
+ // activo, y con ella se leen todos los números de abajo.
46
+ if (report.factsFrom !== undefined) {
47
+ lines.push(report.factsFrom === report.to
48
+ ? {
49
+ level: 'log',
50
+ message: `hechos: los del propio '${report.to}' — es el driver ACTIVO, así que sus hechos son SUYOS y no ` +
51
+ `los de authz_assignments/authz_denies. Esta pasada rehace lo derivado (marcador de raíz, ` +
52
+ `proyección del catálogo, árbol y visibilidad de rol) y no escribe ni borra un solo hecho. ` +
53
+ `Si lo que quieres es MIGRAR desde las tablas, nómbralas: --from=<driver cuyos hechos son authz_*>.`,
54
+ }
55
+ : { level: 'log', message: `hechos: leídos de ${report.factsFrom}` });
56
+ }
57
+ fase('marcador de raíz', 'root');
58
+ fase('proyección del catálogo', 'catalog');
59
+ fase('árbol', 'tree');
60
+ fase('hechos', 'facts');
61
+ for (const skip of report.details) {
62
+ lines.push({ level: 'warning', message: `sin migrar (${skip.reason}) ${skip.kind}: ${skip.detail}` });
63
+ }
64
+ const motivos = Object.entries(report.skipped).sort(([a], [b]) => (a < b ? -1 : 1));
65
+ for (const [reason, count] of motivos) {
66
+ lines.push({ level: 'warning', message: `${count} sin migrar por '${reason}'` });
67
+ }
68
+ if (report.skipped['unknown-scope'] && !report.prune) {
69
+ lines.push({
70
+ level: 'warning',
71
+ message: 'Los hechos de un scope que YA NO RESUELVE siguen en el destino y volverían a conceder si el scope se ' +
72
+ 'restaura con el mismo uuid: repite con --prune para borrarlos.',
73
+ });
74
+ }
75
+ if (report.drift.rootMarker) {
76
+ lines.push({
77
+ level: 'error',
78
+ message: 'Faltaba el MARCADOR DE RAÍZ (scope:app#rooted): sin él el store entero deniega, en silencio. ' +
79
+ 'Lo repone esta pasada y cada authz:catalog:sync.',
80
+ });
81
+ }
82
+ for (const object of report.drift.multiParent) {
83
+ lines.push({
84
+ level: 'error',
85
+ message: `${object} tenía MÁS DE UN PADRE en el destino: había otro escritor del árbol. Esta pasada lo deja con el del consumidor.`,
86
+ });
87
+ }
88
+ if (report.drift.roleVisibility > 0) {
89
+ lines.push({
90
+ level: 'error',
91
+ message: `${report.drift.roleVisibility} arista(s) de visibilidad de rol (invariante 18) estaban mal en el destino: ` +
92
+ 'una escritura que el relay pudo perder. Con la de MÁS, el destino concedía lo que el catálogo y el árbol de hoy no.',
93
+ });
94
+ }
95
+ for (const cycle of report.cycles) {
96
+ lines.push({
97
+ level: 'error',
98
+ message: `CICLO en el árbol del consumidor: ${cycle.join(' → ')} → ${cycle[0]}. Ninguna de sus aristas se escribe ` +
99
+ '(el backend evalúa el ciclo y la herencia pasa a ser bidireccional), así que ese subárbol DENIEGA hasta que lo arregles.',
100
+ });
101
+ }
102
+ if (report.drift.pendingRelay > 0) {
103
+ lines.push({
104
+ level: 'warning',
105
+ message: `${report.drift.pendingRelay} cambio(s) del árbol encolados y sin relevar: el destino decide con el árbol VIEJO ` +
106
+ 'en esa ventana. Drena la cola (authz:scopes:relay) y repite si quieres una foto sin ventana.',
107
+ });
108
+ }
109
+ if (report.drift.deadRelay > 0) {
110
+ lines.push({
111
+ level: 'error',
112
+ message: `${report.drift.deadRelay} cambio(s) del árbol APARCADOS en la outbox: eso no es una ventana, es divergencia permanente.`,
113
+ });
114
+ }
115
+ if (report.frozen?.lapsed) {
116
+ lines.push({
117
+ level: 'error',
118
+ message: 'El LEASE del freeze se perdió a MITAD de la pasada (una pausa más larga que el lease, la base caída, o ' +
119
+ 'alguien levantó la ventana): hubo un intervalo en el que otros procesos pudieron escribir, y lo que ' +
120
+ 'escribieran no está en ningún contador. La pasada NO se certifica: repítela dentro de una ventana entera.',
121
+ });
122
+ }
123
+ if (report.massDelete) {
124
+ lines.push({
125
+ level: 'error',
126
+ message: report.to === 'database'
127
+ ? 'Esta pasada borraría hechos y el ORIGEN no ha devuelto NI UNO: comprueba el --from y el store antes de ' +
128
+ 'usar --allow-mass-delete.'
129
+ : `Esta pasada borraría hechos y el ORIGEN (${report.factsFrom ?? 'authz_assignments/authz_denies'}) no ha ` +
130
+ 'devuelto NI UNO: comprueba la conexión y qué driver está escribiendo los hechos antes de usar ' +
131
+ '--allow-mass-delete.',
132
+ });
133
+ }
134
+ const cambios = report.written + report.updated + report.deleted;
135
+ // **`expired` no es deriva** (3b-8 · B2). Es la ÚNICA pérdida DECLARADA de
136
+ // la migración (README, contrato de migración): una asignación caducada no
137
+ // concede en ningún driver (invariante 3), no hay scheduler que barra sus
138
+ // filas del ORIGEN (a propósito) y ninguna escritura en el DESTINO puede
139
+ // «arreglarla». Contarla como deriva hacía el verde del verificador
140
+ // INALCANZABLE con datos reales: a la primera caducidad, exit 1 para
141
+ // siempre — el CI que el CHANGELOG promete verde no existía. Sigue saliendo
142
+ // contada («N sin migrar por 'expired'»); lo que no hace es tumbar la
143
+ // pasada. Las caducadas que SOBRAN en el destino sí son deriva
144
+ // (`extra-fact`), y su barrido existe: `--prune`.
145
+ const drifting = Object.keys(report.skipped).filter((reason) => reason !== 'expired');
146
+ const clean = report.cycles.length === 0 &&
147
+ report.drift.deadRelay === 0 &&
148
+ report.drift.multiParent.length === 0 &&
149
+ drifting.length === 0 &&
150
+ // La garantía del freeze se DEMUESTRA (3b-7, juez C4): un lease perdido a
151
+ // mitad es una ventana en la que otros pudieron escribir ⇒ exit ≠ 0.
152
+ report.frozen?.lapsed !== true &&
153
+ (!report.dryRun || cambios === 0);
154
+ return { lines, clean };
155
+ }
156
+ /**
157
+ * **Migra —y verifica— entre drivers** (3b-3a). Es la única primitiva de
158
+ * migración del paquete: `openfga:import` se borró en 3b-2k · K2 porque
159
+ * llenaba el store con las tuplas de un modelo que ya no existe.
160
+ *
161
+ * node ace authz:reconcile --to=openfga --dry-run # el VERIFICADOR (CI)
162
+ * node ace authz:reconcile --to=openfga # migra, o MANTIENE (ver abajo)
163
+ * node ace authz:reconcile --to=openfga --prune # y borra lo que el origen ya no respalda
164
+ *
165
+ * `--to` nombra una clave de `drivers` en `config/authorization.ts`, no el
166
+ * driver activo: migrar es llenar el destino mientras el motor sigue
167
+ * corriendo con el otro.
168
+ *
169
+ * **De dónde salen los HECHOS** (3b-5): de quien sea su fuente de verdad, y
170
+ * la pasada lo dice en su primera línea (`report.factsFrom`).
171
+ * - Si `--to` es el driver ACTIVO y sus hechos viven en su backend
172
+ * (`capabilities.hierarchyFacts`, el `openfga` de esta versión), los
173
+ * hechos son los SUYOS: la pasada es de MANTENIMIENTO —rehace lo derivado
174
+ * y aplica el barrido de visibilidad del invariante 18— y no escribe ni
175
+ * borra un solo hecho. `authz_assignments`/`authz_denies` no son su
176
+ * fuente: después de un cutover a `facts` están congeladas, y
177
+ * reconstruir desde ellas resucitaba lo revocado y —con `--prune`—
178
+ * borraba los denies vivos.
179
+ * - Con `--from=<driver>` mandas tú: si ese driver es el `database` del
180
+ * paquete, los hechos son sus tablas y `--to=openfga` es la MIGRACIÓN de
181
+ * un solo sentido de siempre.
182
+ *
183
+ * Qué migra hacia `openfga`: el marcador de raíz, la proyección del catálogo,
184
+ * el árbol (desde `scopes.enumerateEdges`) y los hechos de `authz_*`.
185
+ *
186
+ * Qué migra hacia `database` (3b-3b, la VUELTA): **solo los hechos**, leídos
187
+ * del origen por el puerto `enumerateFacts` (páginas con cursor). El ÁRBOL no
188
+ * se migra —el driver `database` lo lee de tus tablas en cada pregunta, que
189
+ * son su fuente de verdad— y el CATÁLOGO tampoco, que es propiedad local
190
+ * siempre. Por eso en esa dirección las fases `marcador de raíz`,
191
+ * `proyección del catálogo` y `árbol` son cero: no hay nada derivado que
192
+ * rehacer, y el cero lo dice.
193
+ *
194
+ * El origen se elige solo si es inequívoco: con dos drivers registrados es el
195
+ * que no es el destino; con más de un candidato hay que decirlo con
196
+ * `--from=<driver>`, porque de dónde salen los hechos decide lo que va a
197
+ * quedar escrito.
198
+ *
199
+ * Las dos direcciones son IDEMPOTENTES (la segunda pasada escribe cero),
200
+ * reanudables (leen por lotes con cursor y convergen si se repiten) y **nunca
201
+ * silenciosas**: cada cosa que no se migra sale contada y con su motivo.
202
+ *
203
+ * `--dry-run` es el verificador y es **read-only por contrato** (panel 2,
204
+ * cruce 4 · S18): mismo recorrido, cero escrituras y los mismos números. **No
205
+ * hay ni habrá un `--fix`**: sería un mecanismo de concesión.
206
+ *
207
+ * Durante la pasada que ESCRIBE, las escrituras del motor están congeladas
208
+ * (503 reintentable) y las lecturas siguen. **Con `--dry-run` NO se congela**
209
+ * (3b-6): el verificador no escribe nada, así que no tiene nada que proteger,
210
+ * y está publicado para correrlo en CI y en un cron — justo el sitio desde el
211
+ * que un mecanismo de indisponibilidad se dispara solo. Es una operación de
212
+ * PLATAFORMA: no lleva actor, no mide rangos y no se expone por HTTP.
213
+ */
214
+ export default class AuthzReconcile extends BaseCommand {
215
+ static commandName = 'authz:reconcile';
216
+ static description = 'Rebuild (or verify, with --dry-run) a driver from the authz_* tables and the consumer scope tree';
217
+ static options = {
218
+ startApp: true,
219
+ };
220
+ async run() {
221
+ if (!this.to) {
222
+ this.logger.error("Falta --to: 'node ace authz:reconcile --to=openfga'. Es la clave del driver DESTINO en " +
223
+ 'config/authorization.ts, no el driver activo.');
224
+ this.exitCode = 1;
225
+ return;
226
+ }
227
+ const { default: authorization } = await import('../services/main.js');
228
+ const report = await authorization.reconcile({
229
+ to: this.to,
230
+ ...(this.from !== undefined ? { from: this.from } : {}),
231
+ dryRun: this.dryRun === true,
232
+ prune: this.prune === true,
233
+ allowMassDelete: this.allowMassDelete === true,
234
+ batchSize: this.batchSize,
235
+ maxTuples: this.maxTuples,
236
+ });
237
+ const { lines, clean } = reconcileLines(report);
238
+ for (const { level, message } of lines)
239
+ this.logger[level](message);
240
+ const resumen = `escritas ${report.written}, actualizadas ${report.updated}, iguales ${report.unchanged}, ` +
241
+ `sobran ${report.extra}, borradas ${report.deleted}`;
242
+ if (report.dryRun) {
243
+ if (clean) {
244
+ this.logger.success(`Sin deriva: '${this.to}' coincide con ${report.factsFrom ?? 'authz_*'} y con tu árbol (${resumen}).`);
245
+ return;
246
+ }
247
+ this.logger.error(`DERIVA con '${this.to}': ${resumen}. No se ha escrito nada (--dry-run).`);
248
+ this.exitCode = 1;
249
+ return;
250
+ }
251
+ this.logger.success(`'${this.to}' reconciliado: ${resumen}.`);
252
+ if (!clean) {
253
+ this.logger.warning('Queda algo que esta pasada no arregla sola (mira las líneas de arriba): repítela cuando lo hayas resuelto.');
254
+ this.exitCode = 1;
255
+ }
256
+ }
257
+ }
258
+ __decorate([
259
+ flags.string({ description: 'Destination driver: a key of `drivers` in config/authorization.ts' }),
260
+ __metadata("design:type", Object)
261
+ ], AuthzReconcile.prototype, "to", void 0);
262
+ __decorate([
263
+ flags.string({
264
+ description: 'Source driver (a key of `drivers`). Only needed when more than one registered driver can be the source',
265
+ }),
266
+ __metadata("design:type", Object)
267
+ ], AuthzReconcile.prototype, "from", void 0);
268
+ __decorate([
269
+ flags.boolean({ name: 'dry-run', description: 'Report what it would do and write nothing (this is the verifier)' }),
270
+ __metadata("design:type", Object)
271
+ ], AuthzReconcile.prototype, "dryRun", void 0);
272
+ __decorate([
273
+ flags.boolean({ description: 'Also delete the facts the source no longer backs (dead scopes, leftovers)' }),
274
+ __metadata("design:type", Object)
275
+ ], AuthzReconcile.prototype, "prune", void 0);
276
+ __decorate([
277
+ flags.boolean({
278
+ name: 'allow-mass-delete',
279
+ description: 'Allow --prune to delete facts while authz_assignments/authz_denies are empty — check your connection first',
280
+ }),
281
+ __metadata("design:type", Object)
282
+ ], AuthzReconcile.prototype, "allowMassDelete", void 0);
283
+ __decorate([
284
+ flags.number({ name: 'batch-size', description: 'Rows per source batch and operations per destination write (default 100)' }),
285
+ __metadata("design:type", Object)
286
+ ], AuthzReconcile.prototype, "batchSize", void 0);
287
+ __decorate([
288
+ flags.number({
289
+ name: 'max-tuples',
290
+ description: 'Declared cap on the destination dump this pass holds in memory (default 1000000); above it, 500 E_AUTHZ_RECONCILE_TOO_LARGE before writing anything',
291
+ }),
292
+ __metadata("design:type", Object)
293
+ ], AuthzReconcile.prototype, "maxTuples", void 0);
294
+ //# sourceMappingURL=authz_reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz_reconcile.js","sourceRoot":"","sources":["../../commands/authz_reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { CommandOptions } from '@adonisjs/core/types/ace';
3
+ import type { RelationsReconcileReport } from '../src/relations/reconcile.js';
4
+ /**
5
+ * Las líneas del reporte de `authz:relations:reconcile` y si la pasada es
6
+ * «limpia», en una función PURA (mismo patrón que `reconcileLines` de roles):
7
+ * es lo único del comando que DECIDE algo, y así tiene su caso sin montar un
8
+ * ace.
9
+ *
10
+ * Qué cuenta como deriva —y por tanto como exit ≠ 0—:
11
+ * - **`modelDrift`** (siempre): un tipo de relación presente en el ORIGEN que
12
+ * la config del DESTINO no declara. No cabe en su modelo fusionado, así que
13
+ * esas tuplas nunca resolverían allí — es la deriva del presupuesto de
14
+ * modelo compartido, no un detalle cosmético;
15
+ * - en `--dry-run`, **cualquier cosa que la pasada haría** (escribir, borrar o
16
+ * dejar `extra` sin `--prune`): es el verificador, y para eso está en CI.
17
+ *
18
+ * `extra` en una pasada que SÍ escribe (sin `--prune`) se AVISA —«repite con
19
+ * --prune»— pero no tumba: el operador eligió no podar. Es la misma cortesía
20
+ * que `authz:reconcile` de roles con la ventana del relay.
21
+ */
22
+ export declare function relationsReconcileLines(report: RelationsReconcileReport): {
23
+ lines: Array<{
24
+ level: 'log' | 'warning' | 'error' | 'success';
25
+ message: string;
26
+ }>;
27
+ clean: boolean;
28
+ };
29
+ /**
30
+ * **`authz:relations:reconcile`** (Fase 4, lote 4-6) — migra las tuplas de
31
+ * RELACIÓN de un `RelationsDriver` a otro, idempotente y bidireccional, nunca
32
+ * silenciosa. Es el comando de plataforma que envuelve `reconcileRelations`
33
+ * (lote 4-5), análogo a `authz:reconcile` de roles pero para el puerto
34
+ * `RelationsDriver`.
35
+ *
36
+ * node ace authz:relations:reconcile --to=openfga --dry-run # el VERIFICADOR
37
+ * node ace authz:relations:reconcile --to=openfga # migra
38
+ * node ace authz:relations:reconcile --to=openfga --prune # y borra lo que el origen ya no respalda
39
+ *
40
+ * `--to`/`--from` nombran claves de `relations.drivers` en
41
+ * `config/authorization.ts` (NO las de `drivers` de roles): las relaciones
42
+ * tienen su propio puerto y sus propios drivers. La factory del driver
43
+ * `openfga` de relaciones entra por el subpath `/openfga` DENTRO de ella, así
44
+ * que este comando NUNCA importa el SDK (pureza).
45
+ *
46
+ * **Solo migra HECHOS de relación**: no hay árbol ni catálogo en `relations/`,
47
+ * así que —a diferencia de `authz:reconcile` de roles— no hay fases de marcador
48
+ * de raíz, proyección ni árbol. La migración es **POR partición**
49
+ * (mono-tenant: `APP_SCOPE`, el default); para migrar otra partición se pasan
50
+ * `--partition-type`/`--partition-uuid`, y migrar TODAS es iterar por el
51
+ * consumidor (una migración de tenant es un caso natural).
52
+ *
53
+ * **`--dry-run` es el verificador y es read-only por contrato** (como el de
54
+ * roles, S18): mismo recorrido, cero escrituras, los mismos números; NO hay
55
+ * `--fix`. Además vigila la **deriva del modelo fusionado** (`modelDrift`): un
56
+ * tipo del origen que el destino no declara sale como error.
57
+ *
58
+ * Es una operación de PLATAFORMA: no lleva actor, no se expone por HTTP.
59
+ */
60
+ export default class AuthzRelationsReconcile extends BaseCommand {
61
+ static commandName: string;
62
+ static description: string;
63
+ static options: CommandOptions;
64
+ to: string | undefined;
65
+ from: string | undefined;
66
+ dryRun: boolean | undefined;
67
+ prune: boolean | undefined;
68
+ allowMassDelete: boolean | undefined;
69
+ partitionType: string | undefined;
70
+ partitionUuid: string | undefined;
71
+ run(): Promise<void>;
72
+ }
73
+ //# sourceMappingURL=authz_relations_reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz_relations_reconcile.d.ts","sourceRoot":"","sources":["../../commands/authz_relations_reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAE7E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,wBAAwB,GAAG;IACzE,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjF,KAAK,EAAE,OAAO,CAAA;CACf,CA6CA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAwB,SAAQ,WAAW;IAC9D,MAAM,CAAC,WAAW,SAA8B;IAChD,MAAM,CAAC,WAAW,SAAgG;IAElH,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;IAGtB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IAGxB,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAG3B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAM1B,eAAe,EAAE,OAAO,GAAG,SAAS,CAAA;IAGpC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IAGjC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IAEnC,GAAG;CAuGV"}
@@ -0,0 +1,225 @@
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
+ /**
12
+ * Las líneas del reporte de `authz:relations:reconcile` y si la pasada es
13
+ * «limpia», en una función PURA (mismo patrón que `reconcileLines` de roles):
14
+ * es lo único del comando que DECIDE algo, y así tiene su caso sin montar un
15
+ * ace.
16
+ *
17
+ * Qué cuenta como deriva —y por tanto como exit ≠ 0—:
18
+ * - **`modelDrift`** (siempre): un tipo de relación presente en el ORIGEN que
19
+ * la config del DESTINO no declara. No cabe en su modelo fusionado, así que
20
+ * esas tuplas nunca resolverían allí — es la deriva del presupuesto de
21
+ * modelo compartido, no un detalle cosmético;
22
+ * - en `--dry-run`, **cualquier cosa que la pasada haría** (escribir, borrar o
23
+ * dejar `extra` sin `--prune`): es el verificador, y para eso está en CI.
24
+ *
25
+ * `extra` en una pasada que SÍ escribe (sin `--prune`) se AVISA —«repite con
26
+ * --prune»— pero no tumba: el operador eligió no podar. Es la misma cortesía
27
+ * que `authz:reconcile` de roles con la ventana del relay.
28
+ */
29
+ export function relationsReconcileLines(report) {
30
+ const lines = [];
31
+ lines.push({
32
+ level: 'log',
33
+ message: `relaciones: escritas ${report.written}, borradas ${report.deleted}, ` +
34
+ `iguales ${report.unchanged}, sobran ${report.extra}`,
35
+ });
36
+ for (const type of report.modelDrift) {
37
+ lines.push({
38
+ level: 'error',
39
+ message: `El tipo de objeto '${type}' está en el ORIGEN pero NO en la config de relaciones del DESTINO: ` +
40
+ 'no cabe en su modelo fusionado y esas tuplas no resolverían allí. Declara el tipo en ' +
41
+ 'defineRelationsConfig del destino (y republica su modelo) antes de migrarlas.',
42
+ });
43
+ }
44
+ // R-17: el seguro de borrado masivo. En una pasada REAL sin --allow-mass-delete
45
+ // esto ya habría lanzado 500 antes de llegar aquí; en --dry-run NO lanza y lo
46
+ // marca, así que el verificador tiene que sacarlo como deriva (exit ≠ 0).
47
+ if (report.massDelete) {
48
+ lines.push({
49
+ level: 'error',
50
+ message: `--prune borraría ${report.extra || report.deleted} tupla(s) del destino y el ORIGEN no aportó NI UNA ` +
51
+ 'utilizable: la firma de un --from equivocado o de un store vacío. En una pasada real es 500 ' +
52
+ 'E_AUTHZ_MASS_RECONCILE_REFUSED; si de verdad quieres vaciar el destino, --allow-mass-delete.',
53
+ });
54
+ }
55
+ if (report.extra > 0 && !report.dryRun) {
56
+ lines.push({
57
+ level: 'warning',
58
+ message: `${report.extra} tupla(s) del destino que el origen ya no respalda siguen ahí (no pasaste --prune): ` +
59
+ 'repite con --prune si quieres borrarlas.',
60
+ });
61
+ }
62
+ const cambios = report.written + report.deleted + (report.dryRun ? report.extra : 0);
63
+ const clean = report.modelDrift.length === 0 && !report.massDelete && (!report.dryRun || cambios === 0);
64
+ return { lines, clean };
65
+ }
66
+ /**
67
+ * **`authz:relations:reconcile`** (Fase 4, lote 4-6) — migra las tuplas de
68
+ * RELACIÓN de un `RelationsDriver` a otro, idempotente y bidireccional, nunca
69
+ * silenciosa. Es el comando de plataforma que envuelve `reconcileRelations`
70
+ * (lote 4-5), análogo a `authz:reconcile` de roles pero para el puerto
71
+ * `RelationsDriver`.
72
+ *
73
+ * node ace authz:relations:reconcile --to=openfga --dry-run # el VERIFICADOR
74
+ * node ace authz:relations:reconcile --to=openfga # migra
75
+ * node ace authz:relations:reconcile --to=openfga --prune # y borra lo que el origen ya no respalda
76
+ *
77
+ * `--to`/`--from` nombran claves de `relations.drivers` en
78
+ * `config/authorization.ts` (NO las de `drivers` de roles): las relaciones
79
+ * tienen su propio puerto y sus propios drivers. La factory del driver
80
+ * `openfga` de relaciones entra por el subpath `/openfga` DENTRO de ella, así
81
+ * que este comando NUNCA importa el SDK (pureza).
82
+ *
83
+ * **Solo migra HECHOS de relación**: no hay árbol ni catálogo en `relations/`,
84
+ * así que —a diferencia de `authz:reconcile` de roles— no hay fases de marcador
85
+ * de raíz, proyección ni árbol. La migración es **POR partición**
86
+ * (mono-tenant: `APP_SCOPE`, el default); para migrar otra partición se pasan
87
+ * `--partition-type`/`--partition-uuid`, y migrar TODAS es iterar por el
88
+ * consumidor (una migración de tenant es un caso natural).
89
+ *
90
+ * **`--dry-run` es el verificador y es read-only por contrato** (como el de
91
+ * roles, S18): mismo recorrido, cero escrituras, los mismos números; NO hay
92
+ * `--fix`. Además vigila la **deriva del modelo fusionado** (`modelDrift`): un
93
+ * tipo del origen que el destino no declara sale como error.
94
+ *
95
+ * Es una operación de PLATAFORMA: no lleva actor, no se expone por HTTP.
96
+ */
97
+ export default class AuthzRelationsReconcile extends BaseCommand {
98
+ static commandName = 'authz:relations:reconcile';
99
+ static description = 'Rebuild (or verify, with --dry-run) the relation tuples of one RelationsDriver from another';
100
+ static options = {
101
+ startApp: true,
102
+ };
103
+ async run() {
104
+ if (!this.to) {
105
+ this.logger.error("Falta --to: 'node ace authz:relations:reconcile --to=openfga'. Es la clave del driver DESTINO en " +
106
+ '`relations.drivers` de config/authorization.ts.');
107
+ this.exitCode = 1;
108
+ return;
109
+ }
110
+ const config = this.app.config.get('authorization');
111
+ const factories = config?.relations?.drivers ?? {};
112
+ const keys = Object.keys(factories);
113
+ if (keys.length === 0) {
114
+ this.logger.error('No hay `relations.drivers` en config/authorization.ts: declara los drivers de relaciones (por clave) ' +
115
+ 'para poder migrar sus tuplas.');
116
+ this.exitCode = 1;
117
+ return;
118
+ }
119
+ // El ORIGEN: `--from` si se pasa; si no, el ÚNICO que no es el destino.
120
+ let fromKey = this.from;
121
+ if (!fromKey) {
122
+ const candidates = keys.filter((k) => k !== this.to);
123
+ if (candidates.length !== 1) {
124
+ this.logger.error(`No se puede elegir el ORIGEN solo: hay ${candidates.length} candidatos (${candidates.join(', ') || 'ninguno'}). ` +
125
+ 'Nómbralo con --from=<clave>: de dónde salen las tuplas decide lo que queda escrito.');
126
+ this.exitCode = 1;
127
+ return;
128
+ }
129
+ fromKey = candidates[0];
130
+ }
131
+ if (!factories[this.to]) {
132
+ this.logger.error(`El destino '${this.to}' no es una clave de relations.drivers (${keys.join(', ')}).`);
133
+ this.exitCode = 1;
134
+ return;
135
+ }
136
+ if (!factories[fromKey]) {
137
+ this.logger.error(`El origen '${fromKey}' no es una clave de relations.drivers (${keys.join(', ')}).`);
138
+ this.exitCode = 1;
139
+ return;
140
+ }
141
+ // La partición (mono-tenant por defecto). El reconcile es POR partición.
142
+ const partition = this.partitionType && this.partitionType !== 'app'
143
+ ? { type: this.partitionType, uuid: this.partitionUuid ?? '' }
144
+ : { type: 'app', uuid: null };
145
+ const { reconcileRelations } = await import('../src/relations/reconcile.js');
146
+ // La config del DESTINO, para vigilar la deriva del modelo fusionado en
147
+ // --dry-run: es la persistida (una sola en el store compartido).
148
+ const { readRelationsConfig } = await import('../src/relations_config_store.js');
149
+ const toConfig = await readRelationsConfig().catch(() => undefined);
150
+ const from = await factories[fromKey]();
151
+ const to = await factories[this.to]();
152
+ let report;
153
+ try {
154
+ report = await reconcileRelations({
155
+ from,
156
+ to,
157
+ partition,
158
+ dryRun: this.dryRun === true,
159
+ prune: this.prune === true,
160
+ allowMassDelete: this.allowMassDelete === true,
161
+ ...(toConfig ? { toConfig } : {}),
162
+ });
163
+ }
164
+ catch (error) {
165
+ // R-17: el seguro de borrado masivo (500) sale como error de comando, no
166
+ // como stack crudo. `--allow-mass-delete` es la salida humana.
167
+ if (error?.code === 'E_AUTHZ_MASS_RECONCILE_REFUSED') {
168
+ this.logger.error(String(error.message ?? error));
169
+ this.exitCode = 1;
170
+ return;
171
+ }
172
+ throw error;
173
+ }
174
+ const { lines, clean } = relationsReconcileLines(report);
175
+ for (const { level, message } of lines)
176
+ this.logger[level](message);
177
+ const resumen = `escritas ${report.written}, borradas ${report.deleted}, iguales ${report.unchanged}, sobran ${report.extra}`;
178
+ if (report.dryRun) {
179
+ if (clean) {
180
+ this.logger.success(`Sin deriva: '${this.to}' coincide con '${fromKey}' (${resumen}).`);
181
+ return;
182
+ }
183
+ this.logger.error(`DERIVA con '${this.to}': ${resumen}. No se ha escrito nada (--dry-run).`);
184
+ this.exitCode = 1;
185
+ return;
186
+ }
187
+ this.logger.success(`Relaciones de '${this.to}' reconciliadas desde '${fromKey}': ${resumen}.`);
188
+ if (!clean) {
189
+ this.logger.warning('Queda algo que esta pasada no arregla sola (mira las líneas de arriba).');
190
+ this.exitCode = 1;
191
+ }
192
+ }
193
+ }
194
+ __decorate([
195
+ flags.string({ description: 'Destination driver: a key of `relations.drivers` in config/authorization.ts' }),
196
+ __metadata("design:type", Object)
197
+ ], AuthzRelationsReconcile.prototype, "to", void 0);
198
+ __decorate([
199
+ flags.string({ description: 'Source driver (a key of `relations.drivers`); required when more than one is registered' }),
200
+ __metadata("design:type", Object)
201
+ ], AuthzRelationsReconcile.prototype, "from", void 0);
202
+ __decorate([
203
+ flags.boolean({ name: 'dry-run', description: 'Report what it would do and write nothing (this is the verifier)' }),
204
+ __metadata("design:type", Object)
205
+ ], AuthzRelationsReconcile.prototype, "dryRun", void 0);
206
+ __decorate([
207
+ flags.boolean({ description: 'Also delete the tuples the source no longer backs (leftovers)' }),
208
+ __metadata("design:type", Object)
209
+ ], AuthzRelationsReconcile.prototype, "prune", void 0);
210
+ __decorate([
211
+ flags.boolean({
212
+ name: 'allow-mass-delete',
213
+ description: 'Allow --prune to empty the destination when the source has no usable tuple (mass-delete safeguard)',
214
+ }),
215
+ __metadata("design:type", Object)
216
+ ], AuthzRelationsReconcile.prototype, "allowMassDelete", void 0);
217
+ __decorate([
218
+ flags.string({ name: 'partition-type', description: 'Partition (tenant) scope type to migrate; default: app (mono-tenant)' }),
219
+ __metadata("design:type", Object)
220
+ ], AuthzRelationsReconcile.prototype, "partitionType", void 0);
221
+ __decorate([
222
+ flags.string({ name: 'partition-uuid', description: 'Partition (tenant) scope uuid; required with --partition-type' }),
223
+ __metadata("design:type", Object)
224
+ ], AuthzRelationsReconcile.prototype, "partitionUuid", void 0);
225
+ //# 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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"}