@jantstack/adonis-authz 2.5.0-alpha.2 → 2.5.0-alpha.4

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 (275) hide show
  1. package/README.md +743 -28
  2. package/build/commands/authz_catalog_diff.d.ts +0 -1
  3. package/build/commands/authz_catalog_diff.js +0 -1
  4. package/build/commands/authz_catalog_prune_orphans.d.ts +0 -1
  5. package/build/commands/authz_catalog_prune_orphans.js +0 -1
  6. package/build/commands/authz_catalog_sync.d.ts +0 -1
  7. package/build/commands/authz_catalog_sync.js +0 -1
  8. package/build/commands/authz_freeze.d.ts +0 -1
  9. package/build/commands/authz_freeze.js +0 -1
  10. package/build/commands/authz_reconcile.d.ts +0 -1
  11. package/build/commands/authz_reconcile.js +0 -1
  12. package/build/commands/authz_relations_reconcile.d.ts +6 -1
  13. package/build/commands/authz_relations_reconcile.js +163 -5
  14. package/build/commands/authz_scopes_relay.d.ts +0 -1
  15. package/build/commands/authz_scopes_relay.js +49 -5
  16. package/build/commands/authz_scopes_requeue.d.ts +0 -1
  17. package/build/commands/authz_scopes_requeue.js +14 -2
  18. package/build/commands/authz_unfreeze.d.ts +0 -1
  19. package/build/commands/authz_unfreeze.js +0 -1
  20. package/build/commands/main.d.ts +0 -1
  21. package/build/commands/main.js +0 -1
  22. package/build/commands/openfga_provision.d.ts +72 -1
  23. package/build/commands/openfga_provision.js +236 -4
  24. package/build/configure.d.ts +0 -1
  25. package/build/configure.js +0 -1
  26. package/build/index.d.ts +11 -9
  27. package/build/index.js +12 -5
  28. package/build/providers/authz_provider.d.ts +0 -1
  29. package/build/providers/authz_provider.js +0 -1
  30. package/build/services/main.d.ts +0 -1
  31. package/build/services/main.js +0 -1
  32. package/build/services/relations.d.ts +0 -1
  33. package/build/services/relations.js +0 -1
  34. package/build/src/catalog/catalog.d.ts +0 -1
  35. package/build/src/catalog/catalog.js +0 -1
  36. package/build/src/catalog/catalog_cache.d.ts +13 -1
  37. package/build/src/catalog/catalog_cache.js +25 -1
  38. package/build/src/clock.d.ts +0 -1
  39. package/build/src/clock.js +0 -1
  40. package/build/src/define_config.d.ts +0 -1
  41. package/build/src/define_config.js +24 -1
  42. package/build/src/drivers/database_driver.d.ts +6 -1
  43. package/build/src/drivers/database_driver.js +22 -2
  44. package/build/src/drivers/database_relations_driver.d.ts +46 -5
  45. package/build/src/drivers/database_relations_driver.js +297 -59
  46. package/build/src/drivers/openfga_driver.d.ts +159 -26
  47. package/build/src/drivers/openfga_driver.js +980 -231
  48. package/build/src/drivers/openfga_facts.d.ts +112 -1
  49. package/build/src/drivers/openfga_facts.js +351 -11
  50. package/build/src/drivers/openfga_relations_driver.d.ts +109 -8
  51. package/build/src/drivers/openfga_relations_driver.js +817 -53
  52. package/build/src/errors.d.ts +233 -1
  53. package/build/src/errors.js +283 -1
  54. package/build/src/expiry.d.ts +51 -2
  55. package/build/src/expiry.js +96 -2
  56. package/build/src/freeze.d.ts +0 -1
  57. package/build/src/freeze.js +0 -1
  58. package/build/src/hierarchical_resolver.d.ts +0 -1
  59. package/build/src/hierarchical_resolver.js +0 -1
  60. package/build/src/http/app_access_middleware.d.ts +0 -1
  61. package/build/src/http/app_access_middleware.js +0 -1
  62. package/build/src/http/resource_access_middleware.d.ts +0 -1
  63. package/build/src/http/resource_access_middleware.js +0 -1
  64. package/build/src/identity.d.ts +2 -1
  65. package/build/src/identity.js +4 -1
  66. package/build/src/manager.d.ts +0 -1
  67. package/build/src/manager.js +164 -31
  68. package/build/src/memoize_ancestors.d.ts +0 -1
  69. package/build/src/memoize_ancestors.js +0 -1
  70. package/build/src/models/authz_assignment.d.ts +5 -6
  71. package/build/src/models/authz_assignment.js +0 -1
  72. package/build/src/models/authz_deny.d.ts +5 -6
  73. package/build/src/models/authz_deny.js +0 -1
  74. package/build/src/models/authz_permission.d.ts +5 -6
  75. package/build/src/models/authz_permission.js +0 -1
  76. package/build/src/models/authz_role.d.ts +5 -6
  77. package/build/src/models/authz_role.js +0 -1
  78. package/build/src/models/authz_role_permission.d.ts +5 -6
  79. package/build/src/models/authz_role_permission.js +0 -1
  80. package/build/src/openfga.d.ts +3 -4
  81. package/build/src/openfga.js +5 -3
  82. package/build/src/reconcile.d.ts +0 -1
  83. package/build/src/reconcile.js +0 -1
  84. package/build/src/relation_partition_trigger.d.ts +0 -1
  85. package/build/src/relation_partition_trigger.js +0 -1
  86. package/build/src/relations/check_many.d.ts +1 -2
  87. package/build/src/relations/check_many.js +6 -5
  88. package/build/src/relations/define_relations_config.d.ts +140 -5
  89. package/build/src/relations/define_relations_config.js +205 -6
  90. package/build/src/relations/deny_reach.d.ts +102 -0
  91. package/build/src/relations/deny_reach.js +230 -0
  92. package/build/src/relations/group_depth.d.ts +215 -0
  93. package/build/src/relations/group_depth.js +453 -0
  94. package/build/src/relations/manager.d.ts +46 -8
  95. package/build/src/relations/manager.js +178 -16
  96. package/build/src/relations/page.d.ts +19 -0
  97. package/build/src/relations/page.js +74 -0
  98. package/build/src/relations/reconcile.d.ts +274 -2
  99. package/build/src/relations/reconcile.js +1006 -162
  100. package/build/src/relations/tuple_key.d.ts +0 -1
  101. package/build/src/relations/tuple_key.js +0 -1
  102. package/build/src/relations/tuple_order.d.ts +127 -0
  103. package/build/src/relations/tuple_order.js +599 -0
  104. package/build/src/relations_config_store.d.ts +57 -2
  105. package/build/src/relations_config_store.js +155 -5
  106. package/build/src/relations_reconcile.d.ts +0 -1
  107. package/build/src/relations_reconcile.js +0 -1
  108. package/build/src/scope_outbox.d.ts +0 -1
  109. package/build/src/scope_outbox.js +0 -1
  110. package/build/src/shared/backend_guard.d.ts +60 -1
  111. package/build/src/shared/backend_guard.js +179 -2
  112. package/build/src/shared/sql_expiry.d.ts +0 -1
  113. package/build/src/shared/sql_expiry.js +0 -1
  114. package/build/src/shared/transaction_guard.d.ts +0 -1
  115. package/build/src/shared/transaction_guard.js +0 -1
  116. package/build/src/sql_descendants.d.ts +0 -1
  117. package/build/src/sql_descendants.js +0 -1
  118. package/build/src/testing/contract.d.ts +7 -1
  119. package/build/src/testing/contract.js +77 -1
  120. package/build/src/testing/main.d.ts +1 -2
  121. package/build/src/testing/main.js +1 -2
  122. package/build/src/testing/migration_contract.d.ts +0 -1
  123. package/build/src/testing/migration_contract.js +0 -1
  124. package/build/src/testing/relations_contract.d.ts +25 -1
  125. package/build/src/testing/relations_contract.js +996 -49
  126. package/build/src/testing/relations_reconcile_contract.d.ts +9 -1
  127. package/build/src/testing/relations_reconcile_contract.js +207 -1
  128. package/build/src/testing/scope_outbox_contract.d.ts +0 -1
  129. package/build/src/testing/scope_outbox_contract.js +0 -1
  130. package/build/src/testing/scope_tree.d.ts +0 -1
  131. package/build/src/testing/scope_tree.js +0 -1
  132. package/build/src/testing/test_clock.d.ts +0 -1
  133. package/build/src/testing/test_clock.js +0 -1
  134. package/build/src/traits/authz_scopes.d.ts +0 -1
  135. package/build/src/traits/authz_scopes.js +0 -1
  136. package/build/src/traits/has_uuid.d.ts +5 -6
  137. package/build/src/traits/has_uuid.js +0 -1
  138. package/build/src/types.d.ts +170 -15
  139. package/build/src/types.js +6 -1
  140. package/build/stubs/config/authorization.stub +22 -6
  141. package/build/stubs/main.d.ts +0 -1
  142. package/build/stubs/main.js +0 -1
  143. package/package.json +4 -3
  144. package/build/commands/authz_catalog_diff.d.ts.map +0 -1
  145. package/build/commands/authz_catalog_diff.js.map +0 -1
  146. package/build/commands/authz_catalog_prune_orphans.d.ts.map +0 -1
  147. package/build/commands/authz_catalog_prune_orphans.js.map +0 -1
  148. package/build/commands/authz_catalog_sync.d.ts.map +0 -1
  149. package/build/commands/authz_catalog_sync.js.map +0 -1
  150. package/build/commands/authz_freeze.d.ts.map +0 -1
  151. package/build/commands/authz_freeze.js.map +0 -1
  152. package/build/commands/authz_reconcile.d.ts.map +0 -1
  153. package/build/commands/authz_reconcile.js.map +0 -1
  154. package/build/commands/authz_relations_reconcile.d.ts.map +0 -1
  155. package/build/commands/authz_relations_reconcile.js.map +0 -1
  156. package/build/commands/authz_scopes_relay.d.ts.map +0 -1
  157. package/build/commands/authz_scopes_relay.js.map +0 -1
  158. package/build/commands/authz_scopes_requeue.d.ts.map +0 -1
  159. package/build/commands/authz_scopes_requeue.js.map +0 -1
  160. package/build/commands/authz_unfreeze.d.ts.map +0 -1
  161. package/build/commands/authz_unfreeze.js.map +0 -1
  162. package/build/commands/main.d.ts.map +0 -1
  163. package/build/commands/main.js.map +0 -1
  164. package/build/commands/openfga_provision.d.ts.map +0 -1
  165. package/build/commands/openfga_provision.js.map +0 -1
  166. package/build/configure.d.ts.map +0 -1
  167. package/build/configure.js.map +0 -1
  168. package/build/index.d.ts.map +0 -1
  169. package/build/index.js.map +0 -1
  170. package/build/providers/authz_provider.d.ts.map +0 -1
  171. package/build/providers/authz_provider.js.map +0 -1
  172. package/build/services/main.d.ts.map +0 -1
  173. package/build/services/main.js.map +0 -1
  174. package/build/services/relations.d.ts.map +0 -1
  175. package/build/services/relations.js.map +0 -1
  176. package/build/src/catalog/catalog.d.ts.map +0 -1
  177. package/build/src/catalog/catalog.js.map +0 -1
  178. package/build/src/catalog/catalog_cache.d.ts.map +0 -1
  179. package/build/src/catalog/catalog_cache.js.map +0 -1
  180. package/build/src/clock.d.ts.map +0 -1
  181. package/build/src/clock.js.map +0 -1
  182. package/build/src/define_config.d.ts.map +0 -1
  183. package/build/src/define_config.js.map +0 -1
  184. package/build/src/drivers/database_driver.d.ts.map +0 -1
  185. package/build/src/drivers/database_driver.js.map +0 -1
  186. package/build/src/drivers/database_relations_driver.d.ts.map +0 -1
  187. package/build/src/drivers/database_relations_driver.js.map +0 -1
  188. package/build/src/drivers/openfga_driver.d.ts.map +0 -1
  189. package/build/src/drivers/openfga_driver.js.map +0 -1
  190. package/build/src/drivers/openfga_facts.d.ts.map +0 -1
  191. package/build/src/drivers/openfga_facts.js.map +0 -1
  192. package/build/src/drivers/openfga_relations_driver.d.ts.map +0 -1
  193. package/build/src/drivers/openfga_relations_driver.js.map +0 -1
  194. package/build/src/errors.d.ts.map +0 -1
  195. package/build/src/errors.js.map +0 -1
  196. package/build/src/expiry.d.ts.map +0 -1
  197. package/build/src/expiry.js.map +0 -1
  198. package/build/src/freeze.d.ts.map +0 -1
  199. package/build/src/freeze.js.map +0 -1
  200. package/build/src/hierarchical_resolver.d.ts.map +0 -1
  201. package/build/src/hierarchical_resolver.js.map +0 -1
  202. package/build/src/http/app_access_middleware.d.ts.map +0 -1
  203. package/build/src/http/app_access_middleware.js.map +0 -1
  204. package/build/src/http/resource_access_middleware.d.ts.map +0 -1
  205. package/build/src/http/resource_access_middleware.js.map +0 -1
  206. package/build/src/identity.d.ts.map +0 -1
  207. package/build/src/identity.js.map +0 -1
  208. package/build/src/manager.d.ts.map +0 -1
  209. package/build/src/manager.js.map +0 -1
  210. package/build/src/memoize_ancestors.d.ts.map +0 -1
  211. package/build/src/memoize_ancestors.js.map +0 -1
  212. package/build/src/models/authz_assignment.d.ts.map +0 -1
  213. package/build/src/models/authz_assignment.js.map +0 -1
  214. package/build/src/models/authz_deny.d.ts.map +0 -1
  215. package/build/src/models/authz_deny.js.map +0 -1
  216. package/build/src/models/authz_permission.d.ts.map +0 -1
  217. package/build/src/models/authz_permission.js.map +0 -1
  218. package/build/src/models/authz_role.d.ts.map +0 -1
  219. package/build/src/models/authz_role.js.map +0 -1
  220. package/build/src/models/authz_role_permission.d.ts.map +0 -1
  221. package/build/src/models/authz_role_permission.js.map +0 -1
  222. package/build/src/openfga.d.ts.map +0 -1
  223. package/build/src/openfga.js.map +0 -1
  224. package/build/src/reconcile.d.ts.map +0 -1
  225. package/build/src/reconcile.js.map +0 -1
  226. package/build/src/relation_partition_trigger.d.ts.map +0 -1
  227. package/build/src/relation_partition_trigger.js.map +0 -1
  228. package/build/src/relations/check_many.d.ts.map +0 -1
  229. package/build/src/relations/check_many.js.map +0 -1
  230. package/build/src/relations/define_relations_config.d.ts.map +0 -1
  231. package/build/src/relations/define_relations_config.js.map +0 -1
  232. package/build/src/relations/manager.d.ts.map +0 -1
  233. package/build/src/relations/manager.js.map +0 -1
  234. package/build/src/relations/reconcile.d.ts.map +0 -1
  235. package/build/src/relations/reconcile.js.map +0 -1
  236. package/build/src/relations/tuple_key.d.ts.map +0 -1
  237. package/build/src/relations/tuple_key.js.map +0 -1
  238. package/build/src/relations_config_store.d.ts.map +0 -1
  239. package/build/src/relations_config_store.js.map +0 -1
  240. package/build/src/relations_reconcile.d.ts.map +0 -1
  241. package/build/src/relations_reconcile.js.map +0 -1
  242. package/build/src/scope_outbox.d.ts.map +0 -1
  243. package/build/src/scope_outbox.js.map +0 -1
  244. package/build/src/shared/backend_guard.d.ts.map +0 -1
  245. package/build/src/shared/backend_guard.js.map +0 -1
  246. package/build/src/shared/sql_expiry.d.ts.map +0 -1
  247. package/build/src/shared/sql_expiry.js.map +0 -1
  248. package/build/src/shared/transaction_guard.d.ts.map +0 -1
  249. package/build/src/shared/transaction_guard.js.map +0 -1
  250. package/build/src/sql_descendants.d.ts.map +0 -1
  251. package/build/src/sql_descendants.js.map +0 -1
  252. package/build/src/testing/contract.d.ts.map +0 -1
  253. package/build/src/testing/contract.js.map +0 -1
  254. package/build/src/testing/main.d.ts.map +0 -1
  255. package/build/src/testing/main.js.map +0 -1
  256. package/build/src/testing/migration_contract.d.ts.map +0 -1
  257. package/build/src/testing/migration_contract.js.map +0 -1
  258. package/build/src/testing/relations_contract.d.ts.map +0 -1
  259. package/build/src/testing/relations_contract.js.map +0 -1
  260. package/build/src/testing/relations_reconcile_contract.d.ts.map +0 -1
  261. package/build/src/testing/relations_reconcile_contract.js.map +0 -1
  262. package/build/src/testing/scope_outbox_contract.d.ts.map +0 -1
  263. package/build/src/testing/scope_outbox_contract.js.map +0 -1
  264. package/build/src/testing/scope_tree.d.ts.map +0 -1
  265. package/build/src/testing/scope_tree.js.map +0 -1
  266. package/build/src/testing/test_clock.d.ts.map +0 -1
  267. package/build/src/testing/test_clock.js.map +0 -1
  268. package/build/src/traits/authz_scopes.d.ts.map +0 -1
  269. package/build/src/traits/authz_scopes.js.map +0 -1
  270. package/build/src/traits/has_uuid.d.ts.map +0 -1
  271. package/build/src/traits/has_uuid.js.map +0 -1
  272. package/build/src/types.d.ts.map +0 -1
  273. package/build/src/types.js.map +0 -1
  274. package/build/stubs/main.d.ts.map +0 -1
  275. package/build/stubs/main.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import type { Clock } from '../clock.js';
2
2
  import type { RelationsDriver, ScopeRef } from '../types.js';
3
+ import type { DenyReachFinding } from './deny_reach.js';
3
4
  import type { RelationsConfig } from './define_relations_config.js';
4
5
  export interface RelationsReconcileOptions {
5
6
  /** El driver del que se LEEN las tuplas (origen). Necesita `enumerateRelations`. */
@@ -50,6 +51,16 @@ export interface RelationsReconcileOptions {
50
51
  * sigue — nunca un throw a mitad de pasada.
51
52
  */
52
53
  toConfig?: RelationsConfig;
54
+ /**
55
+ * **El detector del alcance de los denies (8)**: con él, la pasada lista en `report.denyReach` los denies vivos de sus
56
+ * particiones (origen y destino) cuyo alcance encoge de `baseline` (la config persistida; `null` = sin línea base, solo
57
+ * huérfanos) a `current` (la config que sirve el proceso). Solo LEE lo que la pasada ya enumeró: cero lecturas y cero
58
+ * escrituras de más. Lo pide `authz:relations:reconcile --dry-run`.
59
+ */
60
+ denyReach?: {
61
+ baseline: RelationsConfig | null;
62
+ current: RelationsConfig;
63
+ };
53
64
  /**
54
65
  * El reloj que decide qué tupla del ORIGEN está CADUCADA (R-15): una caducada
55
66
  * no se migra —se cuenta en `skipped.expired`— y no respalda nada en el
@@ -71,6 +82,17 @@ export interface RelationsReconcileOptions {
71
82
  * en el origen no se migra (la API no puede escribirla) y es deriva. Nunca un
72
83
  * 422 por un dato que no vino del llamante.
73
84
  */
85
+ /**
86
+ * **Los motivos de `RelationsReconcileDetail`, en RUNTIME** (cierre 2 de D2 · ⚪ N6): la guardia L9
87
+ * (`tests/relations_reconcile_l9.spec.ts`) los recorre y exige que cada uno tenga su entrada en `RELATION_RESIDUES`
88
+ * (dónde se clasifica: el PLAN o la PASADA) y su caso. `renewal-unsafe` (cierre 2 de D2 · 🟠 N1, solo `destino`): la
89
+ * renovación (otra caducidad) de una membresía `group:*#member` que TRANSPORTA un deny, en un destino que no declara
90
+ * `atomicRenewal` (renueva con delete + write, `openfga`): entre los dos `Write` el deny no alcanzaría al sujeto. Desde el
91
+ * cierre 5 de D2 (opción A) NIEGA SU PARTICIÓN (`partitionsRefused`: cero escrituras y cero borrados en ella, el resto
92
+ * migra); la línea la nombra con su receta y es deriva. `withheld-by-unsafe-deny` (cierre 4) se fue con la retención.
93
+ */
94
+ export declare const RELATIONS_RECONCILE_DETAIL_REASONS: readonly ["invalid-identity", "userset-unsupported", "undeclared", "deny-conditional", "renewal-unsafe", "invalid-condition"];
95
+ export type RelationsReconcileDetailReason = (typeof RELATIONS_RECONCILE_DETAIL_REASONS)[number];
74
96
  export interface RelationsReconcileDetail {
75
97
  /** De qué store llegó enumerada. */
76
98
  side: 'origen' | 'destino';
@@ -95,8 +117,14 @@ export interface RelationsReconcileDetail {
95
117
  * (hasta el cierre 3 ese 422 salía como stack crudo a mitad de pasada,
96
118
  * después de escribir, y `--prune` quedaba inutilizable para esa partición).
97
119
  * Receta: retírala a mano o vuelve a declarar la relación en el destino.
120
+ * `deny-conditional` (cierre 1 de D2 · 🟡 4; cierre 4 · 🟡 C3-2, solo `destino`): un deny del destino CON caducidad
121
+ * (escrito fuera del paquete, D3) —vivo con otra caducidad o YA VENCIDO— que el origen tiene, en un destino que no
122
+ * declara `atomicRenewal` (`openfga` no reemplaza la misma clave en un `Write`, M5). Lo decide el PLAN (mismos números
123
+ * en `--dry-run` y real): no se reescribe, y desde el cierre 5 de D2 (opción A) NIEGA SU PARTICIÓN —cero escrituras y
124
+ * cero borrados en ella (`partitionsRefused`), el resto migra—; lleva la receta (`removeDeny` + `deny`, con su ventana
125
+ * declarada) y es deriva.
98
126
  */
99
- reason: 'invalid-identity' | 'userset-unsupported' | 'undeclared';
127
+ reason: RelationsReconcileDetailReason;
100
128
  /** La tupla, legible: `<partición> <tipo>:<id>#<relación>@<sujeto>`. */
101
129
  tuple: string;
102
130
  /** El porqué: el mensaje de la gramática que la rechazó. */
@@ -244,11 +272,255 @@ export interface RelationsReconcileReport {
244
272
  from: string;
245
273
  allowed: boolean;
246
274
  };
275
+ /**
276
+ * **Los DENIES de la pasada, aparte** (plan 2.5 · lote D · D2; analista §4.7, tester T3): el SUBCONJUNTO de los
277
+ * contadores de arriba que son denies (`written`/`updated`/`deleted`/`unchanged`/`extra` cuentan allows + denies).
278
+ * `updated` = un deny del destino CON caducidad (escrito a mano) que el origen tiene distinto: se rehace permanente
279
+ * con `to.deny` (D3: el deny no caduca; `database` lo rehace en una transacción, `openfga` responde 409 ⇒ 503 ruidoso).
280
+ * Opcional en el tipo exportado (un reporte de terceros anterior sigue compilando).
281
+ */
282
+ denies?: {
283
+ written: number;
284
+ updated: number;
285
+ deleted: number;
286
+ unchanged: number;
287
+ extra: number;
288
+ };
289
+ /**
290
+ * **Los denies que la pasada NO puede migrar** (D2; S6/R2-5): solo en `--dry-run` (la pasada real lanza 500
291
+ * `E_AUTHZ_DENY_MIGRATION_REFUSED` con CERO escrituras y las mismas entradas en `error.refusals`). Es deriva: el
292
+ * verificador sale ≠ 0 con la línea «deny NO migrable (<motivo>) …». Ausente = ninguno.
293
+ */
294
+ denyRefused?: DenyRefusal[];
295
+ /**
296
+ * **El reporte POR PARTICIÓN** (roadmap 2.5, 🟡 3 del auditor de alpha.6): el desglose de los contadores y el
297
+ * estado de cada partición pedida, en orden. Si la pasada revienta a mitad, el error lleva `report` con
298
+ * `partial: true`, las particiones ya escritas `done`, la que reventó `failed` (con lo que llegó a escribir) y las
299
+ * demás `pending`, y va marcado con `markPartialWrite` si se escribió algo.
300
+ */
301
+ partitions?: RelationsReconcilePartitionReport[];
302
+ /**
303
+ * Solo en el reporte que viaja en un error (`error.report`): `true` si la pasada se cortó a mitad DESPUÉS de escribir
304
+ * algo; `false` si reventó sin haber escrito nada (⚪ 10 del cierre 1 de D2: hasta entonces era `true` siempre y el
305
+ * comando imprimía «pasada PARCIAL: escritas 0»). Desde el cierre 2 de D2 (🟠 N1) también `true` si el DRIVER marcó
306
+ * el error como escritura parcial (`isPartialWrite`: un `relate` de renovación que ya borró), aunque la pasada no
307
+ * hubiera completado ninguna escritura.
308
+ */
309
+ partial?: boolean;
310
+ /**
311
+ * **El RESIDUO del orden** (cierre 2 de D2 · 🟡 N3, linaje L10): las membresías de un CICLO del grafo de precedencias de
312
+ * `relationTransitionOrder` — dos membresías que cambian en la misma pasada cuyas exigencias de orden se contradicen
313
+ * (típicamente: el mismo sujeto entra, o sale, a la vez en dos grupos y cada uno trae un deny que corta un allow que
314
+ * trae el otro). Para ellas no hay orden garantizado: un corte entre las dos puede conceder lo que ni el estado inicial
315
+ * ni el final concedían (para el resto de la pasada, la garantía vale). El estado FINAL es el correcto; no es deriva:
316
+ * el comando lo AVISA con el prefijo estable «orden SIN garantía». Lo mismo en `--dry-run` que en la pasada real.
317
+ * Opcional en el tipo exportado; ausente = `[]`.
318
+ */
319
+ orderResidue?: string[];
320
+ /**
321
+ * **Las particiones NEGADAS** (cierre 5 de D2, decisión del dueño §2026-10-16 (10), opción A): una partición con al
322
+ * menos un `renewal-unsafe` (la caducidad de una membresía que transporta un deny cambia, y el destino no la puede
323
+ * renovar en una escritura) o un `deny-conditional` (un deny del destino con caducidad que no se puede rehacer) recibe
324
+ * CERO escrituras y CERO borrados —también con `--prune`, también las vencidas—: su destino queda EXACTAMENTE como
325
+ * estaba, con la deriva previa a la vista (`pending`). El resto de particiones migra. Lo decide el PLAN: `--dry-run` y
326
+ * la pasada real dan los mismos números. Es deriva (exit ≠ 0) mientras exista. Opcional en el tipo; ausente = `[]`.
327
+ */
328
+ partitionsRefused?: RelationsPartitionRefusal[];
329
+ /**
330
+ * **Las particiones cuya cadena de grupos FINAL excede el techo del destino** (lote «antes de RC» · R1, decisión del
331
+ * dueño 2026-10-17 (5); 🔴 C-1 del auditor): por cada una, la profundidad (saltos `group⊂group` del camino más largo,
332
+ * con los ciclos contados de forma conservadora), el techo (`to.groupDepthLimit()`, el CALIBRADO del destino) y la
333
+ * cadena. Esa partición está además en `partitionsRefused` (causa `too-deep`): cero escrituras en ella — saltar la
334
+ * arista que excede quitaría los denies que transporta y fabricaría una concesión —. Lo decide el PLAN: mismos números
335
+ * en `--dry-run` y en la real. Opcional en el tipo; ausente = `[]`.
336
+ */
337
+ tooDeep?: Array<{
338
+ partition: string;
339
+ depth: number;
340
+ limit: number;
341
+ chain: string[];
342
+ }>;
343
+ /**
344
+ * **El detector de (8)** (lote RC · sublote (8)): solo si se pidió (`denyReach` en las opciones; el comando lo pide en
345
+ * `--dry-run`). Los denies VIVOS de las particiones de la pasada —en el ORIGEN y en el DESTINO, enumerados por la misma
346
+ * pasada— juzgados con `denyReachCheck` (`src/relations/deny_reach.ts`, el mismo punto de paso que la guarda de
347
+ * `database` y que `checkRelationsConfigDenyReach`), en las dos direcciones entre la línea base (`baseline`: la config
348
+ * persistida) y la config que SIRVE el proceso (`current`). Sirve a los dos drivers; `findings` y `stale` son deriva
349
+ * (exit ≠ 0); `unjudged` (sin línea base) NO lo es (firma (8 · `--dry-run`): exit 0 con su línea, el runbook no cambia).
350
+ */
351
+ denyReach?: {
352
+ /** `'persisted'`: se comparó contra la config persistida; `'none'`: sin línea base, solo se buscan huérfanos. */
353
+ baseline: 'persisted' | 'none';
354
+ /** Persistida → servida: `shrunk` y `orphan` (también sin línea base). `count` = claves de tupla DISTINTAS (firma (8 · `count`)). */
355
+ findings: Array<DenyReachFinding & {
356
+ sides: Array<'origen' | 'destino'>;
357
+ }>;
358
+ /** Servida → persistida: la línea base RANCIA (`stale`, solo con línea base; firma (8 · rancia)). */
359
+ stale: Array<DenyReachFinding & {
360
+ sides: Array<'origen' | 'destino'>;
361
+ }>;
362
+ /** Sin línea base: cuántos denies vivos (claves distintas, no huérfanos) quedan SIN juzgar. `0` con línea base. */
363
+ unjudged: number;
364
+ };
365
+ }
366
+ /**
367
+ * Por qué se NIEGA una partición: `renewal-unsafe` (membresía portadora que cambia de caducidad), `deny-conditional` (deny
368
+ * con caducidad que el destino no puede rehacer) o, desde el cierre de L11 (🟡 A2 del auditor), `invalid-condition`: una
369
+ * tupla de `group` (una membresía, portadora posible) con una condición que el paquete no escribe ni sabe leer
370
+ * (`foreign`/`unparseable`) en una partición con ALGÚN deny — el servidor puede estar evaluándola VIVA (un `not_expired`
371
+ * con `current_time` congelado lo está, medido) y transportando ese deny; saltarla como allow fabricaba una concesión.
372
+ */
373
+ export type RelationsPartitionRefusalReason = 'renewal-unsafe' | 'deny-conditional' | 'invalid-condition' | 'too-deep';
374
+ /** Una partición NEGADA (cierre 5 de D2): por qué, qué receta, y lo que la pasada habría hecho en ella y NO hizo. */
375
+ export interface RelationsPartitionRefusal {
376
+ partition: string;
377
+ /** Las causas, una por tupla: `renewal-unsafe` (membresía portadora) o `deny-conditional` (deny con caducidad). */
378
+ causes: Array<{
379
+ reason: RelationsPartitionRefusalReason;
380
+ tuple: string;
381
+ }>;
382
+ /** La deriva que queda a la vista: lo que la pasada escribiría, renovaría y (con `--prune`) borraría en ella. */
383
+ pending: {
384
+ write: number;
385
+ update: number;
386
+ delete: number;
387
+ };
388
+ /** La receta (`partitionRefusalRecipe`): honesta — en un destino sin `atomicRenewal` no hay forma sin ventana. */
389
+ recipe: string;
390
+ /**
391
+ * Cierre 6 de D2 · ⚪ D5-3: «COMO ESTABA» no es estático. Por cada membresía portadora que el origen ALARGA: desde `at`
392
+ * (su caducidad vieja en el destino) el destino deja de transportar los denies `cut` a `member` y CONCEDE lo que cortan
393
+ * si otro camino lo da —el origen lo sigue negando—. Ausente si no aplica.
394
+ */
395
+ evolves?: Array<{
396
+ at: string;
397
+ member: string;
398
+ cut: string[];
399
+ }>;
400
+ }
401
+ /** El desglose de UNA partición (D2). `status`: `done` (terminó), `failed` (reventó aquí), `pending` (no se tocó), `refused` (cierre 5: negada por el plan, cero escrituras). */
402
+ export interface RelationsReconcilePartitionReport {
403
+ partition: string;
404
+ status: 'done' | 'failed' | 'pending' | 'refused';
405
+ written: number;
406
+ updated: number;
407
+ deleted: number;
408
+ unchanged: number;
409
+ extra: number;
410
+ denies: {
411
+ written: number;
412
+ updated: number;
413
+ deleted: number;
414
+ unchanged: number;
415
+ extra: number;
416
+ };
247
417
  }
418
+ /**
419
+ * Un deny que la pasada no puede migrar (D2), con su motivo — uno de un conjunto CERRADO (cada uno su línea):
420
+ * - `no-capability`: el destino no declara `relationDenies` (o no trae `deny`/`removeDeny`);
421
+ * - `no-deny-phase`: el destino declara `relationDenies` pero no trae `denyPhase` (no puede decir si el tipo admite deny);
422
+ * - `phase`: el tipo no está en la fase que hace falta EN EL DESTINO (`'write'` para escribir; con opt-in para uno ya presente);
423
+ * - `undeclared`: el destino no declara el par tipo/relación (F-05);
424
+ * - `model`: el modelo FIJADO del destino no declara `denied_<rel>` (D-31, `openfga`);
425
+ * - `unmarked`: el ORIGEN enumera una tupla `denied_*` sin `kind: 'deny'` (un driver que no marca);
426
+ * - `invalid`: el deny del origen tiene una identidad o un sujeto que el destino no admite (gramática / B3).
427
+ *
428
+ * `carrier-conflict` (cierre 4 de D2) se fue en el cierre 5 con la retención: lo que no viaja niega SU partición
429
+ * (`partitionsRefused`), nunca la pasada entera.
430
+ */
431
+ export interface DenyRefusal {
432
+ objectType: string;
433
+ relation: string;
434
+ reason: 'no-capability' | 'no-deny-phase' | 'phase' | 'undeclared' | 'model' | 'unmarked' | 'invalid';
435
+ message: string;
436
+ /**
437
+ * Cierre 1 de D2 · 🟡 6: la receta de SU motivo (`denyRefusalRecipe`), una que funciona por la API o que dice «a mano»
438
+ * cuando la API no puede. Hasta el cierre todas llevaban la misma («pon el tipo en 'write' o retira esos denies del
439
+ * origen»), que para `undeclared` daba 422 (F-05 en `removeDeny`) y para `unmarked`/`invalid` no aplicaba.
440
+ */
441
+ recipe: string;
442
+ }
443
+ /**
444
+ * **Una receta POR MOTIVO** (cierre 1 de D2 · 🟡 6, ⚪ 11). `honor` = el deny ya está en el destino y lo que falta es
445
+ * que lo honre (opt-in), no que lo escriba. La de `undeclared` está EJECUTADA en `relations_reconcile_deny.spec`
446
+ * (re-declarar → `removeDeny` → retirar ⇒ la pasada entra).
447
+ */
448
+ export declare function denyRefusalRecipe(reason: DenyRefusal['reason'], objectType: string, relation: string, honor?: boolean): string;
449
+ /**
450
+ * **La receta de una partición NEGADA** (cierre 5 de D2, opción A), por el conjunto de causas. HONESTA a propósito: en
451
+ * un destino sin `atomicRenewal` (`openfga`: FGA no reemplaza la misma clave en un `Write` —no hay upsert,
452
+ * openfga/openfga#2151—) NO existe forma sin ventana de cambiar la caducidad de una membresía que transporta un deny, ni
453
+ * de rehacer permanente un deny con caducidad. Ninguna de las salidas de abajo se presenta como segura.
454
+ */
455
+ export declare function partitionRefusalRecipe(reasons: Iterable<RelationsPartitionRefusalReason>): string;
248
456
  /**
249
457
  * Migra las tuplas de relación de `from` a `to` en `partition`. Idempotente
250
458
  * (una segunda pasada no escribe nada) y bidireccional (funciona en cualquier
251
459
  * sentido). Nunca silenciosa: devuelve `written`/`deleted`/`unchanged`/`extra`.
252
460
  */
253
461
  export declare function reconcileRelations(options: RelationsReconcileOptions): Promise<RelationsReconcileReport>;
254
- //# sourceMappingURL=reconcile.d.ts.map
462
+ /**
463
+ * **¿Cuenta una tupla del DESTINO en el plan?** (cierre 3 de D2 · 🟠 C2-1 / 🟡 C2-2): sí si está VIVA en `now` (el mismo
464
+ * `now` de toda la pasada, con el que el origen descarta sus caducadas); una VENCIDA es AUSENTE — no conecta nada en
465
+ * ningún driver—, así que ni se «renueva» ni entra en el contexto del orden. Punto de paso ÚNICO de la regla: lo usa
466
+ * `planPartition` y la fuerza bruta del orden (`tests/helpers/order_bruteforce.ts`, universo con vigencia) deriva con
467
+ * ÉL qué es escritura, renovación y contexto — un mutante que ignore la vigencia aquí pone rojos los dos.
468
+ */
469
+ export declare function liveInDestination(tuple: {
470
+ expiresAt?: Date | null;
471
+ }, now: Date): boolean;
472
+ /**
473
+ * **El REGISTRO de los 422 del puerto de relaciones, clasificados** (plan 2.5 · lote D · D2; linaje **L9** «clasificación
474
+ * en el punto equivocado», decisión del dueño 2026-10-15 (7): R2-5 → lote B decisión 4 → cierre 3 N9). Propiedad
475
+ * universal: **el PLAN clasifica toda tupla por su motivo ANTES del driver; el driver solo rechaza; ningún catch convierte
476
+ * un 422 en otro bucket que no sea el que este registro dice**. Cada clase 422 que el puerto de relaciones puede lanzar
477
+ * tiene aquí UNA entrada:
478
+ *
479
+ * - `classified`: el plan la anticipa (`enumeratedKey` / `classifySubject` / `classifyDestinationRow` / `toConfig`) y la
480
+ * lista con `reason` en `details` (o la cuenta en `skipped`) sin pedírsela al driver; si el driver la lanza igual (sin
481
+ * `toConfig`, o un driver de terceros que contradice al plan), la capa 2 (`relateOrSkip`/`deleteOrClassify`) la
482
+ * clasifica con ESTE mismo `reason` y sigue;
483
+ * - `refused`: el plan NIEGA la pasada (500 `E_AUTHZ_DENY_MIGRATION_REFUSED`, cero escrituras) con ese motivo — la puerta
484
+ * del deny (`denyGate`, por `denyPhase` del destino);
485
+ * - `unreachable`: ninguna llamada que el reconcile hace al driver con una tupla PLANIFICADA puede lanzarla, con su porqué.
486
+ *
487
+ * `tests/relations_reconcile_l9.spec.ts` lo cruza POR REFLEXIÓN con las clases 422 de `src/errors.ts` que nombran los
488
+ * ficheros del puerto (`src/relations/`, los dos drivers de relaciones, el doble y `src/identity.ts`): una clase 422 nueva
489
+ * sin entrada nace ROJA con su nombre, una entrada sin clase es rancia, y cada entrada `classified`/`refused` tiene un
490
+ * caso que la ejecuta. `RELATION_422_REASONS` (la tabla de la capa 2, cierre 3 del lote B) se DERIVA de aquí.
491
+ */
492
+ export type RelationRejection = {
493
+ kind: 'classified';
494
+ reason: RelationsReconcileDetail['reason'];
495
+ where: string;
496
+ } | {
497
+ kind: 'refused';
498
+ refusal: DenyRefusal['reason'];
499
+ where: string;
500
+ } | {
501
+ kind: 'unreachable';
502
+ why: string;
503
+ };
504
+ export declare const RELATION_REJECTIONS: Readonly<Record<string, RelationRejection>>;
505
+ /**
506
+ * **Los 422 de la FAMILIA de relaciones, clasificados por `code`** (cierre 3 del lote B · 🟠 N9, capa 2) — DERIVADA del
507
+ * registro L9: las entradas `classified`. Por `code` y `status`, no por clase: un driver de terceros lanza su propia
508
+ * clase con el mismo código. Fuera de esta tabla NADA se absorbe.
509
+ */
510
+ export declare const RELATION_422_REASONS: Readonly<Record<string, RelationsReconcileDetail['reason']>>;
511
+ /**
512
+ * **El registro de los RESIDUOS de la pasada, por motivo** (cierre 2 de D2 · ⚪ N6; linaje L9): cada motivo de
513
+ * `RelationsReconcileDetail` dice DÓNDE se clasifica —en el PLAN (antes del driver: los mismos números en `--dry-run` y
514
+ * en la pasada real, punto 2 de L9) o en la PASADA (solo el driver lo sabe: EXCEPCIÓN declarada al punto 2, con su
515
+ * porqué)—. `tests/relations_reconcile_l9.spec.ts` cruza las claves con `RELATIONS_RECONCILE_DETAIL_REASONS` (un motivo
516
+ * nuevo sin entrada nace ROJO) y ejecuta un caso por entrada. Hasta el cierre 2 `deny-conditional` (un `catch` del 500
517
+ * marcado en la pasada) quedaba fuera de la guardia, que solo enumeraba 422.
518
+ */
519
+ export declare const RELATION_RESIDUES: Readonly<Record<RelationsReconcileDetailReason, {
520
+ where: 'plan';
521
+ how: string;
522
+ } | {
523
+ where: 'pass';
524
+ how: string;
525
+ why: string;
526
+ }>>;