@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
@@ -13,4 +13,3 @@ export { runRelationsDriverContract, registerRelationsDriverContract, makeRelati
13
13
  export { createTestClock } from './test_clock.js';
14
14
  export { runRelationsReconcileContract, registerRelationsReconcileContract, } from './relations_reconcile_contract.js';
15
15
  export { runScopeOutboxContract, registerScopeOutboxContract, makeScopeOutbox, SCOPE_OUTBOX_CAPABILITY_KEYS, } from './scope_outbox_contract.js';
16
- //# sourceMappingURL=main.js.map
@@ -290,4 +290,3 @@ export declare function runMigrationDirection(harness: MigrationContractHarness,
290
290
  */
291
291
  export declare function runMigrationContract(harness: MigrationContractHarness): void;
292
292
  export declare function registerMigrationContract(harness: MigrationContractHarness, api: ContractTestApi): void;
293
- //# sourceMappingURL=migration_contract.d.ts.map
@@ -586,4 +586,3 @@ export function registerMigrationContract(harness, api) {
586
586
  }
587
587
  });
588
588
  }
589
- //# sourceMappingURL=migration_contract.js.map
@@ -22,6 +22,13 @@ export declare const RELATIONS_CONTRACT_OBJECT_TYPES: readonly ["document", "wor
22
22
  * editor ⊆ viewer` (includes de un nivel) y `membersOf` habilitado en `database`.
23
23
  */
24
24
  export declare function contractRelationsConfig(): RelationsConfig;
25
+ /**
26
+ * **La config del contrato CON techo de anidación de grupos** (lote «antes de RC» · R1, cierre: el runner publicado no
27
+ * tenía ningún caso del techo y un driver de terceros no tenía juez): la MISMA config que `contractRelationsConfig` con
28
+ * `maxGroupDepth` (por defecto 3) y el opt-in de `database` (`database.enforceGroupDepth`). Un driver que declare
29
+ * `groupDepthLimit()` tiene que responder con un techo ≤ ese número (la config solo BAJA el del driver) y aplicarlo.
30
+ */
31
+ export declare function contractGroupDepthRelationsConfig(maxGroupDepth?: number): RelationsConfig;
25
32
  /**
26
33
  * **La config del contrato CON deny** (plan 2.5 · lote D · D1): los MISMOS dos tipos con el mismo retículo y, además,
27
34
  * un tercero sin opt-in —para que «deny sobre un tipo sin deny ⇒ 422» y «D8: un tipo sin opt-in también honra»
@@ -102,4 +109,3 @@ export declare function registerRelationsDriverContract(harness: RelationsDriver
102
109
  registered: number;
103
110
  covered: Set<keyof RelationsDriverCapabilities>;
104
111
  };
105
- //# sourceMappingURL=relations_contract.d.ts.map
@@ -25,6 +25,7 @@ import { RenewalUnsafeError } from '../errors.js';
25
25
  import { RelationsManager } from '../relations/manager.js';
26
26
  import { defineRelationsConfig, assertRelationDeclared, assertObjectTypeDeclared, assertUsersetSubject, assertRelationDenyable, assertDenySubject, assertDenyNoExpiry, cutPathAllows, decideFrom, deniedRelationOf, denyPhaseOf, storedRelationKind, relationsDown, RELATION_DENY_PREFIX, } from '../relations/define_relations_config.js';
27
27
  import { assertCheckManyObjects, distinctObjects, expandByPosition, RELATIONS_CHECK_MANY_MAX } from '../relations/check_many.js';
28
+ import { assertEnumeratePage, assertNoListPage } from '../relations/page.js';
28
29
  import { systemClock } from '../clock.js';
29
30
  import { resolveGrantExpiry, sameInstant } from '../expiry.js';
30
31
  import { isRelUserset } from '../types.js';
@@ -59,6 +60,23 @@ export function contractRelationsConfig() {
59
60
  database: { membersOf: true },
60
61
  });
61
62
  }
63
+ /**
64
+ * **La config del contrato CON techo de anidación de grupos** (lote «antes de RC» · R1, cierre: el runner publicado no
65
+ * tenía ningún caso del techo y un driver de terceros no tenía juez): la MISMA config que `contractRelationsConfig` con
66
+ * `maxGroupDepth` (por defecto 3) y el opt-in de `database` (`database.enforceGroupDepth`). Un driver que declare
67
+ * `groupDepthLimit()` tiene que responder con un techo ≤ ese número (la config solo BAJA el del driver) y aplicarlo.
68
+ */
69
+ export function contractGroupDepthRelationsConfig(maxGroupDepth = 3) {
70
+ return defineRelationsConfig({
71
+ objectTypes: RELATIONS_CONTRACT_OBJECT_TYPES.map((type) => ({
72
+ type,
73
+ relations: [{ name: 'owner' }, { name: 'editor', includes: ['owner'] }, { name: 'viewer', includes: ['editor'] }],
74
+ })),
75
+ holderTypes: [...RELATIONS_HOLDER_TYPES],
76
+ database: { membersOf: true, enforceGroupDepth: true },
77
+ maxGroupDepth,
78
+ });
79
+ }
62
80
  /**
63
81
  * **La config del contrato CON deny** (plan 2.5 · lote D · D1): los MISMOS dos tipos con el mismo retículo y, además,
64
82
  * un tercero sin opt-in —para que «deny sobre un tipo sin deny ⇒ 422» y «D8: un tipo sin opt-in también honra»
@@ -308,10 +326,12 @@ function buildRelationsDouble(options, tuples, now) {
308
326
  assertSubjectDeclared(subject);
309
327
  return resolves(subject, relation, object, scopeKeyOf(partition));
310
328
  },
311
- async listObjects(subject, relation, objectType, partition, page) {
329
+ async listObjects(subject, relation, objectType, partition) {
312
330
  assertRelationDeclared(config, { type: objectType }, relation);
313
331
  assertSubjectDeclared(subject);
314
332
  const partitionKey = scopeKeyOf(partition);
333
+ // 7.4: el doble tampoco pagina (un quinto argumento ≠ undefined ⇒ 500), después de F-05 y la partición.
334
+ assertNoListPage('listObjects', arguments[4], 'double');
315
335
  const ids = new Set();
316
336
  for (const t of tuples) {
317
337
  if (t.partition === partitionKey && t.objectType === objectType && active(t))
@@ -332,9 +352,10 @@ function buildRelationsDouble(options, tuples, now) {
332
352
  }
333
353
  return { objects: matches };
334
354
  },
335
- async listSubjects(relation, object, partition, page) {
355
+ async listSubjects(relation, object, partition) {
336
356
  assertRelationDeclared(config, object, relation);
337
357
  const partitionKey = scopeKeyOf(partition);
358
+ assertNoListPage('listSubjects', arguments[3], 'double');
338
359
  // DIRECTOS del relation EXACTO (invariante 7): ni transitivo (eso es
339
360
  // `membersOf`) ni derivado. `usersetSubjects` decide si los usersets
340
361
  // (`group:g#member`) salen o solo los holders.
@@ -422,9 +443,11 @@ function buildRelationsDouble(options, tuples, now) {
422
443
  assertRelationDeclared(config, object, relation);
423
444
  return denyPhaseOf(config, object.type) ?? null;
424
445
  };
425
- driver.listDenies = async (relation, object, partition) => {
446
+ driver.listDenies = async function (relation, object, partition) {
426
447
  assertRelationDeclared(config, object, relation);
427
- const direct = directSubjects(scopeKeyOf(partition), object.type, object.id, deniedRelationOf(relation));
448
+ const key = scopeKeyOf(partition);
449
+ assertNoListPage('listDenies', arguments[3], 'double');
450
+ const direct = directSubjects(key, object.type, object.id, deniedRelationOf(relation));
428
451
  return { subjects: dedupeSubjects(direct.map((t) => t.subject)) };
429
452
  };
430
453
  driver.decideMany = async (subject, relation, objects, partition) => {
@@ -444,16 +467,19 @@ function buildRelationsDouble(options, tuples, now) {
444
467
  };
445
468
  }
446
469
  if (capabilities.membersOfNative) {
447
- driver.membersOf = async (object, relation, partition) => {
470
+ driver.membersOf = async function (object, relation, partition) {
448
471
  assertRelationDeclared(config, object, relation);
449
472
  // Lote D: `membersOf` RESTA (corte de camino por candidato, como `database`).
450
473
  const key = scopeKeyOf(partition);
474
+ assertNoListPage('membersOf', arguments[3], 'double');
451
475
  return { subjects: collectMembers(object, relation, key).filter((s) => resolves(s, relation, object, key)) };
452
476
  };
453
477
  }
454
478
  if (capabilities.enumerateRelations) {
455
- driver.enumerateRelations = async (partition) => {
479
+ driver.enumerateRelations = async (partition, page) => {
456
480
  const partitionKey = scopeKeyOf(partition);
481
+ // 7.4: la misma forma que los drivers reales; el doble no emite cursor (todo en una página) ⇒ after es 500.
482
+ assertEnumeratePage('enumerateRelations', page, 'none', 'double');
457
483
  // SIN filtrar la caducada (R-15): llega con su `expiresAt` para que el
458
484
  // destino de reconcile la cuente en `skipped`, no para que desaparezca.
459
485
  const out = tuples
@@ -703,6 +729,46 @@ export function registerRelationsDriverContract(harness, api) {
703
729
  await state.manager.relate({ object: g2, relation: 'member' }, 'viewer', doc, p);
704
730
  assert.isTrue(await state.manager.check(u, 'viewer', doc, p));
705
731
  });
732
+ /* ── R1 (lote «antes de RC»): el techo de anidación de grupos, si el driver lo declara ── */
733
+ test('R1 · groupDepthLimit (si el driver lo declara): con maxGroupDepth 3 + database.enforceGroupDepth el techo es ≤ 3 y se APLICA — la arista que deja una cadena más larga, por ARRIBA o por ABAJO, o que cierra un ciclo ⇒ 422 E_AUTHZ_GROUP_DEPTH_LIMIT sin escribir (check sigue F) y sin evento por el manager; la membresía de un holder y una relación de dominio no son anidación', async ({ assert, }) => {
734
+ const config = contractGroupDepthRelationsConfig(3);
735
+ const driver = await harness.makeDriver(config);
736
+ // `groupDepthLimit` es OPCIONAL en el puerto: un driver sin él no declara techo (el plan del reconcile no tiene
737
+ // nada que negar) y no hay gate que juzgar. Lo mismo un driver que responde `null` (sin techo con esta config).
738
+ const limit = typeof driver.groupDepthLimit === 'function' ? await driver.groupDepthLimit() : null;
739
+ if (limit === null)
740
+ return;
741
+ assert.isTrue(Number.isInteger(limit) && limit >= 1 && limit <= 3, `el techo con maxGroupDepth 3 tiene que estar en 1..3 (llegó ${String(limit)})`);
742
+ const events = [];
743
+ const manager = new RelationsManager(driver, config, { driverName: harness.name, onRelationWrite: (e) => void events.push(e) });
744
+ const p = partition();
745
+ const g = Array.from({ length: limit + 2 }, () => ({ type: 'group', id: docId() }));
746
+ const member = (o) => ({ object: o, relation: 'member' });
747
+ for (let i = 0; i < limit; i++)
748
+ await manager.relate(member(g[i]), 'member', g[i + 1], p);
749
+ const u = holder();
750
+ await manager.relate(u, 'member', g[0], p);
751
+ assert.isTrue(await manager.check(u, 'member', g[limit], p), `una cadena de ${limit} saltos (el techo) entra y resuelve`);
752
+ const before = events.length;
753
+ const refused = async (label, run) => {
754
+ const error = await run().then(() => null, (e) => e);
755
+ assert.equal(error?.status, 422, `${label}: ${error?.message ?? 'entró'}`);
756
+ assert.equal(error?.code, 'E_AUTHZ_GROUP_DEPTH_LIMIT', label);
757
+ return error;
758
+ };
759
+ await refused('por ARRIBA (la cima de la cadena dentro de otro grupo)', () => manager.relate(member(g[limit]), 'member', g[limit + 1], p));
760
+ assert.isFalse(await manager.check(u, 'member', g[limit + 1], p), 'la arista rechazada no se escribió');
761
+ const x = { type: 'group', id: docId() };
762
+ await refused('por ABAJO (otro grupo dentro de la base de la cadena)', () => manager.relate(member(x), 'member', g[0], p));
763
+ const v = holder();
764
+ await manager.relate(v, 'member', x, p);
765
+ assert.isFalse(await manager.check(v, 'member', g[0], p), 'x ⊂ g0 no se escribió');
766
+ await refused('el CICLO', () => manager.relate(member(g[limit]), 'member', g[0], p));
767
+ assert.equal(events.length, before + 1, '422 ⇒ esa escritura no ocurrió y no hay evento (invariante 13): solo el del holder v');
768
+ // No es anidación: la membresía de un holder y una relación de dominio sobre la cima.
769
+ await manager.relate(holder(), 'member', g[limit], p);
770
+ await manager.relate(member(g[limit]), 'viewer', { type: 'document', id: docId() }, p);
771
+ });
706
772
  /* ── R-05: la partición aísla ── */
707
773
  test('R-05 · partición: relate en A no concede en B', async ({ assert }) => {
708
774
  const u = holder();
@@ -750,6 +816,93 @@ export function registerRelationsDriverContract(harness, api) {
750
816
  const page = await state.manager.listObjects(u, 'viewer', 'document', p);
751
817
  assert.sameMembers(page.objects.map((o) => o.id).sort(), [d1.id, d2.id].sort());
752
818
  });
819
+ /* ── 7.4 (lote «antes de RC», opción 4 del juez): los listados NO paginan ── */
820
+ /**
821
+ * **P13 · «sin página ⇒ lista completa y sin cursor»** (injerto 1 del juez; auditor R1-2, tester P13). N = 150:
822
+ * por encima del default de 100 de `enumerate*` de `database` y del `page_size` 100 de `Read` en OpenFGA, así que
823
+ * un driver que pagina por dentro y devuelve la PRIMERA página (o un cursor que nadie lee) es rojo aquí. El tipo ya
824
+ * no tiene `cursor` (protege a quien compila); este caso protege en EJECUCIÓN frente a un driver de terceros y a un
825
+ * default de `limit` que alguien meta en 2.6. `listObjects` puede venir `truncated` SOLO si el driver declara
826
+ * `listObjectsTruncation` (y entonces lo que trae ⊆ lo escrito); los demás, el conjunto exacto.
827
+ */
828
+ test('7.4 · P13 · sin page los listados son EXHAUSTIVOS y sin cursor: N=150 (> 100 de enumerate y del page_size de Read) en listSubjects y listObjects —y listDenies con relationDenies, membersOf con membersOfNative—; listObjects truncated solo con listObjectsTruncation', async ({ assert, }) => {
829
+ const N = 150;
830
+ const p = partition();
831
+ const users = Array.from({ length: N }, () => holder());
832
+ const ids = (subjects) => subjects.map((s) => s.uuid).sort();
833
+ const expected = users.map((u) => u.uuid).sort();
834
+ const doc = { type: 'document', id: docId() };
835
+ for (const u of users)
836
+ await state.manager.relate(u, 'viewer', doc, p);
837
+ const subjects = await state.manager.listSubjects('viewer', doc, p);
838
+ assert.notProperty(subjects, 'cursor', 'listSubjects sin page no devuelve cursor');
839
+ assert.notEqual(subjects.truncated, true, 'listSubjects no corta (Read se agota o lanza)');
840
+ assert.deepEqual(ids(subjects.subjects), expected, `listSubjects devuelve los ${N}, no una página`);
841
+ const reader = holder();
842
+ const docs = Array.from({ length: N }, () => docId());
843
+ for (const id of docs)
844
+ await state.manager.relate(reader, 'viewer', { type: 'document', id }, p);
845
+ const objects = await state.manager.listObjects(reader, 'viewer', 'document', p);
846
+ assert.notProperty(objects, 'cursor', 'listObjects sin page no devuelve cursor');
847
+ const got = objects.objects.map((o) => o.id).sort();
848
+ if (objects.truncated === true) {
849
+ assert.isTrue(harness.capabilities.listObjectsTruncation, 'truncated sin declarar listObjectsTruncation');
850
+ assert.includeMembers(docs, got, 'lo que trae una lista truncada es de lo escrito');
851
+ }
852
+ else {
853
+ assert.deepEqual(got, [...docs].sort(), `listObjects devuelve los ${N}, no una página`);
854
+ }
855
+ if (harness.capabilities.membersOfNative) {
856
+ const group = { type: 'group', id: docId() };
857
+ for (const u of users)
858
+ await state.manager.relate(u, 'member', group, p);
859
+ const members = await state.manager.membersOf(group, 'member', p);
860
+ assert.notProperty(members, 'cursor', 'membersOf sin page no devuelve cursor');
861
+ assert.deepEqual(ids(members.subjects), expected, `membersOf devuelve los ${N}`);
862
+ }
863
+ if (harness.capabilities.relationDenies) {
864
+ const { manager } = await denyManager();
865
+ const denied = { type: 'document', id: docId() };
866
+ for (const u of users)
867
+ await manager.deny(u, 'viewer', denied, p);
868
+ const denies = await manager.listDenies('viewer', denied, p);
869
+ assert.notProperty(denies, 'cursor', 'listDenies sin page no devuelve cursor');
870
+ assert.deepEqual(ids(denies.subjects), expected, `listDenies devuelve los ${N}`);
871
+ }
872
+ });
873
+ /**
874
+ * **RC74-1 · página en los cuatro listados ⇒ 500, cero llamadas** (capa MANAGER, sobre el driver del harness). En
875
+ * 2.6 este caso pasa a ser la cara `whenFalse` de cada capacidad de paginación. `undefined` EXPLÍCITO entra
876
+ * (RC74-3). No se exige al driver en directo: obligaría a un tercero a un 500 por un parámetro que ya no está en
877
+ * el tipo (lo cubren la guardia y los specs de los drivers del paquete).
878
+ */
879
+ test('7.4 · RC74-1 · listObjects/listSubjects/listDenies/membersOf con page ({ limit: 1 }, null, {}) ⇒ 500 E_AUTHZ_UNSUPPORTED nombrando la operación, con CERO llamadas al driver; undefined explícito entra (RC74-3)', async ({ assert, }) => {
880
+ const u = holder();
881
+ const doc = { type: 'document', id: docId() };
882
+ const group = { type: 'group', id: docId() };
883
+ const p = partition();
884
+ const m = state.manager;
885
+ const calls = [
886
+ ['listObjects', (page) => m.listObjects(u, 'viewer', 'document', p, page)],
887
+ ['listSubjects', (page) => m.listSubjects('viewer', doc, p, page)],
888
+ ['listDenies', (page) => m.listDenies('viewer', doc, p, page)],
889
+ ['membersOf', (page) => m.membersOf(group, 'member', p, page)],
890
+ ];
891
+ for (const [operation, call] of calls) {
892
+ for (const page of [{ limit: 1 }, null, {}]) {
893
+ const before = state.calls();
894
+ const caught = await call(page).then(() => null, (e) => e);
895
+ assert.isNotNull(caught, `${operation}(…, ${JSON.stringify(page)}) entró: una página que no hace nada es una lista entera que parece una página`);
896
+ assert.equal(caught.status, 500, `${operation}: ${caught.message}`);
897
+ assert.equal(caught.code, 'E_AUTHZ_UNSUPPORTED', `${operation}: ${caught.message}`);
898
+ assert.include(caught.message, operation);
899
+ assert.equal(state.calls(), before, `${operation} con page: CERO llamadas al driver`);
900
+ }
901
+ }
902
+ // RC74-3: el argumento `undefined` explícito es «sin página» (quien reenvía un opcional vacío no se rompe).
903
+ assert.isArray((await m.listObjects(u, 'viewer', 'document', p, undefined)).objects);
904
+ assert.isArray((await m.listSubjects('viewer', doc, p, undefined)).subjects);
905
+ });
753
906
  /* ── R-12: identidad mal formada ⇒ 422 ── */
754
907
  test('R-12 · partición mal formada ⇒ 422 con code', async ({ assert }) => {
755
908
  const u = holder();
@@ -1783,6 +1936,21 @@ export function registerRelationsDriverContract(harness, api) {
1783
1936
  for (const r of denies)
1784
1937
  await m.deny(u, r, object, p);
1785
1938
  }
1939
+ /* ── Estrategia 2.5.0 · firma 4 · el par `experimental` (regla D15; mismo nombre que en roles) ── */
1940
+ // Dice lo que el consumidor tiene que saber («fuera de la garantía de la versión») y no apaga casos: el runner corre
1941
+ // ENTERO contra un driver experimental. Se observa la declaración del driver y la copia normalizada del manager.
1942
+ caseFor('experimental', {
1943
+ whenTrue: () => test('experimental:true · el driver se declara EXPERIMENTAL (capabilities.experimental === true) y `relations.capabilities()` lo publica tal cual; el runner corre igual contra él', async ({ assert }) => {
1944
+ assert.strictEqual(state.base.capabilities?.experimental, true, 'el harness declara experimental: true y el driver no: declara lo observable');
1945
+ const published = state.manager.capabilities();
1946
+ assert.strictEqual(published.experimental, true);
1947
+ assert.isTrue(Object.isFrozen(published));
1948
+ }),
1949
+ whenFalse: () => test('experimental:false · el driver NO se declara experimental (ausente o false) y `relations.capabilities().experimental` es false, nunca undefined (D15)', async ({ assert }) => {
1950
+ assert.notStrictEqual(state.base.capabilities?.experimental, true, 'el harness declara experimental: false y el driver declara true: declara lo observable');
1951
+ assert.strictEqual(state.manager.capabilities().experimental, false);
1952
+ }),
1953
+ });
1786
1954
  /* ── Cierre 2 de D2 · 🟠 N1 · el par `atomicRenewal` (renovar una membresía que TRANSPORTA un deny) ── */
1787
1955
  /**
1788
1956
  * Siembra: U ∈ g con caducidad (+1 h), un allow DIRECTO de U sobre X y —si el driver deniega— el deny de `g#member`
@@ -2361,4 +2529,3 @@ export function registerRelationsDriverContract(harness, api) {
2361
2529
  });
2362
2530
  return { registered, covered };
2363
2531
  }
2364
- //# sourceMappingURL=relations_contract.js.map
@@ -30,4 +30,3 @@ export declare function registerRelationsReconcileContract(harness: RelationsRec
30
30
  }): {
31
31
  registered: number;
32
32
  };
33
- //# sourceMappingURL=relations_reconcile_contract.d.ts.map
@@ -424,4 +424,3 @@ export function registerRelationsReconcileContract(harness, api) {
424
424
  });
425
425
  return { registered };
426
426
  }
427
- //# sourceMappingURL=relations_reconcile_contract.js.map
@@ -108,4 +108,3 @@ export declare function registerScopeOutboxContract(harness: ScopeOutboxContract
108
108
  registered: number;
109
109
  covered: Set<keyof ScopeOutboxCapabilities>;
110
110
  };
111
- //# sourceMappingURL=scope_outbox_contract.d.ts.map
@@ -752,4 +752,3 @@ export function registerScopeOutboxContract(harness, api) {
752
752
  });
753
753
  return { registered, covered };
754
754
  }
755
- //# sourceMappingURL=scope_outbox_contract.js.map
@@ -62,4 +62,3 @@ export declare function descendantsFrom(tree: ContractScopeTree): ScopeDescendan
62
62
  */
63
63
  export declare function memoryScopeTree(): ContractScopeTree;
64
64
  export { APP_SCOPE };
65
- //# sourceMappingURL=scope_tree.d.ts.map
@@ -142,4 +142,3 @@ export function memoryScopeTree() {
142
142
  };
143
143
  }
144
144
  export { APP_SCOPE };
145
- //# sourceMappingURL=scope_tree.js.map
@@ -12,4 +12,3 @@ export interface TestClock {
12
12
  }
13
13
  /** Un reloj de test parado en `start` (default: la hora de pared al crearlo), controlable con `set`/`advance`. */
14
14
  export declare function createTestClock(start?: Date | number | string): TestClock;
15
- //# sourceMappingURL=test_clock.d.ts.map
@@ -62,4 +62,3 @@ export function createTestClock(start = systemClock()) {
62
62
  clock.at = (offsetMs) => new Date(current + offsetMs);
63
63
  return clock;
64
64
  }
65
- //# sourceMappingURL=test_clock.js.map
@@ -42,4 +42,3 @@ export type WithAuthzScopes<T extends Constructor> = T & AuthzScopesStatics;
42
42
  */
43
43
  export declare function withAuthzScopes<T extends Constructor | AuthzScopesOptions>(arg: T): T extends Constructor ? WithAuthzScopes<T> : <U extends Constructor>(Base: U) => WithAuthzScopes<U>;
44
44
  export {};
45
- //# sourceMappingURL=authz_scopes.d.ts.map
@@ -54,4 +54,3 @@ export function withAuthzScopes(arg) {
54
54
  const clock = options.clock ?? systemClock;
55
55
  return (Base) => mixin(Base, clock);
56
56
  }
57
- //# sourceMappingURL=authz_scopes.js.map
@@ -81,6 +81,11 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
81
81
  <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelPaginatorContract<InstanceType<Model_1>>, "paginate">): void;
82
82
  <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event>): void;
83
83
  };
84
+ before: {
85
+ <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_1 extends "find" | "fetch">(this: Model_1, event: Event_1, handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, Event_1>): void;
86
+ <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<[import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>], "paginate">): void;
87
+ <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
88
+ };
84
89
  readonly booted: boolean;
85
90
  $columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
86
91
  $relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
@@ -117,11 +122,6 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
117
122
  };
118
123
  $defineProperty: <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Prop extends keyof Model_1>(this: Model_1, propertyName: Prop, defaultValue: Model_1[Prop], strategy: "inherit" | "define" | ((value: Model_1[Prop]) => Model_1[Prop])) => void;
119
124
  boot: () => void;
120
- before: {
121
- <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_1 extends "find" | "fetch">(this: Model_1, event: Event_1, handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, Event_1>): void;
122
- <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<[import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>], "paginate">): void;
123
- <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
124
- };
125
125
  create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
126
126
  createQuietly: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
127
127
  createMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
@@ -148,4 +148,3 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
148
148
  fetchOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
149
149
  updateOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
150
150
  } & Model;
151
- //# sourceMappingURL=has_uuid.d.ts.map
@@ -35,4 +35,3 @@ export const hasUuid = (superclass) => {
35
35
  ], WithPrimaryUuidClass, "generateUuid", null);
36
36
  return WithPrimaryUuidClass;
37
37
  };
38
- //# sourceMappingURL=has_uuid.js.map
@@ -334,6 +334,15 @@ export interface AuthorizationDriverCapabilities {
334
334
  * `transactional: true`.
335
335
  */
336
336
  transactionalWrites: boolean;
337
+ /**
338
+ * **El driver es EXPERIMENTAL: fuera de la garantía de la versión** (estrategia 2.5.0, firma 4 del dueño 2026-10-21).
339
+ * **Opcional y normalizada a `false`** (regla D15). `openfga` = `true` en 2.5.x: no apto para producción, no se
340
+ * construye sin `experimental: 'openfga@2.5'` (500 `E_AUTHZ_CONFIG` sin él) y sus límites declarados —con cifra,
341
+ * receta, detector y versión de cierre (2.6)— están en README «Limits». `database` = `false` (no la declara). El juez
342
+ * corre ENTERO contra un driver experimental (no hay skip): la capacidad dice lo que el consumidor tiene que saber, no
343
+ * apaga casos. Par de caras en `runAuthorizationDriverContract`.
344
+ */
345
+ experimental?: boolean;
337
346
  }
338
347
  /**
339
348
  * Las claves de `AuthorizationDriverCapabilities`, LITERALES (2.4.0-alpha.4):
@@ -730,6 +739,14 @@ export interface ReconcileFact {
730
739
  * que es justo lo que la migración no puede hacer.
731
740
  */
732
741
  expiresAt?: Date | null;
742
+ /**
743
+ * `deny` (cierre-2 de L11 · C1): el origen guarda ese deny con una condición que el paquete no escribe ni sabe leer
744
+ * (escritor AJENO; el motivo, p. ej. `foreign: condición 'inverted'`). NO es un deny limpio: el destino lo migra como
745
+ * deny —fail-closed: ante la duda, niega— pero lo DECLARA (`invalid-condition`, deriva); y en la pasada de
746
+ * MANTENIMIENTO (origen = destino) no se escribe ni se borra, solo se reporta. Un destino que ignore la marca lo trata
747
+ * como deny permanente, que es la lectura conservadora para un deny.
748
+ */
749
+ invalidCondition?: string;
733
750
  /** Cómo lo nombra el origen (para `details`): un motivo sin la fila no se arregla. */
734
751
  detail: string;
735
752
  }
@@ -1151,6 +1168,18 @@ export interface ScopeRelayFailure {
1151
1168
  change: ScopeTreeChange;
1152
1169
  error: string;
1153
1170
  transient: boolean;
1171
+ /**
1172
+ * Cierre de R3 (⚪ R3-A7; punto 7): el `code` del error (`E_AUTHZ_WRITE_CONFLICT`, `E_AUTHZ_RESOLUTION_LIMIT`,
1173
+ * `E_AUTHZ_BACKEND_UNAVAILABLE`…), para que el comando diga QUÉ rama transitoria corrió: una carrera o un grafo
1174
+ * demasiado profundo no son «el backend no respondió». Ausente si el error no trae `code` (driver de terceros).
1175
+ */
1176
+ code?: string;
1177
+ /**
1178
+ * Cierre-2 de L11 (⚪ C2): `true` ⇒ el fallo es del DESPLIEGUE (el modelo fijado no es el que genera la config, o el
1179
+ * generador no puede generar con él; `isDeploymentFailure`), no del backend ni de la entrada. Es `transient` también
1180
+ * (no consume intento), pero el backend SÍ respondió: el comando lo dice en su propia línea. Ausente = no lo es.
1181
+ */
1182
+ deployment?: true;
1154
1183
  }
1155
1184
  /** Qué fue de una entrada en la pasada (2.4.0-alpha.5 · R1, `ScopeRelayReport.entries`). */
1156
1185
  export type ScopeRelayEntryStatus = 'applied' | 'deferred' | 'failed' | 'dead';
@@ -1179,6 +1208,13 @@ export interface ScopeRelayEntry {
1179
1208
  status: ScopeRelayEntryStatus;
1180
1209
  reason?: string;
1181
1210
  attempts?: number;
1211
+ /**
1212
+ * Solo en `applied` de un `attached`/`moved` relevado contra un driver con `hierarchyFacts` (asentar · cierre 1): el
1213
+ * padre que se APLICÓ de verdad —el FRESCO del consumidor, que puede no ser `change.parent` (`reason:
1214
+ * 'consumer-parent'`)— o `null` si no se escribió arista (`reason: 'superseded'`: el nodo ya no existe y un `detached`
1215
+ * suyo lo respalda). Ausente en `database`, que aplica el encolado tal cual.
1216
+ */
1217
+ appliedParent?: ScopeRef | null;
1182
1218
  /**
1183
1219
  * Solo en `deferred` (2.4.0-alpha.8): el ORIGEN —transitivo, la causa
1184
1220
  * raíz— del bloqueo que la aplazó. `parked` = una entrada APARCADA (la
@@ -1963,7 +1999,12 @@ export interface RelationDenyOptions extends RelationTransactionOptions {
1963
1999
  }
1964
2000
  /** Una decisión de `decideMany` (D9): `allow` (concede aquí), `deny` (no heredes más allá de este objeto), `none` (nada aquí). */
1965
2001
  export type RelationDecision = 'allow' | 'deny' | 'none';
1966
- /** Una página de una enumeración de relaciones (cursor opaco que AVANZA, no filtra herencia). */
2002
+ /**
2003
+ * Una página de `enumerateRelations`/`enumerateRelationsMany` (cursor opaco que AVANZA, no filtra herencia). **Solo
2004
+ * `enumerate*`** desde el lote «antes de RC» · 7.4: los `list*` no paginan en 2.5 (ver `RelationObjectsPage`).
2005
+ * `limit` entero ≥ 1 (si no, 422 `E_AUTHZ_INVALID_PAGE`; por encima de `RELATIONS_ENUMERATE_MAX_LIMIT` se recorta);
2006
+ * `after` = el `cursor` que devolvió la página anterior, sin tocar.
2007
+ */
1967
2008
  export interface RelationPage {
1968
2009
  limit?: number;
1969
2010
  after?: string;
@@ -2054,24 +2095,31 @@ export interface RelationsDriverCapabilities {
2054
2095
  * `runRelationsDriverContract`.
2055
2096
  */
2056
2097
  atomicRenewal?: boolean;
2098
+ /**
2099
+ * **El driver es EXPERIMENTAL: fuera de la garantía de la versión** (estrategia 2.5.0, firma 4). Mismo nombre y misma
2100
+ * regla que en `AuthorizationDriverCapabilities` (D15: opcional, normalizada a `false`). `openfga` = `true` en 2.5.x
2101
+ * (500 `E_AUTHZ_CONFIG` al construir sin `experimental: 'openfga@2.5'`; límites en README «Limits», cierre en 2.6);
2102
+ * `database` = `false`. Par de caras en `runRelationsDriverContract`.
2103
+ */
2104
+ experimental?: boolean;
2057
2105
  }
2058
2106
  /** Las claves de `RelationsDriverCapabilities`, literales: lo que `relations.capabilities()` normaliza (2.4.0-alpha.4). */
2059
2107
  export declare const RELATIONS_DRIVER_CAPABILITY_KEYS: ReadonlyArray<keyof RelationsDriverCapabilities>;
2060
2108
  /** La copia congelada y normalizada de `relations.capabilities()`; misma regla que `normalizeAuthorizationCapabilities`. */
2061
2109
  export declare function normalizeRelationsCapabilities(declared: Partial<RelationsDriverCapabilities> | undefined): Readonly<RelationsDriverCapabilities>;
2062
2110
  /**
2063
- * Una página de `listObjects`/`listSubjects`/`enumerateRelations`. `truncated`
2064
- * dice si el backend cortó al tope (solo con `listObjectsTruncation`): un
2065
- * consumidor que lo ve sabe que hay MÁS y no toma la lista por completa.
2111
+ * La respuesta de `listObjects`. **Sin `cursor`** (lote «antes de RC» · 7.4, opción 4 del juez): los `list*` de
2112
+ * relaciones devuelven el conjunto COMPLETO o señalan `truncated`; nunca un cursor. `truncated` dice si el backend
2113
+ * cortó (tope o deadline; solo con `listObjectsTruncation`): un consumidor que lo ve sabe que hay MÁS y no toma la
2114
+ * lista por completa. La paginación de los listados llegará en 2.6 como métodos APARTE (`*Page`) con capacidad propia.
2066
2115
  */
2067
2116
  export interface RelationObjectsPage {
2068
2117
  objects: RelObject[];
2069
- cursor?: string;
2070
2118
  truncated?: boolean;
2071
2119
  }
2120
+ /** La respuesta de `listSubjects`/`listDenies`/`membersOf`: completa o `truncated`, **sin `cursor`** (7.4). */
2072
2121
  export interface RelationSubjectsPage {
2073
2122
  subjects: RelSubject[];
2074
- cursor?: string;
2075
2123
  truncated?: boolean;
2076
2124
  }
2077
2125
  /** Una tupla de relación tal como la enumera `enumerateRelations` (origen de reconcile). */
@@ -2096,6 +2144,19 @@ export interface RelationTuple {
2096
2144
  * o se contaría como relación no declarada, y perder un deny no es una pérdida que se pueda declarar.
2097
2145
  */
2098
2146
  kind?: 'allow' | 'deny';
2147
+ /**
2148
+ * **La condición de la tupla escrita no es la `not_expired` del paquete, o no se puede leer** (lote «antes de RC» ·
2149
+ * L11): el porqué, legible. Solo lo pone un driver que lee condiciones (`openfga`, `fgaTupleExpiry`): `foreign` (otra
2150
+ * condición o un contexto que no es exactamente `{ valid_until }`) o `unparseable`. Con esto puesto `expiresAt` es la
2151
+ * ÉPOCA (`new Date(0)`) y no es una caducidad real. **La época NO es conservadora para todo** (cierre-2 de L11 · ⚪ C3):
2152
+ * un lector que ignore este campo la lee VENCIDA, que es fail-CLOSED para un ALLOW (deja de conceder) pero fail-OPEN
2153
+ * para un DENY (`kind: 'deny'`) y para una membresía de `group` que TRANSPORTA un deny (deja de negar). Ningún valor de
2154
+ * `expiresAt` es seguro para los dos tipos de tupla (con `null` era al revés): quien lea `enumerateRelations` TIENE que
2155
+ * mirar `invalidCondition` antes que la fecha. Dentro del paquete se hace: `reconcileRelations` la clasifica en el PLAN, antes de mirar la fecha, como `invalid-condition` (origen:
2156
+ * no se migra; destino: presente e intocable, nunca se poda) y es deriva. Opcional (un driver de terceros anterior
2157
+ * sigue compilando; ausente = la condición se entendió).
2158
+ */
2159
+ invalidCondition?: string;
2099
2160
  }
2100
2161
  export interface RelationTuplePage {
2101
2162
  tuples: RelationTuple[];
@@ -2160,10 +2221,14 @@ export interface RelationsDriver {
2160
2221
  * llamante (eso es `decideMany`, lote D).
2161
2222
  */
2162
2223
  checkMany?(subject: RelSubject, relation: string, objects: RelObject[], partition: ScopeRef): Promise<boolean[]>;
2163
- /** Los objetos de tipo `objectType` sobre los que `subject` tiene `relation`. Directos + derivados, sin herencia abierta. */
2164
- listObjects(subject: RelSubject, relation: string, objectType: string, partition: ScopeRef, page?: RelationPage): Promise<RelationObjectsPage>;
2165
- /** Los sujetos DIRECTOS de `relation` sobre `object` (holders y usersets). Nunca la membresía transitiva (eso es `membersOf`). */
2166
- listSubjects(relation: string, object: RelObject, partition: ScopeRef, page?: RelationPage): Promise<RelationSubjectsPage>;
2224
+ /**
2225
+ * Los objetos de tipo `objectType` sobre los que `subject` tiene `relation`. Directos + derivados, sin herencia
2226
+ * abierta. **Sin página** (7.4): el conjunto completo o `truncated`; un quinto argumento distinto de `undefined` ⇒
2227
+ * 500 `E_AUTHZ_UNSUPPORTED` sin tocar el backend (lo mismo en `listSubjects`/`listDenies`/`membersOf`).
2228
+ */
2229
+ listObjects(subject: RelSubject, relation: string, objectType: string, partition: ScopeRef): Promise<RelationObjectsPage>;
2230
+ /** Los sujetos DIRECTOS de `relation` sobre `object` (holders y usersets). Nunca la membresía transitiva (eso es `membersOf`). Sin página (7.4). */
2231
+ listSubjects(relation: string, object: RelObject, partition: ScopeRef): Promise<RelationSubjectsPage>;
2167
2232
  /** Borra todas las tuplas cuyo OBJETO es `object` (F-05 sobre el TIPO, alpha.3) y demuestra cero, o lanza 500 `E_AUTHZ_PURGE_INCOMPLETE` (invariante 11). Si falla tras borrar parte, marcado con `markPartialWrite`. */
2168
2233
  purgeObject(object: RelObject, partition: ScopeRef, options?: RelationTransactionOptions): Promise<void>;
2169
2234
  /** Borra todas las tuplas cuyo SUJETO es `subject` (F-05 sobre el userset, alpha.3) y demuestra cero, o lanza 500. Si falla tras borrar parte, marcado con `markPartialWrite`. */
@@ -2174,7 +2239,7 @@ export interface RelationsDriver {
2174
2239
  * `listSubjects(member, group)`, que devuelve solo los hechos DIRECTOS. Solo
2175
2240
  * lo trae el driver con `membersOfNative: true`.
2176
2241
  */
2177
- membersOf?(object: RelObject, relation: string, partition: ScopeRef, page?: RelationPage): Promise<RelationSubjectsPage>;
2242
+ membersOf?(object: RelObject, relation: string, partition: ScopeRef): Promise<RelationSubjectsPage>;
2178
2243
  /** ORIGEN de `authz:reconcile` de relaciones: las tuplas paginadas, sin filtrar (la caducada LLEGA con su `expiresAt`). Solo con `enumerateRelations: true`. */
2179
2244
  enumerateRelations?(partition: ScopeRef, page?: RelationPage): Promise<RelationTuplePage>;
2180
2245
  /**
@@ -2200,8 +2265,8 @@ export interface RelationsDriver {
2200
2265
  deny?(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationDenyOptions): Promise<void>;
2201
2266
  /** Retira el deny. No-op seguro si no existe. Sobre CUALQUIER tipo declarado (D8: retirar el opt-in es reversible). */
2202
2267
  removeDeny?(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationDenyOptions): Promise<void>;
2203
- /** Los sujetos con deny DIRECTO de `relation` sobre `object` (hechos, como `listSubjects`; no resta nada). */
2204
- listDenies?(relation: string, object: RelObject, partition: ScopeRef, page?: RelationPage): Promise<RelationSubjectsPage>;
2268
+ /** Los sujetos con deny DIRECTO de `relation` sobre `object` (hechos, como `listSubjects`; no resta nada). Sin página (7.4). */
2269
+ listDenies?(relation: string, object: RelObject, partition: ScopeRef): Promise<RelationSubjectsPage>;
2205
2270
  /**
2206
2271
  * **La primitiva de CADENA** (D9): una decisión por objeto, POR POSICIÓN — `allow` si `check` concede; si no, `deny`
2207
2272
  * si el sujeto tiene un deny vivo (directo o por grupo) sobre alguna relación de `up*(down*(R))` en el objeto; si
@@ -2222,6 +2287,17 @@ export interface RelationsDriver {
2222
2287
  denyPhase?(object: {
2223
2288
  type: string;
2224
2289
  }, relation: string): Promise<'read' | 'write' | null>;
2290
+ /**
2291
+ * **El techo de anidación de grupos que ESTE driver aplica** (lote «antes de RC» · R1, decisión del dueño 2026-10-17
2292
+ * (5): «cada driver declara su techo»): saltos `group⊂group` del camino más largo que admite al escribir una arista
2293
+ * `group#member`, o `null` si no aplica ninguno. `openfga`: el CALIBRADO contra el servidor (el mínimo de un miembro y
2294
+ * de un no-miembro con membresía; solo lectura, tuplas contextuales, memoriza solo el éxito) o el `maxGroupDepth` de
2295
+ * la config si es menor; `database`: `maxGroupDepth` con `database.enforceGroupDepth`, si no `null`. Lo llama
2296
+ * `reconcileRelations` sobre el DESTINO en la fase de PLAN (también en `--dry-run`: es de solo lectura) para NEGAR la
2297
+ * partición cuya cadena final lo excede, nunca saltar la arista. Opcional: un driver de terceros sin él no tiene techo
2298
+ * para el plan. Lectura de configuración: no recibe tupla ni partición.
2299
+ */
2300
+ groupDepthLimit?(): Promise<number | null>;
2225
2301
  /**
2226
2302
  * Vista de este driver con OTRO reloj de pared (R-15, paridad con
2227
2303
  * `AuthorizationDriver.withClock`, 2.5 · J1): mismo backend, solo cambia el
@@ -2242,4 +2318,3 @@ export interface RelationsDriver {
2242
2318
  * factory (como el de roles), así que el comando nunca toca el SDK (pureza).
2243
2319
  */
2244
2320
  export type RelationsDriverFactory = () => RelationsDriver | Promise<RelationsDriver>;
2245
- //# sourceMappingURL=types.d.ts.map
@@ -57,6 +57,8 @@ export const AUTHORIZATION_DRIVER_CAPABILITY_KEYS = Object.freeze([
57
57
  'canonicalScopeReads',
58
58
  'enumerateFacts',
59
59
  'transactionalWrites',
60
+ // Estrategia 2.5.0 · firma 4 (D15): opcional, normalizada a `false`.
61
+ 'experimental',
60
62
  ]);
61
63
  /**
62
64
  * **La copia CONGELADA y NORMALIZADA que publica `authorization.capabilities()`**
@@ -100,6 +102,8 @@ export const RELATIONS_DRIVER_CAPABILITY_KEYS = Object.freeze([
100
102
  'transactionalWrites',
101
103
  'relationDenies',
102
104
  'atomicRenewal',
105
+ // Estrategia 2.5.0 · firma 4 (D15): opcional, normalizada a `false`.
106
+ 'experimental',
103
107
  ]);
104
108
  /** La copia congelada y normalizada de `relations.capabilities()`; misma regla que `normalizeAuthorizationCapabilities`. */
105
109
  export function normalizeRelationsCapabilities(declared) {
@@ -108,4 +112,3 @@ export function normalizeRelationsCapabilities(declared) {
108
112
  out[key] = declared?.[key] === true;
109
113
  return Object.freeze(out);
110
114
  }
111
- //# sourceMappingURL=types.js.map