@jantstack/adonis-authz 2.5.0-alpha.3 → 2.5.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +437 -48
  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 +17 -3
  5. package/build/commands/authz_catalog_prune_orphans.js +28 -6
  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 +54 -6
  16. package/build/commands/authz_scopes_requeue.d.ts +0 -1
  17. package/build/commands/authz_scopes_requeue.js +16 -3
  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 +9 -7
  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 +27 -2
  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 +60 -3
  43. package/build/src/drivers/database_driver.js +160 -25
  44. package/build/src/drivers/database_relations_driver.d.ts +11 -5
  45. package/build/src/drivers/database_relations_driver.js +115 -33
  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 +160 -1
  53. package/build/src/errors.js +161 -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 +9 -2
  65. package/build/src/identity.js +33 -1
  66. package/build/src/manager.d.ts +24 -1
  67. package/build/src/manager.js +381 -85
  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 +36 -3
  109. package/build/src/scope_outbox.js +78 -18
  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 +26 -3
  119. package/build/src/testing/contract.js +258 -20
  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 +138 -14
  139. package/build/src/types.js +6 -1
  140. package/build/stubs/config/authorization.stub +22 -6
  141. package/build/stubs/main.d.ts +0 -1
  142. package/build/stubs/main.js +0 -1
  143. package/package.json +4 -3
  144. package/build/commands/authz_catalog_diff.d.ts.map +0 -1
  145. package/build/commands/authz_catalog_diff.js.map +0 -1
  146. package/build/commands/authz_catalog_prune_orphans.d.ts.map +0 -1
  147. package/build/commands/authz_catalog_prune_orphans.js.map +0 -1
  148. package/build/commands/authz_catalog_sync.d.ts.map +0 -1
  149. package/build/commands/authz_catalog_sync.js.map +0 -1
  150. package/build/commands/authz_freeze.d.ts.map +0 -1
  151. package/build/commands/authz_freeze.js.map +0 -1
  152. package/build/commands/authz_reconcile.d.ts.map +0 -1
  153. package/build/commands/authz_reconcile.js.map +0 -1
  154. package/build/commands/authz_relations_reconcile.d.ts.map +0 -1
  155. package/build/commands/authz_relations_reconcile.js.map +0 -1
  156. package/build/commands/authz_scopes_relay.d.ts.map +0 -1
  157. package/build/commands/authz_scopes_relay.js.map +0 -1
  158. package/build/commands/authz_scopes_requeue.d.ts.map +0 -1
  159. package/build/commands/authz_scopes_requeue.js.map +0 -1
  160. package/build/commands/authz_unfreeze.d.ts.map +0 -1
  161. package/build/commands/authz_unfreeze.js.map +0 -1
  162. package/build/commands/main.d.ts.map +0 -1
  163. package/build/commands/main.js.map +0 -1
  164. package/build/commands/openfga_provision.d.ts.map +0 -1
  165. package/build/commands/openfga_provision.js.map +0 -1
  166. package/build/configure.d.ts.map +0 -1
  167. package/build/configure.js.map +0 -1
  168. package/build/index.d.ts.map +0 -1
  169. package/build/index.js.map +0 -1
  170. package/build/providers/authz_provider.d.ts.map +0 -1
  171. package/build/providers/authz_provider.js.map +0 -1
  172. package/build/services/main.d.ts.map +0 -1
  173. package/build/services/main.js.map +0 -1
  174. package/build/services/relations.d.ts.map +0 -1
  175. package/build/services/relations.js.map +0 -1
  176. package/build/src/catalog/catalog.d.ts.map +0 -1
  177. package/build/src/catalog/catalog.js.map +0 -1
  178. package/build/src/catalog/catalog_cache.d.ts.map +0 -1
  179. package/build/src/catalog/catalog_cache.js.map +0 -1
  180. package/build/src/clock.d.ts.map +0 -1
  181. package/build/src/clock.js.map +0 -1
  182. package/build/src/define_config.d.ts.map +0 -1
  183. package/build/src/define_config.js.map +0 -1
  184. package/build/src/drivers/database_driver.d.ts.map +0 -1
  185. package/build/src/drivers/database_driver.js.map +0 -1
  186. package/build/src/drivers/database_relations_driver.d.ts.map +0 -1
  187. package/build/src/drivers/database_relations_driver.js.map +0 -1
  188. package/build/src/drivers/openfga_driver.d.ts.map +0 -1
  189. package/build/src/drivers/openfga_driver.js.map +0 -1
  190. package/build/src/drivers/openfga_facts.d.ts.map +0 -1
  191. package/build/src/drivers/openfga_facts.js.map +0 -1
  192. package/build/src/drivers/openfga_relations_driver.d.ts.map +0 -1
  193. package/build/src/drivers/openfga_relations_driver.js.map +0 -1
  194. package/build/src/errors.d.ts.map +0 -1
  195. package/build/src/errors.js.map +0 -1
  196. package/build/src/expiry.d.ts.map +0 -1
  197. package/build/src/expiry.js.map +0 -1
  198. package/build/src/freeze.d.ts.map +0 -1
  199. package/build/src/freeze.js.map +0 -1
  200. package/build/src/hierarchical_resolver.d.ts.map +0 -1
  201. package/build/src/hierarchical_resolver.js.map +0 -1
  202. package/build/src/http/app_access_middleware.d.ts.map +0 -1
  203. package/build/src/http/app_access_middleware.js.map +0 -1
  204. package/build/src/http/resource_access_middleware.d.ts.map +0 -1
  205. package/build/src/http/resource_access_middleware.js.map +0 -1
  206. package/build/src/identity.d.ts.map +0 -1
  207. package/build/src/identity.js.map +0 -1
  208. package/build/src/manager.d.ts.map +0 -1
  209. package/build/src/manager.js.map +0 -1
  210. package/build/src/memoize_ancestors.d.ts.map +0 -1
  211. package/build/src/memoize_ancestors.js.map +0 -1
  212. package/build/src/models/authz_assignment.d.ts.map +0 -1
  213. package/build/src/models/authz_assignment.js.map +0 -1
  214. package/build/src/models/authz_deny.d.ts.map +0 -1
  215. package/build/src/models/authz_deny.js.map +0 -1
  216. package/build/src/models/authz_permission.d.ts.map +0 -1
  217. package/build/src/models/authz_permission.js.map +0 -1
  218. package/build/src/models/authz_role.d.ts.map +0 -1
  219. package/build/src/models/authz_role.js.map +0 -1
  220. package/build/src/models/authz_role_permission.d.ts.map +0 -1
  221. package/build/src/models/authz_role_permission.js.map +0 -1
  222. package/build/src/openfga.d.ts.map +0 -1
  223. package/build/src/openfga.js.map +0 -1
  224. package/build/src/reconcile.d.ts.map +0 -1
  225. package/build/src/reconcile.js.map +0 -1
  226. package/build/src/relation_partition_trigger.d.ts.map +0 -1
  227. package/build/src/relation_partition_trigger.js.map +0 -1
  228. package/build/src/relations/check_many.d.ts.map +0 -1
  229. package/build/src/relations/check_many.js.map +0 -1
  230. package/build/src/relations/define_relations_config.d.ts.map +0 -1
  231. package/build/src/relations/define_relations_config.js.map +0 -1
  232. package/build/src/relations/manager.d.ts.map +0 -1
  233. package/build/src/relations/manager.js.map +0 -1
  234. package/build/src/relations/reconcile.d.ts.map +0 -1
  235. package/build/src/relations/reconcile.js.map +0 -1
  236. package/build/src/relations/tuple_key.d.ts.map +0 -1
  237. package/build/src/relations/tuple_key.js.map +0 -1
  238. package/build/src/relations/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
@@ -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