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

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.
@@ -343,6 +343,38 @@ export interface AuthorizationDriverCapabilities {
343
343
  * apaga casos. Par de caras en `runAuthorizationDriverContract`.
344
344
  */
345
345
  experimental?: boolean;
346
+ /**
347
+ * **Toda DECISIÓN del driver que combina concesión y deny se lee en UNA foto de sus hechos** (rotación 2 · cierre 3;
348
+ * linaje L12 «lector rasgado»). **Opcional y normalizada a `false`** (regla D15). Con `true` el driver promete:
349
+ * `authorize` (y cada posición de `authorizeMany`) y `listScopes` leen sus concesiones y sus denies de UN mismo estado
350
+ * —una escritura que cae «entre» no puede dar una respuesta que ningún estado real daba—, y trae `readSnapshot` para
351
+ * que el manager lea en una foto lo que compone con dos métodos del puerto (`effectivePermissions`, `authorizedScopes`,
352
+ * la policy de `defineScopedRole`/`updateScopedRole`). Lo que NO promete: la cadena del consumidor (`resolveChain`) y
353
+ * el catálogo se leen fuera de la foto (README «Limits», fila «Torn read»: «cadena frente a hechos»). `database` =
354
+ * `true` (UNA sentencia, `readFoto`); el manager usa `readSnapshot` solo con esta capacidad en `true`; `openfga` no la declara (`false`: su `Check` lee ramas del modelo por separado,
355
+ * la excepción declarada del invariante 2) y el manager compone como antes. Par de caras en
356
+ * `runAuthorizationDriverContract`.
357
+ */
358
+ snapshotDecisions?: boolean;
359
+ }
360
+ /**
361
+ * Lo que `readSnapshot` lee en UNA foto (rotación 2 · cierre 3). Cada clave pedida es la pregunta del método homónimo
362
+ * del puerto para el mismo holder: `rolesInChain` con esa cadena (ya resuelta), `listScopes` con ese permiso y
363
+ * `listDenies: true` = `listDenies(subject)` sin scope. Al menos una.
364
+ */
365
+ export interface SnapshotReads {
366
+ rolesInChain?: ScopeRef[];
367
+ listScopes?: string;
368
+ listDenies?: boolean;
369
+ }
370
+ /** La respuesta de `readSnapshot`: EXACTAMENTE la del método homónimo para cada clave pedida, todas del mismo estado; lo no pedido no viene. */
371
+ export interface SnapshotAnswer {
372
+ rolesInChain?: Array<{
373
+ scope: ScopeRef;
374
+ role: CatalogRoleRef;
375
+ }>;
376
+ listScopes?: ScopeRef[];
377
+ listDenies?: DenyRef[];
346
378
  }
347
379
  /**
348
380
  * Las claves de `AuthorizationDriverCapabilities`, LITERALES (2.4.0-alpha.4):
@@ -619,6 +651,23 @@ export interface AuthorizationDriver {
619
651
  scope: ScopeRef;
620
652
  role: CatalogRoleRef;
621
653
  }>>;
654
+ /**
655
+ * **Varias lecturas del puerto en UNA foto de los hechos** (rotación 2 · cierre 3, L12). Opcional (con su capacidad
656
+ * `snapshotDecisions`, regla D15): cada clave pedida de `reads` se responde EXACTAMENTE como el método homónimo
657
+ * (`rolesInChain(subject, chain)`, `listScopes(subject, permission)`, `listDenies(subject)`), y todas desde el MISMO
658
+ * estado de concesiones y denies. Lo usa el manager para las decisiones que compone con dos métodos del puerto:
659
+ * `effectivePermissions` y la policy de delegación (`rolesInChain` + `listDenies`) y `authorizedScopes`
660
+ * (`listScopes` + `listDenies`). El manager lo llama SOLO si el driver declara `snapshotDecisions: true` (decide la
661
+ * capacidad, no la presencia del método; rotación 2 · cierre 3b): declarar `true` sin traerlo es 500 `E_AUTHZ_CONFIG`
662
+ * —en la PRIMERA decisión compuesta, no al resolver el driver (cierre 3c, J3), nombrando la clave y la clase del
663
+ * driver—, y sin la capacidad el manager compone como antes —dos lecturas sin foto común, el hueco DECLARADO de un driver sin
664
+ * la capacidad— aunque exista un método con este nombre.
665
+ *
666
+ * `reads` mal formado (vacío, una clave desconocida, una cadena que no es array, un permiso que no es string,
667
+ * `listDenies` que no es `true`) es 422 `E_AUTHZ_INVALID_IDENTITY`; un permiso fuera del catálogo responde
668
+ * `listScopes: []`, como el método suelto.
669
+ */
670
+ readSnapshot?(subject: SubjectRef, reads: SnapshotReads): Promise<SnapshotAnswer>;
622
671
  }
623
672
  /**
624
673
  * Un subárbol excluido de un `all` (2.1, B3; tipo nominal desde 2D · F10):
@@ -59,6 +59,8 @@ export const AUTHORIZATION_DRIVER_CAPABILITY_KEYS = Object.freeze([
59
59
  'transactionalWrites',
60
60
  // Estrategia 2.5.0 · firma 4 (D15): opcional, normalizada a `false`.
61
61
  'experimental',
62
+ // Rotación 2 · cierre 3 (D15, L12): opcional, normalizada a `false`; `database` declara `true`.
63
+ 'snapshotDecisions',
62
64
  ]);
63
65
  /**
64
66
  * **La copia CONGELADA y NORMALIZADA que publica `authorization.capabilities()`**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jantstack/adonis-authz",
3
- "version": "2.5.0-alpha.4",
3
+ "version": "2.5.0-rc.2",
4
4
  "description": "Driver-based authorization engine for AdonisJS + Lucid: hierarchical scopes with downward inheritance, explicit denies, expiring assignments and polymorphic holders. Ships a self-contained database driver and an OpenFGA driver, both judged by the same executable contract suite.",
5
5
  "type": "module",
6
6
  "license": "MIT",