@jantstack/adonis-authz 2.5.0-alpha.3 → 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 (277) hide show
  1. package/README.md +414 -35
  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 +0 -1
  13. package/build/commands/authz_relations_reconcile.js +56 -8
  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 +10 -1
  23. package/build/commands/openfga_provision.js +43 -3
  24. package/build/configure.d.ts +0 -1
  25. package/build/configure.js +0 -1
  26. package/build/index.d.ts +7 -5
  27. package/build/index.js +10 -3
  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 +11 -5
  45. package/build/src/drivers/database_relations_driver.js +90 -18
  46. package/build/src/drivers/openfga_driver.d.ts +159 -26
  47. package/build/src/drivers/openfga_driver.js +882 -199
  48. package/build/src/drivers/openfga_facts.d.ts +91 -1
  49. package/build/src/drivers/openfga_facts.js +277 -3
  50. package/build/src/drivers/openfga_relations_driver.d.ts +41 -13
  51. package/build/src/drivers/openfga_relations_driver.js +422 -228
  52. package/build/src/errors.d.ts +121 -1
  53. package/build/src/errors.js +153 -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 +156 -28
  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 +2 -3
  81. package/build/src/openfga.js +4 -2
  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 +0 -1
  87. package/build/src/relations/check_many.js +0 -1
  88. package/build/src/relations/define_relations_config.d.ts +25 -3
  89. package/build/src/relations/define_relations_config.js +8 -3
  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 +14 -7
  95. package/build/src/relations/manager.js +60 -15
  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 +58 -4
  99. package/build/src/relations/reconcile.js +266 -12
  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 +5 -4
  103. package/build/src/relations/tuple_order.js +5 -4
  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 +52 -1
  111. package/build/src/shared/backend_guard.js +164 -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 +0 -1
  121. package/build/src/testing/main.js +0 -1
  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 +7 -1
  125. package/build/src/testing/relations_contract.js +174 -7
  126. package/build/src/testing/relations_reconcile_contract.d.ts +0 -1
  127. package/build/src/testing/relations_reconcile_contract.js +0 -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 +89 -14
  139. package/build/src/types.js +4 -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/tuple_order.d.ts.map +0 -1
  239. package/build/src/relations/tuple_order.js.map +0 -1
  240. package/build/src/relations_config_store.d.ts.map +0 -1
  241. package/build/src/relations_config_store.js.map +0 -1
  242. package/build/src/relations_reconcile.d.ts.map +0 -1
  243. package/build/src/relations_reconcile.js.map +0 -1
  244. package/build/src/scope_outbox.d.ts.map +0 -1
  245. package/build/src/scope_outbox.js.map +0 -1
  246. package/build/src/shared/backend_guard.d.ts.map +0 -1
  247. package/build/src/shared/backend_guard.js.map +0 -1
  248. package/build/src/shared/sql_expiry.d.ts.map +0 -1
  249. package/build/src/shared/sql_expiry.js.map +0 -1
  250. package/build/src/shared/transaction_guard.d.ts.map +0 -1
  251. package/build/src/shared/transaction_guard.js.map +0 -1
  252. package/build/src/sql_descendants.d.ts.map +0 -1
  253. package/build/src/sql_descendants.js.map +0 -1
  254. package/build/src/testing/contract.d.ts.map +0 -1
  255. package/build/src/testing/contract.js.map +0 -1
  256. package/build/src/testing/main.d.ts.map +0 -1
  257. package/build/src/testing/main.js.map +0 -1
  258. package/build/src/testing/migration_contract.d.ts.map +0 -1
  259. package/build/src/testing/migration_contract.js.map +0 -1
  260. package/build/src/testing/relations_contract.d.ts.map +0 -1
  261. package/build/src/testing/relations_contract.js.map +0 -1
  262. package/build/src/testing/relations_reconcile_contract.d.ts.map +0 -1
  263. package/build/src/testing/relations_reconcile_contract.js.map +0 -1
  264. package/build/src/testing/scope_outbox_contract.d.ts.map +0 -1
  265. package/build/src/testing/scope_outbox_contract.js.map +0 -1
  266. package/build/src/testing/scope_tree.d.ts.map +0 -1
  267. package/build/src/testing/scope_tree.js.map +0 -1
  268. package/build/src/testing/test_clock.d.ts.map +0 -1
  269. package/build/src/testing/test_clock.js.map +0 -1
  270. package/build/src/traits/authz_scopes.d.ts.map +0 -1
  271. package/build/src/traits/authz_scopes.js.map +0 -1
  272. package/build/src/traits/has_uuid.d.ts.map +0 -1
  273. package/build/src/traits/has_uuid.js.map +0 -1
  274. package/build/src/types.d.ts.map +0 -1
  275. package/build/src/types.js.map +0 -1
  276. package/build/stubs/main.d.ts.map +0 -1
  277. package/build/stubs/main.js.map +0 -1
@@ -25,4 +25,3 @@ export default class AuthzCatalogDiff extends BaseCommand {
25
25
  failOnShadows: boolean;
26
26
  run(): Promise<void>;
27
27
  }
28
- //# sourceMappingURL=authz_catalog_diff.d.ts.map
@@ -63,4 +63,3 @@ __decorate([
63
63
  }),
64
64
  __metadata("design:type", Boolean)
65
65
  ], AuthzCatalogDiff.prototype, "failOnShadows", void 0);
66
- //# sourceMappingURL=authz_catalog_diff.js.map
@@ -75,4 +75,3 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
75
75
  run(): Promise<void>;
76
76
  }
77
77
  export {};
78
- //# sourceMappingURL=authz_catalog_prune_orphans.d.ts.map
@@ -132,4 +132,3 @@ __decorate([
132
132
  }),
133
133
  __metadata("design:type", Object)
134
134
  ], AuthzCatalogPruneOrphans.prototype, "allowMassPurge", void 0);
135
- //# sourceMappingURL=authz_catalog_prune_orphans.js.map
@@ -34,4 +34,3 @@ export default class AuthzCatalogSync extends BaseCommand {
34
34
  keepLinks: boolean | undefined;
35
35
  run(): Promise<void>;
36
36
  }
37
- //# sourceMappingURL=authz_catalog_sync.d.ts.map
@@ -78,4 +78,3 @@ __decorate([
78
78
  flags.boolean({ description: 'Do not prune role→permission links missing from the spec' }),
79
79
  __metadata("design:type", Object)
80
80
  ], AuthzCatalogSync.prototype, "keepLinks", void 0);
81
- //# sourceMappingURL=authz_catalog_sync.js.map
@@ -41,4 +41,3 @@ export default class AuthzFreeze extends BaseCommand {
41
41
  leaseMs: number | undefined;
42
42
  run(): Promise<void>;
43
43
  }
44
- //# sourceMappingURL=authz_freeze.d.ts.map
@@ -91,4 +91,3 @@ __decorate([
91
91
  }),
92
92
  __metadata("design:type", Object)
93
93
  ], AuthzFreeze.prototype, "leaseMs", void 0);
94
- //# sourceMappingURL=authz_freeze.js.map
@@ -108,4 +108,3 @@ export default class AuthzReconcile extends BaseCommand {
108
108
  maxTuples: number | undefined;
109
109
  run(): Promise<void>;
110
110
  }
111
- //# sourceMappingURL=authz_reconcile.d.ts.map
@@ -453,4 +453,3 @@ __decorate([
453
453
  }),
454
454
  __metadata("design:type", Object)
455
455
  ], AuthzReconcile.prototype, "maxTuples", void 0);
456
- //# sourceMappingURL=authz_reconcile.js.map
@@ -92,4 +92,3 @@ export default class AuthzRelationsReconcile extends BaseCommand {
92
92
  partitionUuid: string | undefined;
93
93
  run(): Promise<void>;
94
94
  }
95
- //# sourceMappingURL=authz_relations_reconcile.d.ts.map
@@ -9,6 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  import { BaseCommand, flags } from '@adonisjs/core/ace';
11
11
  import { RELATION_USERSET_RESIDUE_DELETE } from '../src/relations/define_relations_config.js';
12
+ import { denyReachLine } from '../src/relations/deny_reach.js';
12
13
  import { RelationConfigError } from '../src/errors.js';
13
14
  // Plan 2.5 · lote B · B7: la vía pública vive en su subpath (`src/relations_reconcile.ts`,
14
15
  // `@jantstack/adonis-authz/relations/reconcile`); este módulo es el COMANDO y la importa de allí.
@@ -84,6 +85,34 @@ export function relationsReconcileLines(report) {
84
85
  `el deny concedería lo que el origen niega. Receta: ${refusal.recipe}`,
85
86
  });
86
87
  }
88
+ // Lote RC · sublote (8): el DETECTOR del alcance de los denies (solo si la pasada lo pidió: `--dry-run`). Una línea por
89
+ // hallazgo con su prefijo ESTABLE (punto 7) y su receta: «deny con alcance ENCOGIDO» / «deny HUÉRFANO» (persistida →
90
+ // servida) y «línea base RANCIA» (servida → persistida, firma (8 · rancia)) son deriva (exit ≠ 0). Sin línea base y con
91
+ // denies vivos sin juzgar, la línea «alcance de denies: SIN línea base y N deny(s) vivos sin juzgar» y exit 0 (firma
92
+ // (8 · `--dry-run`), g2: el runbook no persiste la config y no cambia). Sin nada de eso, UNA línea que dice qué se comparó.
93
+ if (report.denyReach !== undefined) {
94
+ const { baseline, findings, stale, unjudged } = report.denyReach;
95
+ for (const finding of [...findings, ...stale]) {
96
+ lines.push({ level: 'error', message: `${denyReachLine(finding)} (lado: ${finding.sides.join(', ')})` });
97
+ }
98
+ if (baseline === 'none' && unjudged > 0) {
99
+ lines.push({
100
+ level: 'warning',
101
+ message: `alcance de denies: SIN línea base y ${unjudged} deny(s) vivos sin juzgar (no hay config persistida en authz_relations_config): ` +
102
+ `solo se buscaron huérfanos, y un estrechamiento de su alcance NO se ve sin línea base. Si usas deny, persistir la config es ` +
103
+ `paso obligatorio de release: adóptala una vez con saveRelationsConfig(spec, { establishBaseline: true }) y vigila con ` +
104
+ `checkRelationsConfigDenyReach (el detector de un solo driver).`,
105
+ });
106
+ }
107
+ else if (findings.length === 0 && stale.length === 0) {
108
+ lines.push({
109
+ level: 'log',
110
+ message: baseline === 'persisted'
111
+ ? 'alcance de denies: ningún deny vivo de la partición encogió ni quedó huérfano entre la config persistida (línea base) y la que sirve este proceso, y la línea base no es más estrecha que lo servido.'
112
+ : 'alcance de denies: SIN línea base (no hay config persistida en authz_relations_config) y ningún deny vivo en la partición que juzgar.',
113
+ });
114
+ }
115
+ }
87
116
  // R-15: la caducada del origen es la pérdida DECLARADA de la migración (como
88
117
  // `expired` en roles, 3b-8 · B2): se dice, no es deriva.
89
118
  if (report.skipped.expired > 0) {
@@ -137,7 +166,9 @@ export function relationsReconcileLines(report) {
137
166
  level: 'error',
138
167
  message: detail.reason === 'userset-unsupported'
139
168
  ? `sin migrar ${what}. Es un residuo pre-2.5 (B3) del store de ORIGEN: el destino no lo admite por ninguna puerta y ningún --prune lo toca. Bórrala a mano (los tres motores): ${RELATION_USERSET_RESIDUE_DELETE} y repite la pasada.`
140
- : `sin migrar ${what}. Es un dato del store de ORIGEN, no una pregunta: corrígela o bórrala a mano (SQL/SDK) y repite la pasada.`,
169
+ : detail.reason === 'invalid-condition'
170
+ ? `sin migrar ${what}. Es una tupla cuya condición el paquete no escribe ni sabe leer (L11): no se migra como permanente ni como caducada. Revísala y corrígela o bórrala a mano (SDK/CLI de OpenFGA) y repite la pasada.`
171
+ : `sin migrar ${what}. Es un dato del store de ORIGEN, no una pregunta: corrígela o bórrala a mano (SQL/SDK) y repite la pasada.`,
141
172
  });
142
173
  continue;
143
174
  }
@@ -164,11 +195,13 @@ export function relationsReconcileLines(report) {
164
195
  level: 'error',
165
196
  message: detail.reason === 'userset-unsupported'
166
197
  ? `sobra ${what}. Es un residuo pre-2.5 (B3) del DESTINO: --prune ${verdict} lo borra${detail.pruned === false ? '' : 'ría'} (nunca un DELETE de plataforma sobre una fila que la API no admite). Bórrala a mano (los tres motores): ${RELATION_USERSET_RESIDUE_DELETE} y repite la pasada.`
167
- : detail.reason === 'deny-conditional'
168
- ? `sobra ${what}. Es un deny CONDICIONAL del DESTINO (con caducidad, escrito fuera del paquete) que el driver destino no puede rehacer permanente (openfga no reemplaza la misma clave en un Write, M5): niega solo mientras viva (si ya venció, no niega nada) y su partición queda NEGADA (línea «partición NEGADA»: cero escrituras en ella).`
169
- : detail.reason === 'undeclared'
170
- ? `sobra ${what}. Es una fila del DESTINO cuya relación (o tipo) el destino YA NO declara (F-05): --prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'} (unrelate la rechaza por F-05 y la pasada no insiste: nunca un DELETE de plataforma). Sigue ahí: retírala a mano (SQL/SDK) o vuelve a declarar la relación en el destino (y re-guarda su config persistida) y repite la pasada.`
171
- : `sobra ${what}. Es un dato del DESTINO fuera de la gramática: --prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'} (ningún driver del paquete la admite en unrelate, R-16, y la pasada no se la pide a nadie: nunca un DELETE de plataforma). Sigue ahí: corrígela o bórrala a mano (SQL/SDK) y repite la pasada.`,
198
+ : detail.reason === 'invalid-condition'
199
+ ? `sobra ${what}. Es una tupla del DESTINO con una condición que el paquete no escribe ni sabe leer (L11): presente e intocable —--prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'}, y su clave no se reescribe desde el origen—. Revísala y bórrala a mano (SDK/CLI de OpenFGA) y repite la pasada.`
200
+ : detail.reason === 'deny-conditional'
201
+ ? `sobra ${what}. Es un deny CONDICIONAL del DESTINO (con caducidad, escrito fuera del paquete) que el driver destino no puede rehacer permanente (openfga no reemplaza la misma clave en un Write, M5): niega solo mientras viva (si ya venció, no niega nada) y su partición queda NEGADA (línea «partición NEGADA»: cero escrituras en ella).`
202
+ : detail.reason === 'undeclared'
203
+ ? `sobra ${what}. Es una fila del DESTINO cuya relación (o tipo) el destino YA NO declara (F-05): --prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'} (unrelate la rechaza por F-05 y la pasada no insiste: nunca un DELETE de plataforma). Sigue ahí: retírala a mano (SQL/SDK) o vuelve a declarar la relación en el destino (y re-guarda su config persistida) y repite la pasada.`
204
+ : `sobra ${what}. Es un dato del DESTINO fuera de la gramática: --prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'} (ningún driver del paquete la admite en unrelate, R-16, y la pasada no se la pide a nadie: nunca un DELETE de plataforma). Sigue ahí: corrígela o bórrala a mano (SQL/SDK) y repite la pasada.`,
172
205
  });
173
206
  }
174
207
  // Cierre 5 de D2 (decisión del dueño §2026-10-16 (10), opción A): la PARTICIÓN NEGADA — ≥ 1 `renewal-unsafe` o
@@ -182,7 +215,7 @@ export function relationsReconcileLines(report) {
182
215
  const later = evolves.length > 0 ? ` OJO, ese estado EVOLUCIONA: ${evolves.slice(0, 5).join('; ')}${evolves.length > 5 ? '; …' : ''}.` : '';
183
216
  lines.push({
184
217
  level: 'error',
185
- message: `partición NEGADA ${refusal.partition}: ${causes.length} causa(s) que el destino no puede escribir en una escritura — ` +
218
+ message: `partición NEGADA ${refusal.partition}: ${causes.length} causa(s) por las que la pasada no puede escribir en ella sin conceder de más (una ventana entre dos Write, una tupla que el paquete no sabe leer, o una cadena de grupos anidados más larga que el techo del destino) — ` +
186
219
  `${causes.slice(0, 5).join('; ')}${causes.length > 5 ? '; …' : ''}. Cero escrituras y cero borrados en esta partición ` +
187
220
  `(también en la pasada real${report.prune === true ? ' y con --prune' : ''}): su destino queda COMO ESTABA, con la deriva ` +
188
221
  `previa a la vista (por escribir ${refusal.pending.write}, por renovar ${refusal.pending.update}, por borrar ` +
@@ -287,6 +320,8 @@ export function relationsReconcileLines(report) {
287
320
  pendingRenewals === 0 &&
288
321
  (report.partitionsRefused?.length ?? 0) === 0 &&
289
322
  (report.denyRefused?.length ?? 0) === 0 &&
323
+ (report.denyReach?.findings.length ?? 0) === 0 &&
324
+ (report.denyReach?.stale.length ?? 0) === 0 &&
290
325
  !(report.massDelete && !massDeleteLifted) &&
291
326
  !(report.frozen?.lapsed === true) &&
292
327
  (!report.dryRun || cambios === 0);
@@ -461,6 +496,19 @@ export default class AuthzRelationsReconcile extends BaseCommand {
461
496
  else {
462
497
  this.logger.info(`Config del destino: leída de authz_relations_config (${toConfig.objectTypes.length} tipo(s)): la deriva del modelo se vigila.`);
463
498
  }
499
+ // Lote RC · sublote (8): el DETECTOR del alcance de los denies, solo en el verificador (`--dry-run`): línea base = la
500
+ // config persistida (`toConfig`), config nueva = la que SIRVE este proceso (`relations.config`). Sin `relations.config`
501
+ // en el proceso no hay con qué comparar: se dice (punto 7) y la pasada sigue.
502
+ let denyReach;
503
+ if (this.dryRun === true) {
504
+ const current = this.app.config?.get('authorization')?.relations?.config;
505
+ if (current && Array.isArray(current.objectTypes)) {
506
+ denyReach = { baseline: toConfig, current };
507
+ }
508
+ else {
509
+ this.logger.info('alcance de denies: NO se vigila en esta pasada (este proceso no tiene relations.config en config/authorization.ts con el que comparar).');
510
+ }
511
+ }
464
512
  // L-1 · J1: la pasada que ESCRIBE corre bajo la ventana durable del
465
513
  // manager de roles (la misma fila `id = 2`, toda la flota); `--dry-run` no.
466
514
  // Los drivers los construye `runRelationsReconcile`, DESPUÉS del guard.
@@ -475,6 +523,7 @@ export default class AuthzRelationsReconcile extends BaseCommand {
475
523
  toKey: this.to,
476
524
  fromKey,
477
525
  ...(toConfig ? { toConfig } : {}),
526
+ ...(denyReach ? { denyReach } : {}),
478
527
  });
479
528
  }
480
529
  catch (error) {
@@ -583,4 +632,3 @@ __decorate([
583
632
  flags.string({ description: 'Partition (tenant) scope uuid; required with --partition-type' }),
584
633
  __metadata("design:type", Object)
585
634
  ], AuthzRelationsReconcile.prototype, "partitionUuid", void 0);
586
- //# sourceMappingURL=authz_relations_reconcile.js.map
@@ -94,4 +94,3 @@ export default class AuthzScopesRelay extends BaseCommand {
94
94
  static parseOnly(values: string[] | undefined): Array<string | number> | undefined;
95
95
  run(): Promise<void>;
96
96
  }
97
- //# sourceMappingURL=authz_scopes_relay.d.ts.map
@@ -252,8 +252,23 @@ export default class AuthzScopesRelay extends BaseCommand {
252
252
  // Qué fue de cada entrada, una a una (`entries`): la pasada no es atómica
253
253
  // y un contador no permite retomar nada. Lo aparcado ya se dijo arriba.
254
254
  for (const entry of report.entries) {
255
- if (entry.status === 'applied')
256
- this.logger.log(`aplicado: ${linea(entry)}`);
255
+ if (entry.status === 'applied') {
256
+ // Asentar · cierre 1 (auditor ⚪ 8): la auditoría dice el padre APLICADO, no solo el encolado.
257
+ if (entry.reason === 'superseded') {
258
+ this.logger.warning(`aplicado SIN arista (superseded: el nodo ya no existe y su detached está en la cola): ${linea(entry)}`);
259
+ }
260
+ else if (entry.reason === 'consumer-parent' && entry.appliedParent) {
261
+ // Asentar · cierre 2 (C1-1 (b), firma del dueño 2026-10-21; O-1 del re-ataque): exit ≠ 0. La carrera S1 y una
262
+ // caché rancia con dos movimientos del nodo dentro de su TTL son INDISTINGUIBLES para el relay, y un cron no lee
263
+ // avisos: la carrera hace saltar la alarma una vez; la caché, cada vez que pase.
264
+ this.logger.warning(`aplicado con el padre del CONSUMIDOR ${entry.appliedParent.type}:${entry.appliedParent.uuid ?? ''} ` +
265
+ `(no el encolado; otra entrada del nodo lo respalda): ${linea(entry)} — sale ≠ 0: comprueba que el resolutor ` +
266
+ 'del relay lee lo CONFIRMADO, sin caché ni réplica atrasada (authz:reconcile --dry-run lo contrasta)');
267
+ this.exitCode = 1;
268
+ }
269
+ else
270
+ this.logger.log(`aplicado: ${linea(entry)}`);
271
+ }
257
272
  if (entry.status === 'deferred') {
258
273
  const motivo = entry.reason?.startsWith('parked:')
259
274
  ? `aplazado por APARCADO (${entry.reason}): no se aplica hasta que alguien mire esa entrada`
@@ -267,8 +282,38 @@ export default class AuthzScopesRelay extends BaseCommand {
267
282
  notEvaluatedLines();
268
283
  if (report.failures.length > 0) {
269
284
  for (const item of report.failures) {
270
- this.logger.error(`falló #${item.id} (${item.change.op} ` +
271
- `${item.change.child.type}:${item.change.child.uuid ?? ''}): ${item.error}` +
285
+ const target = `#${item.id} (${item.change.op} ${item.change.child.type}:${item.change.child.uuid ?? ''})`;
286
+ // Cierre-2 de L11 (⚪ C2; punto 7): el fallo de DESPLIEGUE tiene su prefijo ESTABLE —el backend SÍ respondió, lo
287
+ // que no cuadra es el despliegue con la config—; «el backend no respondió» queda para el 503 de verdad. Desde R3
288
+ // la marca la ponen también el store/modelo inexistente, las credenciales (401/403) y el 4xx desconocido.
289
+ if (item.deployment === true) {
290
+ this.logger.error(`fallo de DESPLIEGUE ${target}: ${item.error} — el despliegue no es el que espera la config (modelo fijado, ` +
291
+ 'store, credenciales o un 4xx del servidor): este fallo NO consume intento (corrige el despliegue —' +
292
+ 'openfga:provision publica el modelo de la config— y repite)');
293
+ continue;
294
+ }
295
+ // Cierre de R3 (⚪ R3-A7; punto 7): la carrera y el límite de resolución son transitorios, pero el backend SÍ
296
+ // respondió — cada uno con su prefijo ESTABLE.
297
+ if (item.transient && item.code === 'E_AUTHZ_WRITE_CONFLICT') {
298
+ this.logger.error(`CARRERA en ${target}: ${item.error} — otro escritor (p. ej. un grant concurrente) tocó las mismas tuplas: ` +
299
+ 'este fallo NO consume intento (la pasada siguiente relee y re-aplica)');
300
+ continue;
301
+ }
302
+ if (item.transient && item.code === 'E_AUTHZ_RESOLUTION_LIMIT') {
303
+ this.logger.error(`límite de RESOLUCIÓN en ${target}: ${item.error} — el backend respondió que el grafo excede lo que ` +
304
+ 'resuelve (ver «Limits» en el README): este fallo NO consume intento');
305
+ continue;
306
+ }
307
+ // Lote «antes de RC» · F4 (⚪ RC-2 del auditor de R3; punto 7): un deadline vencido —el timeout del cliente, o un
308
+ // 504 / `deadline_exceeded` de una ESCRITURA, que es indeterminada desde el cierre de R3— no es «el backend no
309
+ // respondió»: con un 504 SÍ respondió (o un proxy), y la escritura pudo aplicarse. Prefijo ESTABLE propio.
310
+ if (item.transient && item.code === 'E_AUTHZ_BACKEND_TIMEOUT') {
311
+ this.logger.error(`PLAZO VENCIDO en ${target}: ${item.error} — venció el deadline (timeout del cliente, o un 504/deadline_exceeded ` +
312
+ 'del servidor o de un proxy): si era una escritura pudo aplicarse o no (INDETERMINADA); la pasada siguiente ' +
313
+ 'relee el store y re-aplica: este fallo NO consume intento');
314
+ continue;
315
+ }
316
+ this.logger.error(`falló ${target}: ${item.error}` +
272
317
  (item.transient ? ' — el backend no respondió: este fallo NO consume intento' : ''));
273
318
  }
274
319
  this.logger.warning('Lo que DEPENDE de un cambio fallido no se adelanta (el orden del árbol importa: aparece como «aplazado»), ' +
@@ -341,4 +386,3 @@ __decorate([
341
386
  }),
342
387
  __metadata("design:type", Object)
343
388
  ], AuthzScopesRelay.prototype, "maxAge", void 0);
344
- //# sourceMappingURL=authz_scopes_relay.js.map
@@ -77,4 +77,3 @@ export default class AuthzScopesRequeue extends BaseCommand {
77
77
  static parseIds(values: string[] | undefined): Array<string | number> | undefined;
78
78
  run(): Promise<void>;
79
79
  }
80
- //# sourceMappingURL=authz_scopes_requeue.d.ts.map
@@ -90,7 +90,20 @@ export function requeueLines(input) {
90
90
  const relay = report.relay;
91
91
  let clean = true;
92
92
  for (const entry of relay.entries) {
93
- if (entry.status === 'applied')
93
+ // Asentar · cierre 2 (gemelo del relay, C1-1): el padre APLICADO cuando no es el encolado, con su línea propia. No
94
+ // cambia `clean`: aquí hay un operador delante, y es justo la salida de una aparcada por DRIFT (re-notificar el
95
+ // padre actual y reencolar) la que termina en esta línea.
96
+ if (entry.status === 'applied' && entry.reason === 'consumer-parent' && entry.appliedParent) {
97
+ lines.push({
98
+ level: 'warning',
99
+ message: `aplicada con el padre del CONSUMIDOR ${entry.appliedParent.type}:${entry.appliedParent.uuid ?? ''} ` +
100
+ `(no el encolado; otra entrada del nodo lo respalda): ${linea(entry)}`,
101
+ });
102
+ }
103
+ else if (entry.status === 'applied' && entry.reason === 'superseded') {
104
+ lines.push({ level: 'warning', message: `aplicada SIN arista (superseded: el nodo ya no existe y su detached está en la cola): ${linea(entry)}` });
105
+ }
106
+ else if (entry.status === 'applied')
94
107
  lines.push({ level: 'log', message: `aplicada: ${linea(entry)}` });
95
108
  else if (entry.status === 'deferred') {
96
109
  clean = false;
@@ -227,4 +240,3 @@ __decorate([
227
240
  }),
228
241
  __metadata("design:type", Object)
229
242
  ], AuthzScopesRequeue.prototype, "waitLeaseMs", void 0);
230
- //# sourceMappingURL=authz_scopes_requeue.js.map
@@ -42,4 +42,3 @@ export default class AuthzUnfreeze extends BaseCommand {
42
42
  fence: number | undefined;
43
43
  run(): Promise<void>;
44
44
  }
45
- //# sourceMappingURL=authz_unfreeze.d.ts.map
@@ -154,4 +154,3 @@ __decorate([
154
154
  }),
155
155
  __metadata("design:type", Object)
156
156
  ], AuthzUnfreeze.prototype, "fence", void 0);
157
- //# sourceMappingURL=authz_unfreeze.js.map
@@ -14,4 +14,3 @@ export { OpenFgaProvision, AuthzCatalogDiff, AuthzCatalogSync, AuthzCatalogPrune
14
14
  export declare function getMetaData(): Promise<CommandMetaData[]>;
15
15
  /** Contrato de loader de ace (`LoadersContract.getCommand`): la clase, o `null` si el nombre no es nuestro. */
16
16
  export declare function getCommand(metaData: CommandMetaData): Promise<any>;
17
- //# sourceMappingURL=main.d.ts.map
@@ -59,4 +59,3 @@ export function getMetaData() {
59
59
  export function getCommand(metaData) {
60
60
  return loader.getCommand(metaData);
61
61
  }
62
- //# sourceMappingURL=main.js.map
@@ -83,6 +83,16 @@ export default class OpenFgaProvision extends BaseCommand {
83
83
  */
84
84
  prepare(): Promise<void>;
85
85
  run(): Promise<void>;
86
+ /**
87
+ * **La RELECTURA de lo publicado** (lote «antes de RC» · L11, diseño 1.3 D del analista; condición de aceptación C12 del
88
+ * tester): tras `writeAuthorizationModel` se LEE el modelo por su id y se compara ENTERO (`slice: 'all'`) con el que
89
+ * genera la config. Ancla el canonicalizador de L11 contra el servidor REAL en cada publicación: un servidor que
90
+ * serialice distinto (otra versión, un campo nuevo) se descubre en el despliegue, no con un 500 en la primera petición
91
+ * de producción. Si la relectura falla o difiere: exit 1, prefijo ESTABLE «provision RELECTURA FALLIDA:» (punto 7) y la
92
+ * letra que no miente (H11 c): el modelo YA está publicado en el store, y NO hay que fijarlo — por eso la línea
93
+ * `OPENFGA_MODEL_ID=` NO se imprime. Devuelve si se puede fijar.
94
+ */
95
+ private reread;
86
96
  /**
87
97
  * **La guarda contra la MUTILACIÓN del modelo** (plan 2.5 · lote D · D2; analista §4.6, juez ronda 2 · R2-4 c y H3;
88
98
  * cierre 1 de D2 · 🟡 3 y 🟠 2). `openfga:provision --store-id` publica desde la config del PROCESO: si ese proceso ya
@@ -174,4 +184,3 @@ export declare function countLiveDenies(client: any, dropped: string[]): Promise
174
184
  count: number;
175
185
  read: number;
176
186
  }>;
177
- //# sourceMappingURL=openfga_provision.d.ts.map
@@ -105,7 +105,7 @@ export default class OpenFgaProvision extends BaseCommand {
105
105
  }
106
106
  const { OpenFgaClient } = await import('@openfga/sdk');
107
107
  // El subpath `/openfga` es la única puerta al SDK (D9).
108
- const { openFgaFactsModel, provisionOpenFgaStore, assertFactsModelPublishable } = await import('../src/openfga.js');
108
+ const { openFgaFactsModel, provisionOpenFgaStore, assertFactsModelPublishable, factsModelDifferences } = await import('../src/openfga.js');
109
109
  // Los holders del consumidor viven en SU config, no en el paquete.
110
110
  const holderTypes = config?.holderTypes ?? {};
111
111
  // Los PERMISOS del modelo (c2r) salen de los catálogos del config. Se
@@ -130,7 +130,8 @@ export default class OpenFgaProvision extends BaseCommand {
130
130
  // esto salía como error crudo del SDK con una request ya hecha (⚪ N1
131
131
  // del auditor de alpha.4). Cero requests si no es publicable.
132
132
  assertFactsModelPublishable(holderTypes, permissions, undefined, relations);
133
- const client = new OpenFgaClient({ apiUrl, storeId: this.storeId });
133
+ // Injerto 15 del juez (H12): sin reintentos a escondidas (desde el SDK 0.9.6 un cliente sin `retryParams` reintenta).
134
+ const client = new OpenFgaClient({ apiUrl, storeId: this.storeId, retryParams: { maxRetry: 0 } });
134
135
  const next = openFgaFactsModel(holderTypes, permissions, relations);
135
136
  // Lote D · D2: la guarda contra la MUTILACIÓN del modelo (R2-4 c / H3) — ANTES de escribir nada. Cierre 1 de D2:
136
137
  // juzga el ESTADO del store (🟡 3) y devuelve las relaciones que hay que RE-CONTAR justo antes de publicar (🟠 2).
@@ -147,6 +148,8 @@ export default class OpenFgaProvision extends BaseCommand {
147
148
  if (relations)
148
149
  this.logger.info(`Tipos de relación fusionados: ${relations.objectTypes.map((t) => t.type).join(', ')}`);
149
150
  this.logger.log(`OPENFGA_STORE_ID=${this.storeId}`);
151
+ if (!(await this.reread(OpenFgaClient, factsModelDifferences, apiUrl, this.storeId, model.authorization_model_id, holderTypes, relations)))
152
+ return;
150
153
  this.logger.log(`OPENFGA_MODEL_ID=${model.authorization_model_id}`);
151
154
  return;
152
155
  }
@@ -158,8 +161,46 @@ export default class OpenFgaProvision extends BaseCommand {
158
161
  this.logger.log('Añade al .env (junto a AUTHZ_DRIVER=openfga):');
159
162
  this.logger.log(`OPENFGA_URL=${apiUrl}`);
160
163
  this.logger.log(`OPENFGA_STORE_ID=${storeId}`);
164
+ if (!(await this.reread(OpenFgaClient, factsModelDifferences, apiUrl, storeId, modelId, holderTypes, relations)))
165
+ return;
161
166
  this.logger.log(`OPENFGA_MODEL_ID=${modelId}`);
162
167
  }
168
+ /**
169
+ * **La RELECTURA de lo publicado** (lote «antes de RC» · L11, diseño 1.3 D del analista; condición de aceptación C12 del
170
+ * tester): tras `writeAuthorizationModel` se LEE el modelo por su id y se compara ENTERO (`slice: 'all'`) con el que
171
+ * genera la config. Ancla el canonicalizador de L11 contra el servidor REAL en cada publicación: un servidor que
172
+ * serialice distinto (otra versión, un campo nuevo) se descubre en el despliegue, no con un 500 en la primera petición
173
+ * de producción. Si la relectura falla o difiere: exit 1, prefijo ESTABLE «provision RELECTURA FALLIDA:» (punto 7) y la
174
+ * letra que no miente (H11 c): el modelo YA está publicado en el store, y NO hay que fijarlo — por eso la línea
175
+ * `OPENFGA_MODEL_ID=` NO se imprime. Devuelve si se puede fijar.
176
+ */
177
+ async reread(OpenFgaClient, factsModelDifferences, apiUrl, storeId, modelId, holderTypes, relations) {
178
+ let differences;
179
+ try {
180
+ const client = new OpenFgaClient({ apiUrl, storeId, authorizationModelId: modelId, retryParams: { maxRetry: 0 } });
181
+ const response = await client.readAuthorizationModel();
182
+ differences = factsModelDifferences(response?.authorization_model, { holderTypes, relations, slice: 'all' }).differences.map((d) => d.path);
183
+ }
184
+ catch (error) {
185
+ this.logger.error(`provision RELECTURA FALLIDA: el modelo ${modelId} SE PUBLICÓ en el store ${storeId}, pero no se pudo releer y ` +
186
+ `comparar con el que genera la config (${String(error?.message ?? error)}). NO lo fijes (OPENFGA_MODEL_ID): los ` +
187
+ 'drivers lo comparan al arrancar y, sin esta comprobación, podrían negarse a servir. Repite el comando cuando el ' +
188
+ 'servidor responda.');
189
+ this.exitCode = 1;
190
+ return false;
191
+ }
192
+ if (differences.length === 0) {
193
+ // Punto 7 de CLAUDE.md (cierre de L11, 🟡-1 del tester): la rama de éxito también dice que corrió, con prefijo ESTABLE.
194
+ this.logger.success(`provision RELECTURA OK: el modelo ${modelId} releído del store ${storeId} es IGUAL (entero) al que genera la config; ya se puede fijar.`);
195
+ return true;
196
+ }
197
+ this.logger.error(`provision RELECTURA FALLIDA: el modelo ${modelId} SE PUBLICÓ en el store ${storeId}, pero lo que el servidor devuelve ` +
198
+ `NO es igual al que genera la config (L11): ${differences.slice(0, 20).join('; ')}${differences.length > 20 ? ' …' : ''}. ` +
199
+ 'NO lo fijes (OPENFGA_MODEL_ID): los drivers openfga lo compararían igual al arrancar y responderían 500 E_AUTHZ_CONFIG. ' +
200
+ 'Suele ser un servidor de otra versión que serializa el modelo distinto: compruébalo (CI prueba OpenFGA v1.19.0) y repórtalo.');
201
+ this.exitCode = 1;
202
+ return false;
203
+ }
163
204
  /**
164
205
  * **La guarda contra la MUTILACIÓN del modelo** (plan 2.5 · lote D · D2; analista §4.6, juez ronda 2 · R2-4 c y H3;
165
206
  * cierre 1 de D2 · 🟡 3 y 🟠 2). `openfga:provision --store-id` publica desde la config del PROCESO: si ese proceso ya
@@ -411,4 +452,3 @@ export async function countLiveDenies(client, dropped) {
411
452
  } while (continuationToken);
412
453
  return { live, count, read };
413
454
  }
414
- //# sourceMappingURL=openfga_provision.js.map
@@ -18,4 +18,3 @@ import type Configure from '@adonisjs/core/commands/configure';
18
18
  * nada distinto.
19
19
  */
20
20
  export declare function configure(command: Configure): Promise<void>;
21
- //# sourceMappingURL=configure.d.ts.map
@@ -76,4 +76,3 @@ export async function configure(command) {
76
76
  }
77
77
  command.logger.info('Siguiente paso: node ace migration:run');
78
78
  }
79
- //# sourceMappingURL=configure.js.map
package/build/index.d.ts CHANGED
@@ -50,7 +50,7 @@ export type { SqlScopeOutboxOptions } from './src/scope_outbox.js';
50
50
  * obligaría a importar ese SDK — acoplando el call-site al backend que este
51
51
  * paquete existe para abstraer.
52
52
  */
53
- export { AuthorizationBackendError, AuthorizationBackendTimeoutError, ScopeResolverError, InvalidIdentityError, InvalidSlugError, AuthorizationConfigError, AuthorizationInternalError, UnknownScopeError, NoScopeResolverError, UnknownRoleError, UnknownPermissionError, CatalogConflictError, RoleIsNotAccessError, ScopeCycleError, PurgeIncompleteError, WriteConflictError, RenewalUnsafeError, RenewalRacedError, ActorRequiredError, CheckManyBatchError, NotWithinError, WithinRequiredError, WithinRootForbiddenError, TooManyScopesError, TooManyLocalRolesError, MassPurgeRefusedError, PruneInterruptedError, UnsupportedDialectError, ScopeTooDeepError, ScopeDepthLimitError, ScopeRequeueRefusedError, UnsupportedOperationError, NoDescendantsResolverError, ViewExpiredError, RoleNotVisibleError, AmbiguousRoleError, RoleImmutableError, RoleLevelAboveOwnerError, RoleNotAssignableAtError, PermissionNotDelegableError, RankExceededError, ModelTooLargeError, RelationConfigError, RelationConfigUnknownKeyError, RelationTypeUnknownError, RelationUnknownError, RelationUsersetUnsupportedError, RelationNotDenyableError, RelationDenyExpiryError, MembersOfDenyLimitError, ScopeTreeDriftError, ScopeDriftUnguardedError, AuthorizationFrozenError, FreezeHeldError, MassReconcileRefusedError, DenyMigrationRefusedError, StaleSourceRefusedError, OperatorWindowExpiredError, ReconcileTooLargeError, markPartialWrite, isPartialWrite, } from './src/errors.js';
53
+ export { AuthorizationBackendError, AuthorizationBackendTimeoutError, ResolutionLimitError, ScopeResolverError, InvalidIdentityError, InvalidSlugError, AuthorizationConfigError, AuthorizationInternalError, UnknownScopeError, NoScopeResolverError, UnknownRoleError, UnknownPermissionError, CatalogConflictError, RoleIsNotAccessError, ScopeCycleError, PurgeIncompleteError, WriteConflictError, RenewalUnsafeError, RenewalRacedError, ActorRequiredError, CheckManyBatchError, NotWithinError, WithinRequiredError, WithinRootForbiddenError, TooManyScopesError, TooManyLocalRolesError, MassPurgeRefusedError, PruneInterruptedError, UnsupportedDialectError, ScopeTooDeepError, ScopeDepthLimitError, GroupDepthLimitError, ScopeRequeueRefusedError, UnsupportedOperationError, NoDescendantsResolverError, ViewExpiredError, RoleNotVisibleError, AmbiguousRoleError, RoleImmutableError, RoleLevelAboveOwnerError, RoleNotAssignableAtError, PermissionNotDelegableError, RankExceededError, ModelTooLargeError, RelationConfigError, RelationConfigUnknownKeyError, RelationTypeUnknownError, RelationUnknownError, RelationUsersetUnsupportedError, RelationNotDenyableError, RelationDenyExpiryError, MembersOfDenyLimitError, InvalidRelationPageError, ScopeTreeDriftError, ScopeDriftUnguardedError, AuthorizationFrozenError, FreezeHeldError, MassReconcileRefusedError, DenyMigrationRefusedError, DenyReachRefusedError, StaleSourceRefusedError, OperatorWindowExpiredError, ReconcileTooLargeError, markPartialWrite, isPartialWrite, } from './src/errors.js';
54
54
  /**
55
55
  * Validación de identidad y de slugs (422). La aplica el manager en cada
56
56
  * llamada y los drivers por defensa en profundidad; se exporta para que un
@@ -72,9 +72,12 @@ export type { RelationsManagerOptions } from './src/relations/manager.js';
72
72
  export { relationTupleKey, relationSubjectKey } from './src/relations/tuple_key.js';
73
73
  export type { RelationTupleKind, RelationTupleIdentity } from './src/relations/tuple_key.js';
74
74
  export { RELATIONS_CHECK_MANY_MAX } from './src/relations/check_many.js';
75
- export type { RelationsReconcileReport, RelationsReconcileDetail, RelationsPartitionRefusal } from './src/relations/reconcile.js';
76
- export { saveRelationsConfig, readRelationsConfig, readRelationsConfigSpec, } from './src/relations_config_store.js';
77
- export type { RelationsConfigStoreOptions } from './src/relations_config_store.js';
75
+ export { RELATIONS_ENUMERATE_DEFAULT_LIMIT, RELATIONS_ENUMERATE_MAX_LIMIT } from './src/relations/page.js';
76
+ export { RELATIONS_GROUP_DEPTH_MEASURED, GROUP_DEPTH_CONFIG_MAX, GROUP_DEPTH_MAX_NODES, GROUP_DEPTH_MAX_READS, GROUP_DEPTH_MAX_TUPLES, GROUP_DEPTH_DEADLINE_MS, GROUP_DEPTH_CALIBRATION_BACKOFF_MS, GROUP_DEPTH_RECALIBRATION_MIN_INTERVAL_MS, } from './src/relations/group_depth.js';
77
+ export type { RelationsReconcileReport, RelationsReconcileDetail, RelationsPartitionRefusal, RelationsPartitionRefusalReason } from './src/relations/reconcile.js';
78
+ export { saveRelationsConfig, readRelationsConfig, readRelationsConfigSpec, checkRelationsConfigDenyReach, } from './src/relations_config_store.js';
79
+ export type { RelationsConfigStoreOptions, SaveRelationsConfigOptions, RelationsConfigDenyReachCheck } from './src/relations_config_store.js';
80
+ export type { DenyReachFinding, DenyReachReason } from './src/relations/deny_reach.js';
78
81
  export { DatabaseRelationsDriver, RELATIONS_MEMBERS_DENY_CHECK_MAX } from './src/drivers/database_relations_driver.js';
79
82
  export type { DatabaseRelationsDriverOptions } from './src/drivers/database_relations_driver.js';
80
83
  export { resolveGrantExpiry, isActiveExpiry, sameInstant } from './src/expiry.js';
@@ -106,4 +109,3 @@ export { default as AuthzAssignment } from './src/models/authz_assignment.js';
106
109
  export { default as AuthzDeny } from './src/models/authz_deny.js';
107
110
  export { withAuthzScopes } from './src/traits/authz_scopes.js';
108
111
  export { hasUuid } from './src/traits/has_uuid.js';
109
- //# sourceMappingURL=index.d.ts.map
package/build/index.js CHANGED
@@ -43,7 +43,7 @@ export { sqlScopeOutbox } from './src/scope_outbox.js';
43
43
  * obligaría a importar ese SDK — acoplando el call-site al backend que este
44
44
  * paquete existe para abstraer.
45
45
  */
46
- export { AuthorizationBackendError, AuthorizationBackendTimeoutError, ScopeResolverError, InvalidIdentityError, InvalidSlugError, AuthorizationConfigError, AuthorizationInternalError, UnknownScopeError, NoScopeResolverError, UnknownRoleError, UnknownPermissionError, CatalogConflictError, RoleIsNotAccessError, ScopeCycleError, PurgeIncompleteError, WriteConflictError, RenewalUnsafeError, RenewalRacedError, ActorRequiredError, CheckManyBatchError, NotWithinError, WithinRequiredError, WithinRootForbiddenError, TooManyScopesError, TooManyLocalRolesError, MassPurgeRefusedError, PruneInterruptedError, UnsupportedDialectError, ScopeTooDeepError, ScopeDepthLimitError, ScopeRequeueRefusedError, UnsupportedOperationError, NoDescendantsResolverError, ViewExpiredError, RoleNotVisibleError, AmbiguousRoleError, RoleImmutableError, RoleLevelAboveOwnerError, RoleNotAssignableAtError, PermissionNotDelegableError, RankExceededError, ModelTooLargeError, RelationConfigError, RelationConfigUnknownKeyError, RelationTypeUnknownError, RelationUnknownError, RelationUsersetUnsupportedError, RelationNotDenyableError, RelationDenyExpiryError, MembersOfDenyLimitError, ScopeTreeDriftError, ScopeDriftUnguardedError, AuthorizationFrozenError, FreezeHeldError, MassReconcileRefusedError, DenyMigrationRefusedError, StaleSourceRefusedError, OperatorWindowExpiredError, ReconcileTooLargeError,
46
+ export { AuthorizationBackendError, AuthorizationBackendTimeoutError, ResolutionLimitError, ScopeResolverError, InvalidIdentityError, InvalidSlugError, AuthorizationConfigError, AuthorizationInternalError, UnknownScopeError, NoScopeResolverError, UnknownRoleError, UnknownPermissionError, CatalogConflictError, RoleIsNotAccessError, ScopeCycleError, PurgeIncompleteError, WriteConflictError, RenewalUnsafeError, RenewalRacedError, ActorRequiredError, CheckManyBatchError, NotWithinError, WithinRequiredError, WithinRootForbiddenError, TooManyScopesError, TooManyLocalRolesError, MassPurgeRefusedError, PruneInterruptedError, UnsupportedDialectError, ScopeTooDeepError, ScopeDepthLimitError, GroupDepthLimitError, ScopeRequeueRefusedError, UnsupportedOperationError, NoDescendantsResolverError, ViewExpiredError, RoleNotVisibleError, AmbiguousRoleError, RoleImmutableError, RoleLevelAboveOwnerError, RoleNotAssignableAtError, PermissionNotDelegableError, RankExceededError, ModelTooLargeError, RelationConfigError, RelationConfigUnknownKeyError, RelationTypeUnknownError, RelationUnknownError, RelationUsersetUnsupportedError, RelationNotDenyableError, RelationDenyExpiryError, MembersOfDenyLimitError, InvalidRelationPageError, ScopeTreeDriftError, ScopeDriftUnguardedError, AuthorizationFrozenError, FreezeHeldError, MassReconcileRefusedError, DenyMigrationRefusedError, DenyReachRefusedError, StaleSourceRefusedError, OperatorWindowExpiredError, ReconcileTooLargeError,
47
47
  // Cierre de alpha.3 (invariante 13 en purgas multi-request): un driver de
48
48
  // relaciones de terceros marca con `markPartialWrite` el fallo que llega
49
49
  // DESPUÉS de haber borrado parte; el manager notifica `indeterminate: true`.
@@ -66,10 +66,18 @@ export { RelationsManager } from './src/relations/manager.js';
66
66
  // Plan 2.5 · lote A: la clave PURA de una tupla (P3, con `kind` desde el primer día) y el tope de `checkMany` (P4).
67
67
  export { relationTupleKey, relationSubjectKey } from './src/relations/tuple_key.js';
68
68
  export { RELATIONS_CHECK_MANY_MAX } from './src/relations/check_many.js';
69
+ // Lote «antes de RC» · 7.4: la página de `enumerate*` (default y techo; por encima se recorta con cursor).
70
+ export { RELATIONS_ENUMERATE_DEFAULT_LIMIT, RELATIONS_ENUMERATE_MAX_LIMIT } from './src/relations/page.js';
71
+ // Lote «antes de RC» · R1: las cotas de la anidación de grupos (la fila de «Limits»); la regla vive en los drivers.
72
+ export { RELATIONS_GROUP_DEPTH_MEASURED, GROUP_DEPTH_CONFIG_MAX, GROUP_DEPTH_MAX_NODES,
73
+ // Cierre de R1 (🟠 1 del auditor): el presupuesto de la comprobación de una arista en openfga.
74
+ GROUP_DEPTH_MAX_READS, GROUP_DEPTH_MAX_TUPLES, GROUP_DEPTH_DEADLINE_MS,
75
+ // Cierre-2 de R1 (D5): la espera tras una calibración fallida y la cota de la re-calibración tras un 503 de profundidad.
76
+ GROUP_DEPTH_CALIBRATION_BACKOFF_MS, GROUP_DEPTH_RECALIBRATION_MIN_INTERVAL_MS, } from './src/relations/group_depth.js';
69
77
  // La config de relaciones PERSISTIDA (🟡3): API de PLATAFORMA (bajo el gate de
70
78
  // versión, invariante 14). La CONSUME `authz:relations:reconcile` (config del
71
79
  // destino, para la deriva del modelo fusionado en --dry-run).
72
- export { saveRelationsConfig, readRelationsConfig, readRelationsConfigSpec, } from './src/relations_config_store.js';
80
+ export { saveRelationsConfig, readRelationsConfig, readRelationsConfigSpec, checkRelationsConfigDenyReach, } from './src/relations_config_store.js';
73
81
  // El driver `database` de relaciones (el consumidor lo cablea en
74
82
  // `relations.drivers` de config/authorization.ts). El driver `openfga` de
75
83
  // relaciones NO sale de aquí: vive en el subpath `/openfga` (peer opcional).
@@ -100,4 +108,3 @@ export { default as AuthzAssignment } from './src/models/authz_assignment.js';
100
108
  export { default as AuthzDeny } from './src/models/authz_deny.js';
101
109
  export { withAuthzScopes } from './src/traits/authz_scopes.js';
102
110
  export { hasUuid } from './src/traits/has_uuid.js';
103
- //# sourceMappingURL=index.js.map
@@ -43,4 +43,3 @@ export default class AuthzProvider {
43
43
  constructor(app: ApplicationService);
44
44
  register(): void;
45
45
  }
46
- //# sourceMappingURL=authz_provider.d.ts.map
@@ -84,4 +84,3 @@ export default class AuthzProvider {
84
84
  });
85
85
  }
86
86
  }
87
- //# sourceMappingURL=authz_provider.js.map
@@ -7,4 +7,3 @@ import type { AuthorizationManager } from '../src/manager.js';
7
7
  */
8
8
  declare let authorization: AuthorizationManager;
9
9
  export { authorization as default };
10
- //# sourceMappingURL=main.d.ts.map
@@ -10,4 +10,3 @@ await app.booted(async () => {
10
10
  authorization = await app.container.make('authz.manager');
11
11
  });
12
12
  export { authorization as default };
13
- //# sourceMappingURL=main.js.map
@@ -11,4 +11,3 @@ import type { RelationsManager } from '../src/relations/manager.js';
11
11
  */
12
12
  declare let relations: RelationsManager;
13
13
  export { relations as default };
14
- //# sourceMappingURL=relations.d.ts.map
@@ -14,4 +14,3 @@ await app.booted(async () => {
14
14
  relations = await app.container.make('authz.relations');
15
15
  });
16
16
  export { relations as default };
17
- //# sourceMappingURL=relations.js.map
@@ -286,4 +286,3 @@ export declare function runCatalogDiff(catalogs: CatalogSource[], options?: {
286
286
  export declare function syncCatalogs(catalogs: CatalogSource[], options?: SyncCatalogOptions): Promise<{
287
287
  count: number;
288
288
  } & CatalogSyncReport>;
289
- //# sourceMappingURL=catalog.d.ts.map
@@ -856,4 +856,3 @@ export async function syncCatalogs(catalogs, options = {}) {
856
856
  }
857
857
  return { count, shadowedByGlobal, assignableAtViolations };
858
858
  }
859
- //# sourceMappingURL=catalog.js.map
@@ -304,6 +304,19 @@ export declare class CatalogCache {
304
304
  * vuelve a intentarlo.
305
305
  */
306
306
  view(): Promise<CatalogView>;
307
+ /**
308
+ * **La foto para una ESCRITURA** (lote C · carreras, resolución 2 del juez = K4): contrasta la versión con la fila
309
+ * SIEMPRE, ignore o no la ventana `{ everyMs }`. Con `'always'` es exactamente `view()`. La ventana es para DECIDIR
310
+ * (invariante 14: una pregunta puede responder con un catálogo de hasta `everyMs` de antigüedad); una escritura del driver
311
+ * deja ESTADO en el store que no caduca —el barrido de `moved`, el settle, la proyección—, y con un memo rancio un rol
312
+ * recién definido en otro proceso no se barre (fail-open persistente medido, K4). Coste: el SELECT por PK de `'always'`.
313
+ *
314
+ * **Nunca se suma a una comprobación ni a una carga EN VUELO** (asentar · cierre 2, ⚪ 6 / O-6 del auditor): la que ya
315
+ * estaba en marcha pudo leer la fila ANTES de la escritura que precede a esta llamada (la propia, o la de otro proceso
316
+ * que la escritura acaba de observar) y serviría esa foto. Se espera la carga en vuelo y se lee la versión otra vez;
317
+ * una carga que arranca DESPUÉS de esta llamada sí se comparte (ya es posterior).
318
+ */
319
+ freshView(): Promise<CatalogView>;
307
320
  /**
308
321
  * Olvida la foto de ESTE memo. La siguiente pregunta recarga. Es un bump
309
322
  * de generación, no un `#view = null`: una carga en vuelo capturó la
@@ -321,4 +334,3 @@ export declare class CatalogCache {
321
334
  * disfrazado de dato.
322
335
  */
323
336
  export declare function parseAssignableAt(slug: string, raw: unknown): readonly ScopeType[] | null;
324
- //# sourceMappingURL=catalog_cache.d.ts.map