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