@jantstack/adonis-authz 2.4.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 (158) hide show
  1. package/README.md +496 -29
  2. package/build/commands/authz_catalog_diff.d.ts.map +1 -1
  3. package/build/commands/authz_catalog_diff.js +0 -1
  4. package/build/commands/authz_catalog_diff.js.map +1 -1
  5. package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -1
  6. package/build/commands/authz_catalog_prune_orphans.js +0 -1
  7. package/build/commands/authz_catalog_prune_orphans.js.map +1 -1
  8. package/build/commands/authz_freeze.d.ts.map +1 -1
  9. package/build/commands/authz_freeze.js +1 -2
  10. package/build/commands/authz_freeze.js.map +1 -1
  11. package/build/commands/authz_reconcile.d.ts +15 -6
  12. package/build/commands/authz_reconcile.d.ts.map +1 -1
  13. package/build/commands/authz_reconcile.js +143 -20
  14. package/build/commands/authz_reconcile.js.map +1 -1
  15. package/build/commands/authz_relations_reconcile.d.ts +72 -1
  16. package/build/commands/authz_relations_reconcile.d.ts.map +1 -1
  17. package/build/commands/authz_relations_reconcile.js +234 -21
  18. package/build/commands/authz_relations_reconcile.js.map +1 -1
  19. package/build/commands/authz_scopes_relay.d.ts +50 -0
  20. package/build/commands/authz_scopes_relay.d.ts.map +1 -1
  21. package/build/commands/authz_scopes_relay.js +213 -10
  22. package/build/commands/authz_scopes_relay.js.map +1 -1
  23. package/build/commands/authz_scopes_requeue.d.ts +80 -0
  24. package/build/commands/authz_scopes_requeue.d.ts.map +1 -0
  25. package/build/commands/authz_scopes_requeue.js +230 -0
  26. package/build/commands/authz_scopes_requeue.js.map +1 -0
  27. package/build/commands/authz_unfreeze.d.ts +12 -4
  28. package/build/commands/authz_unfreeze.d.ts.map +1 -1
  29. package/build/commands/authz_unfreeze.js +74 -9
  30. package/build/commands/authz_unfreeze.js.map +1 -1
  31. package/build/commands/main.d.ts +16 -13
  32. package/build/commands/main.d.ts.map +1 -1
  33. package/build/commands/main.js +59 -11
  34. package/build/commands/main.js.map +1 -1
  35. package/build/commands/openfga_provision.d.ts +49 -3
  36. package/build/commands/openfga_provision.d.ts.map +1 -1
  37. package/build/commands/openfga_provision.js +72 -7
  38. package/build/commands/openfga_provision.js.map +1 -1
  39. package/build/index.d.ts +15 -5
  40. package/build/index.d.ts.map +1 -1
  41. package/build/index.js +16 -3
  42. package/build/index.js.map +1 -1
  43. package/build/providers/authz_provider.d.ts +7 -2
  44. package/build/providers/authz_provider.d.ts.map +1 -1
  45. package/build/providers/authz_provider.js +20 -3
  46. package/build/providers/authz_provider.js.map +1 -1
  47. package/build/src/define_config.d.ts +106 -1
  48. package/build/src/define_config.d.ts.map +1 -1
  49. package/build/src/define_config.js +11 -0
  50. package/build/src/define_config.js.map +1 -1
  51. package/build/src/drivers/database_driver.d.ts +53 -14
  52. package/build/src/drivers/database_driver.d.ts.map +1 -1
  53. package/build/src/drivers/database_driver.js +104 -32
  54. package/build/src/drivers/database_driver.js.map +1 -1
  55. package/build/src/drivers/database_relations_driver.d.ts +74 -9
  56. package/build/src/drivers/database_relations_driver.d.ts.map +1 -1
  57. package/build/src/drivers/database_relations_driver.js +391 -93
  58. package/build/src/drivers/database_relations_driver.js.map +1 -1
  59. package/build/src/drivers/openfga_driver.d.ts +71 -10
  60. package/build/src/drivers/openfga_driver.d.ts.map +1 -1
  61. package/build/src/drivers/openfga_driver.js +195 -16
  62. package/build/src/drivers/openfga_driver.js.map +1 -1
  63. package/build/src/drivers/openfga_facts.d.ts +15 -0
  64. package/build/src/drivers/openfga_facts.d.ts.map +1 -1
  65. package/build/src/drivers/openfga_facts.js +25 -2
  66. package/build/src/drivers/openfga_facts.js.map +1 -1
  67. package/build/src/drivers/openfga_relations_driver.d.ts +72 -6
  68. package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -1
  69. package/build/src/drivers/openfga_relations_driver.js +308 -50
  70. package/build/src/drivers/openfga_relations_driver.js.map +1 -1
  71. package/build/src/errors.d.ts +175 -4
  72. package/build/src/errors.d.ts.map +1 -1
  73. package/build/src/errors.js +219 -3
  74. package/build/src/errors.js.map +1 -1
  75. package/build/src/freeze.d.ts +100 -20
  76. package/build/src/freeze.d.ts.map +1 -1
  77. package/build/src/freeze.js +159 -25
  78. package/build/src/freeze.js.map +1 -1
  79. package/build/src/manager.d.ts +239 -7
  80. package/build/src/manager.d.ts.map +1 -1
  81. package/build/src/manager.js +1192 -148
  82. package/build/src/manager.js.map +1 -1
  83. package/build/src/models/authz_assignment.d.ts +2 -2
  84. package/build/src/models/authz_assignment.d.ts.map +1 -1
  85. package/build/src/models/authz_deny.d.ts +2 -2
  86. package/build/src/models/authz_deny.d.ts.map +1 -1
  87. package/build/src/models/authz_permission.d.ts +2 -2
  88. package/build/src/models/authz_permission.d.ts.map +1 -1
  89. package/build/src/models/authz_role.d.ts +2 -2
  90. package/build/src/models/authz_role.d.ts.map +1 -1
  91. package/build/src/models/authz_role_permission.d.ts +2 -2
  92. package/build/src/models/authz_role_permission.d.ts.map +1 -1
  93. package/build/src/relation_partition_trigger.d.ts.map +1 -1
  94. package/build/src/relation_partition_trigger.js +11 -3
  95. package/build/src/relation_partition_trigger.js.map +1 -1
  96. package/build/src/relations/define_relations_config.d.ts +51 -0
  97. package/build/src/relations/define_relations_config.d.ts.map +1 -1
  98. package/build/src/relations/define_relations_config.js +68 -1
  99. package/build/src/relations/define_relations_config.js.map +1 -1
  100. package/build/src/relations/manager.d.ts +106 -6
  101. package/build/src/relations/manager.d.ts.map +1 -1
  102. package/build/src/relations/manager.js +368 -30
  103. package/build/src/relations/manager.js.map +1 -1
  104. package/build/src/relations/reconcile.d.ts +81 -6
  105. package/build/src/relations/reconcile.d.ts.map +1 -1
  106. package/build/src/relations/reconcile.js +233 -77
  107. package/build/src/relations/reconcile.js.map +1 -1
  108. package/build/src/scope_outbox.d.ts +54 -0
  109. package/build/src/scope_outbox.d.ts.map +1 -1
  110. package/build/src/scope_outbox.js +274 -33
  111. package/build/src/scope_outbox.js.map +1 -1
  112. package/build/src/shared/backend_guard.d.ts +28 -0
  113. package/build/src/shared/backend_guard.d.ts.map +1 -1
  114. package/build/src/shared/backend_guard.js +52 -0
  115. package/build/src/shared/backend_guard.js.map +1 -1
  116. package/build/src/shared/sql_expiry.d.ts +2 -0
  117. package/build/src/shared/sql_expiry.d.ts.map +1 -1
  118. package/build/src/shared/sql_expiry.js +4 -0
  119. package/build/src/shared/sql_expiry.js.map +1 -1
  120. package/build/src/shared/transaction_guard.d.ts +53 -0
  121. package/build/src/shared/transaction_guard.d.ts.map +1 -0
  122. package/build/src/shared/transaction_guard.js +74 -0
  123. package/build/src/shared/transaction_guard.js.map +1 -0
  124. package/build/src/sql_descendants.d.ts +8 -0
  125. package/build/src/sql_descendants.d.ts.map +1 -1
  126. package/build/src/sql_descendants.js.map +1 -1
  127. package/build/src/testing/contract.d.ts +55 -4
  128. package/build/src/testing/contract.d.ts.map +1 -1
  129. package/build/src/testing/contract.js +244 -9
  130. package/build/src/testing/contract.js.map +1 -1
  131. package/build/src/testing/main.d.ts +6 -4
  132. package/build/src/testing/main.d.ts.map +1 -1
  133. package/build/src/testing/main.js +3 -2
  134. package/build/src/testing/main.js.map +1 -1
  135. package/build/src/testing/migration_contract.d.ts +9 -0
  136. package/build/src/testing/migration_contract.d.ts.map +1 -1
  137. package/build/src/testing/migration_contract.js +5 -2
  138. package/build/src/testing/migration_contract.js.map +1 -1
  139. package/build/src/testing/relations_contract.d.ts +23 -1
  140. package/build/src/testing/relations_contract.d.ts.map +1 -1
  141. package/build/src/testing/relations_contract.js +603 -19
  142. package/build/src/testing/relations_contract.js.map +1 -1
  143. package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -1
  144. package/build/src/testing/relations_reconcile_contract.js +49 -1
  145. package/build/src/testing/relations_reconcile_contract.js.map +1 -1
  146. package/build/src/testing/scope_outbox_contract.d.ts +98 -0
  147. package/build/src/testing/scope_outbox_contract.d.ts.map +1 -0
  148. package/build/src/testing/scope_outbox_contract.js +677 -0
  149. package/build/src/testing/scope_outbox_contract.js.map +1 -0
  150. package/build/src/traits/has_uuid.d.ts +2 -2
  151. package/build/src/traits/has_uuid.d.ts.map +1 -1
  152. package/build/src/types.d.ts +708 -38
  153. package/build/src/types.d.ts.map +1 -1
  154. package/build/src/types.js +55 -0
  155. package/build/src/types.js.map +1 -1
  156. package/build/stubs/config/authorization.stub +45 -1
  157. package/build/stubs/migration.stub +20 -10
  158. package/package.json +1 -1
@@ -8,6 +8,56 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  import { BaseCommand, flags } from '@adonisjs/core/ace';
11
+ /**
12
+ * **La edad de la cola en el comando** (2.4.0-alpha.8; panel relay 2,
13
+ * cuestión 5), en una función PURA (patrón `requeueLines`): qué se imprime y
14
+ * si `--max-age` la convierte en salida ≠ 0.
15
+ *
16
+ * - Sin cabeza con fecha (`pendingAgeMs === null`: cola vacía, solo
17
+ * aparcadas, o una outbox de terceros sin `createdAt`) no se dice nada y
18
+ * nunca se alarma.
19
+ * - Con edad, se INFORMA siempre (es lo que faltaba en el B2 de COGNITIV:
20
+ * diez pasadas con el store caído y nadie sabía desde cuándo).
21
+ * - **Solo con umbral** (`--max-age=<ms>`) y edad POR ENCIMA es `exceeded`
22
+ * (exit ≠ 0): sin umbral un cron no puede quedarse en rojo para siempre
23
+ * por una cola que simplemente tiene backlog. Un umbral inválido lanza.
24
+ */
25
+ export function queueAgeLines(queue, maxAgeMs, remaining = false) {
26
+ if (maxAgeMs !== undefined && !(Number.isInteger(maxAgeMs) && maxAgeMs >= 0)) {
27
+ throw new TypeError(`authz:scopes:relay: --max-age debe ser un entero >= 0 en milisegundos (llegó ${String(maxAgeMs)})`);
28
+ }
29
+ if (queue.pendingAgeMs === null || queue.oldestPendingAt === null) {
30
+ // «Cola vacía» y «no sé la edad» no son lo mismo (cierre alpha.8, 🟡 2 del
31
+ // auditor): con entradas pendientes y una outbox que no devuelve `createdAt`
32
+ // la alarma por edad no puede vigilar nada — y con `--max-age` eso es un
33
+ // fallo, no un silencio.
34
+ if (!remaining)
35
+ return { lines: [], exceeded: false };
36
+ const message = 'Hay entradas pendientes pero la outbox no devuelve `createdAt` (par de capacidad `createdAt` del contrato): ' +
37
+ 'la edad de la cola es DESCONOCIDA y --max-age no puede vigilarla.';
38
+ return maxAgeMs === undefined
39
+ ? { lines: [{ level: 'warning', message }], exceeded: false }
40
+ : { lines: [{ level: 'error', message }], exceeded: true };
41
+ }
42
+ const since = queue.oldestPendingAt.toISOString();
43
+ if (maxAgeMs !== undefined && queue.pendingAgeMs > maxAgeMs) {
44
+ return {
45
+ exceeded: true,
46
+ lines: [
47
+ {
48
+ level: 'error',
49
+ message: `La entrada pendiente más antigua de la cola lleva ${queue.pendingAgeMs} ms sin aplicarse (desde ${since}), ` +
50
+ `por encima de --max-age=${maxAgeMs}: la cola no avanza (¿el backend lleva caído desde entonces? un 503 no consume ` +
51
+ 'intento y no aparca nada). Mira `failures` de esta pasada.',
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ return {
57
+ exceeded: false,
58
+ lines: [{ level: 'info', message: `Cabeza de la cola: pendiente desde ${since} (${queue.pendingAgeMs} ms; medida con el reloj de quien encoló).` }],
59
+ };
60
+ }
11
61
  /**
12
62
  * Drena la OUTBOX del árbol de scopes y aplica los cambios al driver
13
63
  * (3b-2d; panel 2, cruce 4 · S5).
@@ -19,6 +69,29 @@ import { BaseCommand, flags } from '@adonisjs/core/ace';
19
69
  * node ace authz:scopes:relay # drena
20
70
  * node ace authz:scopes:relay --dry-run # solo dice qué haría
21
71
  * node ace authz:scopes:relay --limit 500 # una pasada acotada
72
+ * node ace authz:scopes:relay --only=12,13 # SOLO esas entradas (2.4.0-alpha.5)
73
+ * 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)
74
+ * node ace authz:scopes:relay --max-age=300000 # exit ≠ 0 si la pendiente más antigua lleva > 5 min (2.4.0-alpha.8)
75
+ *
76
+ * **`--max-age`** (alpha.8, cuestión 5 del panel relay 2): el reporte lleva
77
+ * SIEMPRE la edad de la cabeza global de la cola (`queue.pendingAgeMs`, con
78
+ * el reloj de quien encoló) y se imprime; con umbral, superarlo es salida ≠ 0
79
+ * — la alarma que faltaba cuando el store está caído (un 503 no consume
80
+ * intento: `attempts` y `dead` se quedan a cero). Sin umbral solo informa.
81
+ *
82
+ * **`--only`** (alpha.5 · R1): releva solo los ids del recibo que devolvió
83
+ * `scopes.attached/moved/detached` (`{ queued }`). El orden por clave no se
84
+ * relaja: una entrada pedida que dependa de una anterior sin aplicar y no
85
+ * pedida sale «aplazada» (`depends-on:<id>`), nunca fuera de orden; `--only`
86
+ * vacío es un error, no «todo». La salida es POR ENTRADA (`entries`), y los
87
+ * ids que no estaban ni pendientes ni aparcados se dicen (`missing`).
88
+ *
89
+ * **Lo aparcado tapona por clave** (alpha.5 · R2a): un `attached`/`moved`
90
+ * que nombre un scope de una entrada APARCADA sale «aplazado» con
91
+ * `parked:<id>` en todas las pasadas hasta que alguien mire la aparcada; un
92
+ * `detached` de esa clave se aplica igual (una purga nunca queda bloqueada).
93
+ * Y un 503 del backend NO consume intento: la caída del store es común a
94
+ * todas las entradas y no dice nada de ninguna.
22
95
  *
23
96
  * Pensado para un supervisor (un bucle, un cron corto, un worker): la pasada
24
97
  * es REANUDABLE —lo que no entra sigue pendiente—. Un cambio que falla
@@ -49,13 +122,46 @@ export default class AuthzScopesRelay extends BaseCommand {
49
122
  static options = {
50
123
  startApp: true,
51
124
  };
125
+ /** `--only=12,13` o `--only=12 --only=13`: ids numéricos como número, el resto como cadena. */
126
+ static parseOnly(values) {
127
+ if (values === undefined)
128
+ return undefined;
129
+ return values
130
+ .flatMap((value) => String(value).split(','))
131
+ .map((value) => value.trim())
132
+ .filter((value) => value.length > 0)
133
+ .map((value) => (/^\d+$/.test(value) ? Number(value) : value));
134
+ }
52
135
  async run() {
53
136
  const { default: authorization } = await import('../services/main.js');
137
+ const only = AuthzScopesRelay.parseOnly(this.only);
138
+ if (only !== undefined && only.length === 0) {
139
+ this.logger.error('--only está vacío: relevar «nada» no es relevar «todo». Quita --only para drenar la cola entera.');
140
+ this.exitCode = 1;
141
+ return;
142
+ }
143
+ // alpha.10 · S7 (auditoría de sistema): el umbral se valida ANTES de tocar
144
+ // la cola. Hasta alpha.9 `queueAgeLines` lanzaba DESPUÉS de relevar: la
145
+ // pasada aplicaba y marcaba entradas y el comando reventaba con un
146
+ // TypeError sin decir qué había aplicado.
147
+ if (this.maxAge !== undefined && !(Number.isInteger(this.maxAge) && this.maxAge >= 0)) {
148
+ this.logger.error(`--max-age debe ser un entero >= 0 en milisegundos (llegó ${String(this.maxAge)}). No se ha relevado nada.`);
149
+ this.exitCode = 1;
150
+ return;
151
+ }
54
152
  const report = await authorization.relayScopeChanges({
55
153
  dryRun: this.dryRun === true,
56
154
  limit: this.limit,
57
155
  batchSize: this.batchSize,
156
+ only,
157
+ ...(this.waitLeaseMs === undefined ? {} : { waitForLeaseMs: this.waitLeaseMs }),
58
158
  });
159
+ // La EDAD de la cola, siempre; con `--max-age`, la alarma (2.4.0-alpha.8).
160
+ const age = queueAgeLines(report.queue, this.maxAge, report.remaining);
161
+ for (const line of age.lines)
162
+ this.logger[line.level](line.message);
163
+ if (age.exceeded)
164
+ this.exitCode = 1;
59
165
  const linea = (item) => {
60
166
  const { change } = item;
61
167
  const scope = (s) => `${s.type}:${s.uuid ?? ''}`;
@@ -68,6 +174,34 @@ export default class AuthzScopesRelay extends BaseCommand {
68
174
  for (const item of report.dead) {
69
175
  this.logger.error(`APARCADO tras agotar los intentos: ${linea(item)}${item.error ? ` — ${item.error}` : ''}`);
70
176
  }
177
+ // Cierre-2 · C2 (gemelo): un id PEDIDO aparcado más allá del primer lote de
178
+ // `dead` (acotado a `batchSize`) llega en `entries`; se nombra igual.
179
+ if (only !== undefined) {
180
+ const listed = new Set(report.dead.map((item) => String(item.id)));
181
+ for (const entry of report.entries) {
182
+ if (entry.status !== 'dead' || listed.has(String(entry.id)))
183
+ continue;
184
+ this.logger.error(`APARCADO tras agotar los intentos: ${linea(entry)}${entry.reason ? ` — ${entry.reason}` : ''}`);
185
+ this.exitCode = 1;
186
+ }
187
+ }
188
+ // Cierre-2 · C2: `missing` (no está en la cola) ≠ `notEvaluated` (PENDIENTE y
189
+ // la pasada no llegó: alcanzó --limit). Hasta el cierre-2 los dos salían como
190
+ // «no estaba ni pendiente ni aparcado … o no existe», con exit 0.
191
+ const notEvaluated = only === undefined ? [] : (report.notEvaluated ?? []);
192
+ const limite = this.limit === undefined ? 'el --limit por defecto' : `--limit=${this.limit}`;
193
+ const rama = report.dryRun ? '--dry-run' : 'pasada que aplica';
194
+ const notEvaluatedLines = () => {
195
+ if (notEvaluated.length === 0)
196
+ return;
197
+ for (const id of notEvaluated) {
198
+ this.logger.error(`#${id} está PENDIENTE y esta pasada NO lo evaluó (${rama}): alcanzó ${limite} antes de llegar a él. ` +
199
+ (report.dryRun ? 'No se sabe si se aplicaría.' : 'No se ha aplicado: el cambio no ha aterrizado.'));
200
+ }
201
+ this.logger.error(`${notEvaluated.length} de los ids pedidos NO se evaluaron por el límite de la pasada ` +
202
+ `(${notEvaluated.map((id) => `#${id}`).join(', ')}): repite con un --limit mayor (o sin él).`);
203
+ this.exitCode = 1;
204
+ };
71
205
  if (report.dead.length > 0) {
72
206
  this.logger.warning('Una entrada aparcada NO se va a aplicar sola: el árbol del backend está divergente en ese nodo. ' +
73
207
  'Revísala en la cola (el consumidor puede volver a notificar el cambio) o reconcilia.');
@@ -81,28 +215,61 @@ export default class AuthzScopesRelay extends BaseCommand {
81
215
  if (report.dryRun) {
82
216
  for (const item of report.wouldApply)
83
217
  this.logger.log(`pendiente: ${linea(item)}`);
218
+ // Cierre del auditor de alpha.10 · R3 (gemelo de S6 en --dry-run): con
219
+ // `--only` la pregunta es «¿qué fue de LO PEDIDO?» también aquí. Hasta el
220
+ // cierre esta rama retornaba antes del bloque S6 y `--dry-run --only=999`
221
+ // (errata) decía «La outbox del árbol está vacía» en verde sin nombrar el id.
222
+ // Misma letra por id que la pasada que aplica; el resumen dice su rama.
223
+ if (only !== undefined) {
224
+ for (const id of report.missing) {
225
+ this.logger.warning(`#${id} no estaba ni pendiente ni aparcado: ya lo aplicó otra pasada, o no existe.`);
226
+ }
227
+ notEvaluatedLines();
228
+ }
84
229
  if (report.wouldApply.length === 0) {
85
230
  if (report.dead.length > 0)
86
231
  this.exitCode = 1;
87
- else
232
+ if (only !== undefined) {
233
+ if (report.missing.length > 0 && notEvaluated.length === 0) {
234
+ this.logger.warning(`--dry-run: ninguno de los ids pedidos está pendiente (${report.missing.map((id) => `#${id}`).join(', ')}: ` +
235
+ 'ni pendientes ni aparcados). No hay nada de LO PEDIDO que propagar; si no los aplicó otra pasada, revisa los ids.');
236
+ }
237
+ return;
238
+ }
239
+ if (report.dead.length === 0)
88
240
  this.logger.success('La outbox del árbol está vacía: nada que propagar.');
89
241
  return;
90
242
  }
91
243
  this.logger.warning(`${report.wouldApply.length} cambio(s) del árbol sin propagar. No se ha aplicado nada: repite sin --dry-run. ` +
92
244
  `Mientras tanto el backend decide con el árbol VIEJO.`);
245
+ // alpha.10 · S5: lo APARCADO es exit ≠ 0 «mientras exista» (README), haya
246
+ // o no pendientes. Hasta alpha.9 un cron con --dry-run perdía la
247
+ // divergencia permanente justo cuando además había tráfico.
248
+ if (report.dead.length > 0)
249
+ this.exitCode = 1;
93
250
  return;
94
251
  }
95
- // Qué se aplicó, uno a uno: la pasada no es atómica y un contador no
96
- // permite retomar nada.
97
- for (const item of report.applied)
98
- this.logger.log(`aplicado: ${linea(item)}`);
99
- for (const item of report.deferred) {
100
- this.logger.warning(`aplazado: ${linea(item)}${item.error ? ` — ${item.error}` : ''}`);
252
+ // Qué fue de cada entrada, una a una (`entries`): la pasada no es atómica
253
+ // y un contador no permite retomar nada. Lo aparcado ya se dijo arriba.
254
+ for (const entry of report.entries) {
255
+ if (entry.status === 'applied')
256
+ this.logger.log(`aplicado: ${linea(entry)}`);
257
+ if (entry.status === 'deferred') {
258
+ const motivo = entry.reason?.startsWith('parked:')
259
+ ? `aplazado por APARCADO (${entry.reason}): no se aplica hasta que alguien mire esa entrada`
260
+ : `aplazado (${entry.reason ?? 'depende de una entrada anterior sin aplicar'}): corre el relay completo`;
261
+ this.logger.warning(`${motivo}: ${linea(entry)}`);
262
+ }
263
+ }
264
+ for (const id of report.missing) {
265
+ this.logger.warning(`#${id} no estaba ni pendiente ni aparcado: ya lo aplicó otra pasada, o no existe.`);
101
266
  }
267
+ notEvaluatedLines();
102
268
  if (report.failures.length > 0) {
103
269
  for (const item of report.failures) {
104
270
  this.logger.error(`falló #${item.id} (${item.change.op} ` +
105
- `${item.change.child.type}:${item.change.child.uuid ?? ''}): ${item.error}`);
271
+ `${item.change.child.type}:${item.change.child.uuid ?? ''}): ${item.error}` +
272
+ (item.transient ? ' — el backend no respondió: este fallo NO consume intento' : ''));
106
273
  }
107
274
  this.logger.warning('Lo que DEPENDE de un cambio fallido no se adelanta (el orden del árbol importa: aparece como «aplazado»), ' +
108
275
  'pero el resto de la cola sí avanza. Arregla la causa y vuelve a ejecutar; retoma donde lo dejó. ' +
@@ -112,6 +279,25 @@ export default class AuthzScopesRelay extends BaseCommand {
112
279
  }
113
280
  if (report.dead.length > 0)
114
281
  this.exitCode = 1;
282
+ // alpha.10 · S6: con `--only` la pregunta es «¿aterrizó LO QUE PEDÍ?», no
283
+ // «¿está vacía la cola?». Hasta alpha.9 un id aplazado (o una errata)
284
+ // imprimía «La outbox del árbol está vacía» en verde: el script de alta de
285
+ // tenant daba por relevado un cambio que no había aterrizado.
286
+ if (only !== undefined) {
287
+ const deferred = report.entries.filter((entry) => entry.status === 'deferred');
288
+ if (deferred.length > 0) {
289
+ this.logger.error(`${deferred.length} de los ids pedidos NO se ha aplicado (aplazado, arriba el motivo): ` +
290
+ `${deferred.map((entry) => `#${entry.id}`).join(', ')}. El cambio no ha aterrizado en el backend.`);
291
+ this.exitCode = 1;
292
+ }
293
+ if (report.applied.length === 0) {
294
+ if (deferred.length === 0 && notEvaluated.length === 0 && report.missing.length > 0) {
295
+ this.logger.warning(`Ninguno de los ids pedidos se ha aplicado en esta pasada (${report.missing.map((id) => `#${id}`).join(', ')}: ` +
296
+ 'ni pendientes ni aparcados). Si no los aplicó otra pasada, revisa los ids.');
297
+ }
298
+ return;
299
+ }
300
+ }
115
301
  if (report.applied.length === 0) {
116
302
  if (report.dead.length === 0)
117
303
  this.logger.success('La outbox del árbol está vacía: nada que propagar.');
@@ -125,7 +311,6 @@ export default class AuthzScopesRelay extends BaseCommand {
125
311
  }
126
312
  __decorate([
127
313
  flags.boolean({
128
- name: 'dry-run',
129
314
  description: 'List the pending tree changes without applying any of them',
130
315
  }),
131
316
  __metadata("design:type", Object)
@@ -135,7 +320,25 @@ __decorate([
135
320
  __metadata("design:type", Object)
136
321
  ], AuthzScopesRelay.prototype, "limit", void 0);
137
322
  __decorate([
138
- flags.number({ name: 'batch-size', description: 'How many pending changes to read per round trip' }),
323
+ flags.number({ description: 'How many pending changes to read per round trip' }),
139
324
  __metadata("design:type", Object)
140
325
  ], AuthzScopesRelay.prototype, "batchSize", void 0);
326
+ __decorate([
327
+ flags.array({
328
+ description: 'Apply ONLY these outbox ids (the receipt of scopes.*), comma-separated or repeated; dependencies are deferred, never skipped',
329
+ }),
330
+ __metadata("design:type", Object)
331
+ ], AuthzScopesRelay.prototype, "only", void 0);
332
+ __decorate([
333
+ flags.number({
334
+ description: 'Wait up to this many ms for the relay lease instead of reporting busy at once (default 0; needs a pool >= 2)',
335
+ }),
336
+ __metadata("design:type", Object)
337
+ ], AuthzScopesRelay.prototype, "waitLeaseMs", void 0);
338
+ __decorate([
339
+ flags.number({
340
+ description: 'Exit ≠ 0 if the oldest pending entry of the whole queue is older than this many ms (without it the age is only reported)',
341
+ }),
342
+ __metadata("design:type", Object)
343
+ ], AuthzScopesRelay.prototype, "maxAge", void 0);
141
344
  //# sourceMappingURL=authz_scopes_relay.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authz_scopes_relay.js","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"authz_scopes_relay.js","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import { CommandOptions } from '@adonisjs/core/types/ace';
3
+ import type { ScopeRequeueConflict, ScopeRequeueReport } from '../src/types.js';
4
+ /**
5
+ * Las líneas del comando y si la ejecución es «limpia», en una función PURA
6
+ * (mismo patrón que `reconcileLines`/`orphanLines`): es lo único del comando
7
+ * que decide algo, y así tiene su caso sin montar un ace.
8
+ *
9
+ * - `refused`: el manager se NEGÓ (500 `E_AUTHZ_REQUEUE_REFUSED`). Se imprime
10
+ * el DIFF entero —cada aparcada y cada entrada APLICADA posterior que
11
+ * reaplicarla revertiría— y se sale ≠ 0, con las dos salidas: `--force`
12
+ * (que entra igual, con este mismo diff delante) y `authz:reconcile`.
13
+ * - `report`: se reencoló. Con `forced` y `overrides`, el diff se imprime
14
+ * como AVISO: forzar no es un éxito mudo.
15
+ * - `report.relay` (con `--apply`, 2.4.0-alpha.8): la pasada que relevó SOLO
16
+ * lo reencolado, bajo el mismo lease. `busy` ⇒ NO se reencoló nada (todo o
17
+ * nada) y se sale ≠ 0 con la letra («otra pasada tiene el lease; reintenta,
18
+ * o `requeue` sin `--apply`»); cada entrada dice si se aplicó, falló o quedó
19
+ * aplazada, y solo es limpio si TODO lo reencolado se aplicó.
20
+ */
21
+ export declare function requeueLines(input: {
22
+ refused?: ScopeRequeueConflict[];
23
+ report?: ScopeRequeueReport;
24
+ }): {
25
+ lines: Array<{
26
+ level: 'log' | 'warning' | 'error' | 'success';
27
+ message: string;
28
+ }>;
29
+ clean: boolean;
30
+ };
31
+ /**
32
+ * **Devuelve entradas APARCADAS de la outbox a la cola** (2.4.0-alpha.5 · R2b;
33
+ * panel relay, cuestión 2).
34
+ *
35
+ * node ace authz:scopes:requeue --id=12 # una
36
+ * node ace authz:scopes:requeue --id=12,13 # varias (o --id repetido)
37
+ * node ace authz:scopes:requeue --id=12 --force # aunque revierta lo aplicado después (diff impreso)
38
+ * node ace authz:scopes:requeue --id=12 --apply # y la aplica en la MISMA llamada (2.4.0-alpha.8)
39
+ * node ace authz:scopes:requeue --id=12 --apply --wait-lease-ms=5000 # esperando el lease si el cron lo tiene
40
+ *
41
+ * Una entrada aparcada (`sqlScopeOutbox({ maxAttempts })`, 5 por defecto)
42
+ * no se reintenta sola: el relay la reporta en cada pasada y sale ≠ 0. Cuando
43
+ * el operador arregla la causa —el padre que faltaba vuelve, el store se
44
+ * reconcilia— este comando la devuelve a `pending()` con **`attempts =
45
+ * maxAttempts − 1`**: entra a la primera si está arreglada y vuelve a
46
+ * aparcarse en UNA sola pasada si no (así un requeue automatizado de lo
47
+ * irreparable no es un grifo de aplazamiento para el resto del scope).
48
+ *
49
+ * **La sonda anti-reversión es del manager y no tiene excepción**: si después
50
+ * de la aparcada se APLICÓ otra entrada sobre la misma clave de scope,
51
+ * reencolarla reaplicaría el cambio viejo por encima del nuevo —un `moved`
52
+ * viejo tras el nuevo deja el store con el padre VIEJO y un solo padre—. El
53
+ * comando imprime ese diff y sale ≠ 0. **`--force` existe SOLO aquí**
54
+ * (decisión del dueño del 2026-09-09 (2)): imprime el diff y entra igual. Nunca
55
+ * en una ruta alcanzable por HTTP.
56
+ *
57
+ * **`--apply` (2.4.0-alpha.8)**: reencola Y releva SOLO esas entradas en la
58
+ * misma llamada, bajo UN lease (medido: sin esto la reencolada volvía con su
59
+ * id original y la siguiente pasada `only` del tenant salía `depends-on`; el
60
+ * incidente no se cerraba con un comando). Explícito, jamás por defecto: el
61
+ * comando pasa a ESCRIBIR en el backend. No compone con `--force` (500). Pasa
62
+ * la barrera del freeze. Con el lease tomado por otra pasada NO reencola nada
63
+ * y sale ≠ 0 (`--wait-lease-ms` espera, acotado).
64
+ *
65
+ * Es una operación de PLATAFORMA (como `authz:scopes:relay`): sin actor, sin
66
+ * `within`, y no se expone por HTTP.
67
+ */
68
+ export default class AuthzScopesRequeue extends BaseCommand {
69
+ static commandName: string;
70
+ static description: string;
71
+ static options: CommandOptions;
72
+ id: string[] | undefined;
73
+ force: boolean | undefined;
74
+ apply: boolean | undefined;
75
+ waitLeaseMs: number | undefined;
76
+ /** `--id=12,13` o `--id=12 --id=13`: ids numéricos como número, el resto como cadena. */
77
+ static parseIds(values: string[] | undefined): Array<string | number> | undefined;
78
+ run(): Promise<void>;
79
+ }
80
+ //# sourceMappingURL=authz_scopes_requeue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz_scopes_requeue.d.ts","sourceRoot":"","sources":["../../commands/authz_scopes_requeue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAA0B,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAYvG;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAAG;IACtG,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,CAqFA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,OAAO,OAAO,kBAAmB,SAAQ,WAAW;IACzD,MAAM,CAAC,WAAW,SAAyB;IAC3C,MAAM,CAAC,WAAW,SAAkH;IAEpI,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAKO,EAAE,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAKxB,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAK1B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAK1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAEvC,yFAAyF;IACzF,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS;IAS3E,GAAG;CAuBV"}
@@ -0,0 +1,230 @@
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
+ /** Una entrada de la cola, en una línea (misma forma que en `authz:scopes:relay`). */
12
+ function linea(item) {
13
+ const { change } = item;
14
+ const scope = (s) => `${s.type}:${s.uuid ?? ''}`;
15
+ const destino = change.op === 'detached' ? '' : ` → ${scope(change.parent)}`;
16
+ const intentos = item.attempts ? ` (intentos: ${item.attempts})` : '';
17
+ const causa = item.lastError ? ` — ${item.lastError}` : '';
18
+ return `#${item.id} ${change.op} ${scope(change.child)}${destino}${intentos}${causa}`;
19
+ }
20
+ /**
21
+ * Las líneas del comando y si la ejecución es «limpia», en una función PURA
22
+ * (mismo patrón que `reconcileLines`/`orphanLines`): es lo único del comando
23
+ * que decide algo, y así tiene su caso sin montar un ace.
24
+ *
25
+ * - `refused`: el manager se NEGÓ (500 `E_AUTHZ_REQUEUE_REFUSED`). Se imprime
26
+ * el DIFF entero —cada aparcada y cada entrada APLICADA posterior que
27
+ * reaplicarla revertiría— y se sale ≠ 0, con las dos salidas: `--force`
28
+ * (que entra igual, con este mismo diff delante) y `authz:reconcile`.
29
+ * - `report`: se reencoló. Con `forced` y `overrides`, el diff se imprime
30
+ * como AVISO: forzar no es un éxito mudo.
31
+ * - `report.relay` (con `--apply`, 2.4.0-alpha.8): la pasada que relevó SOLO
32
+ * lo reencolado, bajo el mismo lease. `busy` ⇒ NO se reencoló nada (todo o
33
+ * nada) y se sale ≠ 0 con la letra («otra pasada tiene el lease; reintenta,
34
+ * o `requeue` sin `--apply`»); cada entrada dice si se aplicó, falló o quedó
35
+ * aplazada, y solo es limpio si TODO lo reencolado se aplicó.
36
+ */
37
+ export function requeueLines(input) {
38
+ const lines = [];
39
+ const diff = (level, conflict) => {
40
+ lines.push({
41
+ level,
42
+ message: `${level === 'error' ? 'NEGADO' : 'FORZADO'}: ${linea(conflict.parked)} está APARCADA y hay ` +
43
+ `${conflict.applied.length} entrada(s) APLICADA(s) después sobre la misma clave de scope. Reencolarla las REVERTIRÍA ` +
44
+ `(el store quedaría con un árbol que nunca existió, y con UN solo padre nada lo delata):`,
45
+ });
46
+ for (const applied of conflict.applied)
47
+ lines.push({ level, message: ` revertiría ${linea(applied)}` });
48
+ if (conflict.truncated) {
49
+ lines.push({
50
+ level,
51
+ message: ` … y más: solo se listan las primeras ${conflict.applied.length} aplicadas posteriores sobre esa clave; el diff está CORTADO.`,
52
+ });
53
+ }
54
+ };
55
+ if (input.refused !== undefined) {
56
+ for (const conflict of input.refused)
57
+ diff('error', conflict);
58
+ lines.push({
59
+ level: 'warning',
60
+ message: 'No se ha reencolado NADA (todo o nada). Para recalcular el store con el árbol de HOY: ' +
61
+ 'node ace authz:reconcile --to=<driver>. Si de verdad quieres revertir esas entradas, repite con --force: ' +
62
+ 'imprime este mismo diff y entra igual.',
63
+ });
64
+ return { lines, clean: false };
65
+ }
66
+ const report = input.report;
67
+ if (report.relay?.busy) {
68
+ lines.push({
69
+ level: 'error',
70
+ message: 'Otra pasada del relay tiene el lease de la cola: con --apply no se ha reencolado NADA (todo o nada: reencolar y no ' +
71
+ 'aplicar dejaría la entrada con un solo intento, que la siguiente pasada gastaría). Reintenta (--wait-lease-ms espera ' +
72
+ 'a que la suelten), o reencola sin --apply y deja que la próxima pasada la aplique.',
73
+ });
74
+ return { lines, clean: false };
75
+ }
76
+ if (report.forced)
77
+ for (const conflict of report.overrides)
78
+ diff('warning', conflict);
79
+ for (const item of report.requeued)
80
+ lines.push({ level: 'log', message: `reencolada: ${linea(item)}` });
81
+ if (report.relay === undefined) {
82
+ lines.push({
83
+ level: 'success',
84
+ message: `${report.requeued.length} entrada(s) devuelta(s) a la cola con attempts = maxAttempts − 1: si la causa sigue rota ` +
85
+ 'vuelve(n) a aparcarse en UNA pasada; si la arreglaste, entra(n) a la primera. Corre node ace authz:scopes:relay.',
86
+ });
87
+ return { lines, clean: true };
88
+ }
89
+ // --apply: qué fue de cada reencolada en la pasada que corrió bajo el mismo lease.
90
+ const relay = report.relay;
91
+ let clean = true;
92
+ for (const entry of relay.entries) {
93
+ if (entry.status === 'applied')
94
+ lines.push({ level: 'log', message: `aplicada: ${linea(entry)}` });
95
+ else if (entry.status === 'deferred') {
96
+ clean = false;
97
+ lines.push({ level: 'warning', message: `aplazada (${entry.reason ?? 'depende de una entrada anterior'}): ${linea(entry)} — no se ha aplicado` });
98
+ }
99
+ else if (entry.status === 'failed') {
100
+ clean = false;
101
+ lines.push({ level: 'error', message: `falló: ${linea(entry)} — ${entry.reason ?? ''}` });
102
+ }
103
+ else if (entry.status === 'dead') {
104
+ clean = false;
105
+ lines.push({ level: 'error', message: `sigue APARCADA: ${linea(entry)}${entry.reason ? ` — ${entry.reason}` : ''}` });
106
+ }
107
+ }
108
+ if (relay.applied.length !== report.requeued.length)
109
+ clean = false;
110
+ if (relay.remaining) {
111
+ lines.push({
112
+ level: 'warning',
113
+ message: 'Quedan entradas pendientes en la cola (--apply releva SOLO lo reencolado; lo que dependía de ello sigue aplazado): ' +
114
+ 'corre node ace authz:scopes:relay, o releva su recibo con --only.',
115
+ });
116
+ }
117
+ lines.push({
118
+ level: clean ? 'success' : 'warning',
119
+ message: clean
120
+ ? `${relay.applied.length} entrada(s) reencolada(s) y aplicada(s) en la misma pasada.`
121
+ : `${relay.applied.length} de ${report.requeued.length} reencolada(s) aplicada(s): mira las líneas de arriba (la causa sigue rota, o el backend no respondió).`,
122
+ });
123
+ return { lines, clean };
124
+ }
125
+ /**
126
+ * **Devuelve entradas APARCADAS de la outbox a la cola** (2.4.0-alpha.5 · R2b;
127
+ * panel relay, cuestión 2).
128
+ *
129
+ * node ace authz:scopes:requeue --id=12 # una
130
+ * node ace authz:scopes:requeue --id=12,13 # varias (o --id repetido)
131
+ * node ace authz:scopes:requeue --id=12 --force # aunque revierta lo aplicado después (diff impreso)
132
+ * node ace authz:scopes:requeue --id=12 --apply # y la aplica en la MISMA llamada (2.4.0-alpha.8)
133
+ * node ace authz:scopes:requeue --id=12 --apply --wait-lease-ms=5000 # esperando el lease si el cron lo tiene
134
+ *
135
+ * Una entrada aparcada (`sqlScopeOutbox({ maxAttempts })`, 5 por defecto)
136
+ * no se reintenta sola: el relay la reporta en cada pasada y sale ≠ 0. Cuando
137
+ * el operador arregla la causa —el padre que faltaba vuelve, el store se
138
+ * reconcilia— este comando la devuelve a `pending()` con **`attempts =
139
+ * maxAttempts − 1`**: entra a la primera si está arreglada y vuelve a
140
+ * aparcarse en UNA sola pasada si no (así un requeue automatizado de lo
141
+ * irreparable no es un grifo de aplazamiento para el resto del scope).
142
+ *
143
+ * **La sonda anti-reversión es del manager y no tiene excepción**: si después
144
+ * de la aparcada se APLICÓ otra entrada sobre la misma clave de scope,
145
+ * reencolarla reaplicaría el cambio viejo por encima del nuevo —un `moved`
146
+ * viejo tras el nuevo deja el store con el padre VIEJO y un solo padre—. El
147
+ * comando imprime ese diff y sale ≠ 0. **`--force` existe SOLO aquí**
148
+ * (decisión del dueño del 2026-09-09 (2)): imprime el diff y entra igual. Nunca
149
+ * en una ruta alcanzable por HTTP.
150
+ *
151
+ * **`--apply` (2.4.0-alpha.8)**: reencola Y releva SOLO esas entradas en la
152
+ * misma llamada, bajo UN lease (medido: sin esto la reencolada volvía con su
153
+ * id original y la siguiente pasada `only` del tenant salía `depends-on`; el
154
+ * incidente no se cerraba con un comando). Explícito, jamás por defecto: el
155
+ * comando pasa a ESCRIBIR en el backend. No compone con `--force` (500). Pasa
156
+ * la barrera del freeze. Con el lease tomado por otra pasada NO reencola nada
157
+ * y sale ≠ 0 (`--wait-lease-ms` espera, acotado).
158
+ *
159
+ * Es una operación de PLATAFORMA (como `authz:scopes:relay`): sin actor, sin
160
+ * `within`, y no se expone por HTTP.
161
+ */
162
+ export default class AuthzScopesRequeue extends BaseCommand {
163
+ static commandName = 'authz:scopes:requeue';
164
+ static description = 'Return parked scope-tree outbox entries to the queue (refuses to revert later applied changes unless --force)';
165
+ static options = {
166
+ startApp: true,
167
+ };
168
+ /** `--id=12,13` o `--id=12 --id=13`: ids numéricos como número, el resto como cadena. */
169
+ static parseIds(values) {
170
+ if (values === undefined)
171
+ return undefined;
172
+ return values
173
+ .flatMap((value) => String(value).split(','))
174
+ .map((value) => value.trim())
175
+ .filter((value) => value.length > 0)
176
+ .map((value) => (/^\d+$/.test(value) ? Number(value) : value));
177
+ }
178
+ async run() {
179
+ const ids = AuthzScopesRequeue.parseIds(this.id);
180
+ if (ids === undefined || ids.length === 0) {
181
+ this.logger.error('Falta --id: di QUÉ entradas aparcadas reencolar (las que reporta authz:scopes:relay). Sin ids no se reencola nada.');
182
+ this.exitCode = 1;
183
+ return;
184
+ }
185
+ const { default: authorization } = await import('../services/main.js');
186
+ let result;
187
+ try {
188
+ const report = await authorization.requeueScopeChanges(ids, {
189
+ force: this.force === true,
190
+ apply: this.apply === true,
191
+ ...(this.waitLeaseMs === undefined ? {} : { waitForLeaseMs: this.waitLeaseMs }),
192
+ });
193
+ result = requeueLines({ report });
194
+ }
195
+ catch (error) {
196
+ if (error?.code !== 'E_AUTHZ_REQUEUE_REFUSED')
197
+ throw error;
198
+ result = requeueLines({ refused: error.conflicts });
199
+ }
200
+ for (const line of result.lines)
201
+ this.logger[line.level](line.message);
202
+ if (!result.clean)
203
+ this.exitCode = 1;
204
+ }
205
+ }
206
+ __decorate([
207
+ flags.array({
208
+ description: 'Outbox ids to requeue (the parked entries the relay reports), comma-separated or repeated',
209
+ }),
210
+ __metadata("design:type", Object)
211
+ ], AuthzScopesRequeue.prototype, "id", void 0);
212
+ __decorate([
213
+ flags.boolean({
214
+ description: 'Requeue even if a later APPLIED entry shares a scope key (the diff is printed; the store WILL be reverted)',
215
+ }),
216
+ __metadata("design:type", Object)
217
+ ], AuthzScopesRequeue.prototype, "force", void 0);
218
+ __decorate([
219
+ flags.boolean({
220
+ description: 'Also relay ONLY the requeued entries in the same call, under one lease (writes to the backend; not with --force)',
221
+ }),
222
+ __metadata("design:type", Object)
223
+ ], AuthzScopesRequeue.prototype, "apply", void 0);
224
+ __decorate([
225
+ flags.number({
226
+ description: 'With --apply: wait up to this many ms for the relay lease instead of failing at once (pool >= 2)',
227
+ }),
228
+ __metadata("design:type", Object)
229
+ ], AuthzScopesRequeue.prototype, "waitLeaseMs", void 0);
230
+ //# sourceMappingURL=authz_scopes_requeue.js.map