@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.
- package/README.md +437 -48
- 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 +17 -3
- package/build/commands/authz_catalog_prune_orphans.js +28 -6
- 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 +0 -1
- package/build/commands/authz_relations_reconcile.js +56 -8
- package/build/commands/authz_scopes_relay.d.ts +0 -1
- package/build/commands/authz_scopes_relay.js +54 -6
- package/build/commands/authz_scopes_requeue.d.ts +0 -1
- package/build/commands/authz_scopes_requeue.js +16 -3
- 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 +10 -1
- package/build/commands/openfga_provision.js +43 -3
- package/build/configure.d.ts +0 -1
- package/build/configure.js +0 -1
- package/build/index.d.ts +9 -7
- package/build/index.js +10 -3
- 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 +27 -2
- 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 +60 -3
- package/build/src/drivers/database_driver.js +160 -25
- package/build/src/drivers/database_relations_driver.d.ts +11 -5
- package/build/src/drivers/database_relations_driver.js +115 -33
- package/build/src/drivers/openfga_driver.d.ts +159 -26
- package/build/src/drivers/openfga_driver.js +882 -199
- package/build/src/drivers/openfga_facts.d.ts +91 -1
- package/build/src/drivers/openfga_facts.js +277 -3
- package/build/src/drivers/openfga_relations_driver.d.ts +41 -13
- package/build/src/drivers/openfga_relations_driver.js +422 -228
- package/build/src/errors.d.ts +160 -1
- package/build/src/errors.js +161 -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 +9 -2
- package/build/src/identity.js +33 -1
- package/build/src/manager.d.ts +24 -1
- package/build/src/manager.js +381 -85
- 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 +2 -3
- package/build/src/openfga.js +4 -2
- 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 +0 -1
- package/build/src/relations/check_many.js +0 -1
- package/build/src/relations/define_relations_config.d.ts +25 -3
- package/build/src/relations/define_relations_config.js +8 -3
- 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 +14 -7
- package/build/src/relations/manager.js +60 -15
- package/build/src/relations/page.d.ts +19 -0
- package/build/src/relations/page.js +74 -0
- package/build/src/relations/reconcile.d.ts +58 -4
- package/build/src/relations/reconcile.js +266 -12
- 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 +5 -4
- package/build/src/relations/tuple_order.js +5 -4
- 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 +36 -3
- package/build/src/scope_outbox.js +78 -18
- package/build/src/shared/backend_guard.d.ts +52 -1
- package/build/src/shared/backend_guard.js +164 -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 +26 -3
- package/build/src/testing/contract.js +258 -20
- package/build/src/testing/main.d.ts +0 -1
- package/build/src/testing/main.js +0 -1
- 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 +7 -1
- package/build/src/testing/relations_contract.js +174 -7
- package/build/src/testing/relations_reconcile_contract.d.ts +0 -1
- package/build/src/testing/relations_reconcile_contract.js +0 -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 +138 -14
- 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/tuple_order.d.ts.map +0 -1
- package/build/src/relations/tuple_order.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
|
@@ -34,6 +34,11 @@ export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, s
|
|
|
34
34
|
level: 'log' | 'warning';
|
|
35
35
|
message: string;
|
|
36
36
|
}>;
|
|
37
|
+
/**
|
|
38
|
+
* La letra de los disparadores del seguro de purga masiva (3b-0b · AA2 + rotación 2 · R2-02), en el orden en que
|
|
39
|
+
* el manager los reporta. Pura, para que el caso la fije.
|
|
40
|
+
*/
|
|
41
|
+
export declare function massPurgeLetter(reasons: ReadonlyArray<string>): string;
|
|
37
42
|
/**
|
|
38
43
|
* Lista —y con `--force` borra— los roles LOCALES cuyo owner el árbol del
|
|
39
44
|
* consumidor YA NO conoce (3b-0 · Z2).
|
|
@@ -56,6 +61,7 @@ export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, s
|
|
|
56
61
|
* node ace authz:catalog:prune-orphans # --dry-run: solo lista
|
|
57
62
|
* node ace authz:catalog:prune-orphans --force # purga de verdad
|
|
58
63
|
* node ace authz:catalog:prune-orphans --force --allow-mass-purge
|
|
64
|
+
* node ace authz:catalog:prune-orphans --max-local-roles=50000 # catálogo local de más de 10 000 roles
|
|
59
65
|
*
|
|
60
66
|
* Es una operación de PLATAFORMA, no una escritura que dispare un tenant: no
|
|
61
67
|
* lleva actor ni mide rangos. Hasta 3G esta limpieza la arrastraba
|
|
@@ -63,8 +69,10 @@ export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, s
|
|
|
63
69
|
* resuelve—, y de ahí salieron tres de las cuatro regresiones de la Fase 3.
|
|
64
70
|
*
|
|
65
71
|
* `--allow-mass-purge` (3b-0b · AA2) desbloquea la cota que rechaza una
|
|
66
|
-
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
67
|
-
* más del 50 % de los roles locales
|
|
72
|
+
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
73
|
+
* más del 50 % de los roles locales, o —rotación 2 · R2-02— huérfanos de más
|
|
74
|
+
* de un owner con alguno que todavía concede): es una decisión humana, no un
|
|
75
|
+
* default. `--max-local-roles` (R2-04) sube la cota de la lectura.
|
|
68
76
|
*/
|
|
69
77
|
export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
70
78
|
static commandName: string;
|
|
@@ -72,7 +80,13 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
|
72
80
|
static options: CommandOptions;
|
|
73
81
|
force: boolean | undefined;
|
|
74
82
|
allowMassPurge: boolean | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Rotación 2 · R2-04: la cota de la lectura del catálogo local (default 10 000). Hasta este cierre el mensaje
|
|
85
|
+
* de `E_AUTHZ_TOO_MANY_LOCAL_ROLES` pedía «subir la cota» y no había mando: un tenant que pasara de 10 000 roles
|
|
86
|
+
* locales dejaba este comando muerto para todos. Un valor que no es un entero ≥ 1 lo rechaza el manager (500
|
|
87
|
+
* `E_AUTHZ_CONFIG`) antes de leer nada.
|
|
88
|
+
*/
|
|
89
|
+
maxLocalRoles: number | undefined;
|
|
75
90
|
run(): Promise<void>;
|
|
76
91
|
}
|
|
77
92
|
export {};
|
|
78
|
-
//# sourceMappingURL=authz_catalog_prune_orphans.d.ts.map
|
|
@@ -54,6 +54,18 @@ export function orphanLines(orphans, purged, skipped) {
|
|
|
54
54
|
}
|
|
55
55
|
return lines;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* La letra de los disparadores del seguro de purga masiva (3b-0b · AA2 + rotación 2 · R2-02), en el orden en que
|
|
59
|
+
* el manager los reporta. Pura, para que el caso la fije.
|
|
60
|
+
*/
|
|
61
|
+
export function massPurgeLetter(reasons) {
|
|
62
|
+
const words = {
|
|
63
|
+
'all-owners': 'todos los owners salen huérfanos',
|
|
64
|
+
'proportion': 'más del 50 % de los roles locales',
|
|
65
|
+
'granting-across-owners': 'huérfanos de más de un owner y alguno TODAVÍA concede o no se sabe',
|
|
66
|
+
};
|
|
67
|
+
return reasons.map((reason) => words[reason] ?? reason).join('; ') || 'sin motivo reportado';
|
|
68
|
+
}
|
|
57
69
|
/**
|
|
58
70
|
* Lista —y con `--force` borra— los roles LOCALES cuyo owner el árbol del
|
|
59
71
|
* consumidor YA NO conoce (3b-0 · Z2).
|
|
@@ -76,6 +88,7 @@ export function orphanLines(orphans, purged, skipped) {
|
|
|
76
88
|
* node ace authz:catalog:prune-orphans # --dry-run: solo lista
|
|
77
89
|
* node ace authz:catalog:prune-orphans --force # purga de verdad
|
|
78
90
|
* node ace authz:catalog:prune-orphans --force --allow-mass-purge
|
|
91
|
+
* node ace authz:catalog:prune-orphans --max-local-roles=50000 # catálogo local de más de 10 000 roles
|
|
79
92
|
*
|
|
80
93
|
* Es una operación de PLATAFORMA, no una escritura que dispare un tenant: no
|
|
81
94
|
* lleva actor ni mide rangos. Hasta 3G esta limpieza la arrastraba
|
|
@@ -83,8 +96,10 @@ export function orphanLines(orphans, purged, skipped) {
|
|
|
83
96
|
* resuelve—, y de ahí salieron tres de las cuatro regresiones de la Fase 3.
|
|
84
97
|
*
|
|
85
98
|
* `--allow-mass-purge` (3b-0b · AA2) desbloquea la cota que rechaza una
|
|
86
|
-
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
87
|
-
* más del 50 % de los roles locales
|
|
99
|
+
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
100
|
+
* más del 50 % de los roles locales, o —rotación 2 · R2-02— huérfanos de más
|
|
101
|
+
* de un owner con alguno que todavía concede): es una decisión humana, no un
|
|
102
|
+
* default. `--max-local-roles` (R2-04) sube la cota de la lectura.
|
|
88
103
|
*/
|
|
89
104
|
export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
90
105
|
static commandName = 'authz:catalog:prune-orphans';
|
|
@@ -94,9 +109,10 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
|
94
109
|
};
|
|
95
110
|
async run() {
|
|
96
111
|
const { default: authorization } = await import('../services/main.js');
|
|
97
|
-
const { orphans, purged, skipped, massPurge, dryRun } = await authorization.pruneOrphanRoles({
|
|
112
|
+
const { orphans, purged, skipped, massPurge, massPurgeReasons, dryRun } = await authorization.pruneOrphanRoles({
|
|
98
113
|
force: this.force === true,
|
|
99
114
|
allowMassPurge: this.allowMassPurge === true,
|
|
115
|
+
...(this.maxLocalRoles !== undefined ? { maxLocalRoles: this.maxLocalRoles } : {}),
|
|
100
116
|
});
|
|
101
117
|
// Los que todavía conceden —y los que NADIE puede demostrar que no
|
|
102
118
|
// concedan— van aparte, con aviso: purgarlos es revocar permisos vivos,
|
|
@@ -110,7 +126,8 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
|
110
126
|
}
|
|
111
127
|
if (dryRun) {
|
|
112
128
|
if (massPurge) {
|
|
113
|
-
this.logger.warning(`${orphans.length} de los roles locales quedarían huérfanos: eso es la firma de un 'scopes.resolveChain' ciego
|
|
129
|
+
this.logger.warning(`${orphans.length} de los roles locales quedarían huérfanos: eso es la firma de un 'scopes.resolveChain' ciego ` +
|
|
130
|
+
`(${massPurgeLetter(massPurgeReasons ?? [])}). ` +
|
|
114
131
|
`Con --force esto se rechaza (E_AUTHZ_MASS_PURGE_REFUSED); comprueba el resolutor antes de añadir --allow-mass-purge.`);
|
|
115
132
|
}
|
|
116
133
|
this.logger.warning(`${orphans.length} rol(es) local(es) con el owner fuera del árbol. No se ha borrado nada: repite con --force.`);
|
|
@@ -128,8 +145,13 @@ __decorate([
|
|
|
128
145
|
], AuthzCatalogPruneOrphans.prototype, "force", void 0);
|
|
129
146
|
__decorate([
|
|
130
147
|
flags.boolean({
|
|
131
|
-
description: 'Allow a pass that would purge every owner
|
|
148
|
+
description: 'Allow a pass that would purge every owner, more than half the local roles, or orphans of several owners that still grant (or cannot be counted) — check your resolveChain first',
|
|
132
149
|
}),
|
|
133
150
|
__metadata("design:type", Object)
|
|
134
151
|
], AuthzCatalogPruneOrphans.prototype, "allowMassPurge", void 0);
|
|
135
|
-
|
|
152
|
+
__decorate([
|
|
153
|
+
flags.number({
|
|
154
|
+
description: 'Cap on the local roles this pass reads (default 10000); above it, 500 E_AUTHZ_TOO_MANY_LOCAL_ROLES, never a partial list',
|
|
155
|
+
}),
|
|
156
|
+
__metadata("design:type", Object)
|
|
157
|
+
], AuthzCatalogPruneOrphans.prototype, "maxLocalRoles", void 0);
|
|
@@ -9,6 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
11
11
|
import { RELATION_USERSET_RESIDUE_DELETE } from '../src/relations/define_relations_config.js';
|
|
12
|
+
import { denyReachLine } from '../src/relations/deny_reach.js';
|
|
12
13
|
import { RelationConfigError } from '../src/errors.js';
|
|
13
14
|
// Plan 2.5 · lote B · B7: la vía pública vive en su subpath (`src/relations_reconcile.ts`,
|
|
14
15
|
// `@jantstack/adonis-authz/relations/reconcile`); este módulo es el COMANDO y la importa de allí.
|
|
@@ -84,6 +85,34 @@ export function relationsReconcileLines(report) {
|
|
|
84
85
|
`el deny concedería lo que el origen niega. Receta: ${refusal.recipe}`,
|
|
85
86
|
});
|
|
86
87
|
}
|
|
88
|
+
// Lote RC · sublote (8): el DETECTOR del alcance de los denies (solo si la pasada lo pidió: `--dry-run`). Una línea por
|
|
89
|
+
// hallazgo con su prefijo ESTABLE (punto 7) y su receta: «deny con alcance ENCOGIDO» / «deny HUÉRFANO» (persistida →
|
|
90
|
+
// servida) y «línea base RANCIA» (servida → persistida, firma (8 · rancia)) son deriva (exit ≠ 0). Sin línea base y con
|
|
91
|
+
// denies vivos sin juzgar, la línea «alcance de denies: SIN línea base y N deny(s) vivos sin juzgar» y exit 0 (firma
|
|
92
|
+
// (8 · `--dry-run`), g2: el runbook no persiste la config y no cambia). Sin nada de eso, UNA línea que dice qué se comparó.
|
|
93
|
+
if (report.denyReach !== undefined) {
|
|
94
|
+
const { baseline, findings, stale, unjudged } = report.denyReach;
|
|
95
|
+
for (const finding of [...findings, ...stale]) {
|
|
96
|
+
lines.push({ level: 'error', message: `${denyReachLine(finding)} (lado: ${finding.sides.join(', ')})` });
|
|
97
|
+
}
|
|
98
|
+
if (baseline === 'none' && unjudged > 0) {
|
|
99
|
+
lines.push({
|
|
100
|
+
level: 'warning',
|
|
101
|
+
message: `alcance de denies: SIN línea base y ${unjudged} deny(s) vivos sin juzgar (no hay config persistida en authz_relations_config): ` +
|
|
102
|
+
`solo se buscaron huérfanos, y un estrechamiento de su alcance NO se ve sin línea base. Si usas deny, persistir la config es ` +
|
|
103
|
+
`paso obligatorio de release: adóptala una vez con saveRelationsConfig(spec, { establishBaseline: true }) y vigila con ` +
|
|
104
|
+
`checkRelationsConfigDenyReach (el detector de un solo driver).`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else if (findings.length === 0 && stale.length === 0) {
|
|
108
|
+
lines.push({
|
|
109
|
+
level: 'log',
|
|
110
|
+
message: baseline === 'persisted'
|
|
111
|
+
? 'alcance de denies: ningún deny vivo de la partición encogió ni quedó huérfano entre la config persistida (línea base) y la que sirve este proceso, y la línea base no es más estrecha que lo servido.'
|
|
112
|
+
: 'alcance de denies: SIN línea base (no hay config persistida en authz_relations_config) y ningún deny vivo en la partición que juzgar.',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
87
116
|
// R-15: la caducada del origen es la pérdida DECLARADA de la migración (como
|
|
88
117
|
// `expired` en roles, 3b-8 · B2): se dice, no es deriva.
|
|
89
118
|
if (report.skipped.expired > 0) {
|
|
@@ -137,7 +166,9 @@ export function relationsReconcileLines(report) {
|
|
|
137
166
|
level: 'error',
|
|
138
167
|
message: detail.reason === 'userset-unsupported'
|
|
139
168
|
? `sin migrar ${what}. Es un residuo pre-2.5 (B3) del store de ORIGEN: el destino no lo admite por ninguna puerta y ningún --prune lo toca. Bórrala a mano (los tres motores): ${RELATION_USERSET_RESIDUE_DELETE} y repite la pasada.`
|
|
140
|
-
:
|
|
169
|
+
: detail.reason === 'invalid-condition'
|
|
170
|
+
? `sin migrar ${what}. Es una tupla cuya condición el paquete no escribe ni sabe leer (L11): no se migra como permanente ni como caducada. Revísala y corrígela o bórrala a mano (SDK/CLI de OpenFGA) y repite la pasada.`
|
|
171
|
+
: `sin migrar ${what}. Es un dato del store de ORIGEN, no una pregunta: corrígela o bórrala a mano (SQL/SDK) y repite la pasada.`,
|
|
141
172
|
});
|
|
142
173
|
continue;
|
|
143
174
|
}
|
|
@@ -164,11 +195,13 @@ export function relationsReconcileLines(report) {
|
|
|
164
195
|
level: 'error',
|
|
165
196
|
message: detail.reason === 'userset-unsupported'
|
|
166
197
|
? `sobra ${what}. Es un residuo pre-2.5 (B3) del DESTINO: --prune ${verdict} lo borra${detail.pruned === false ? '' : 'ría'} (nunca un DELETE de plataforma sobre una fila que la API no admite). Bórrala a mano (los tres motores): ${RELATION_USERSET_RESIDUE_DELETE} y repite la pasada.`
|
|
167
|
-
: detail.reason === '
|
|
168
|
-
? `sobra ${what}. Es
|
|
169
|
-
: detail.reason === '
|
|
170
|
-
? `sobra ${what}. Es
|
|
171
|
-
:
|
|
198
|
+
: detail.reason === 'invalid-condition'
|
|
199
|
+
? `sobra ${what}. Es una tupla del DESTINO con una condición que el paquete no escribe ni sabe leer (L11): presente e intocable —--prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'}, y su clave no se reescribe desde el origen—. Revísala y bórrala a mano (SDK/CLI de OpenFGA) y repite la pasada.`
|
|
200
|
+
: detail.reason === 'deny-conditional'
|
|
201
|
+
? `sobra ${what}. Es un deny CONDICIONAL del DESTINO (con caducidad, escrito fuera del paquete) que el driver destino no puede rehacer permanente (openfga no reemplaza la misma clave en un Write, M5): niega solo mientras viva (si ya venció, no niega nada) y su partición queda NEGADA (línea «partición NEGADA»: cero escrituras en ella).`
|
|
202
|
+
: detail.reason === 'undeclared'
|
|
203
|
+
? `sobra ${what}. Es una fila del DESTINO cuya relación (o tipo) el destino YA NO declara (F-05): --prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'} (unrelate la rechaza por F-05 y la pasada no insiste: nunca un DELETE de plataforma). Sigue ahí: retírala a mano (SQL/SDK) o vuelve a declarar la relación en el destino (y re-guarda su config persistida) y repite la pasada.`
|
|
204
|
+
: `sobra ${what}. Es un dato del DESTINO fuera de la gramática: --prune ${verdict} la borra${detail.pruned === false ? '' : 'ría'} (ningún driver del paquete la admite en unrelate, R-16, y la pasada no se la pide a nadie: nunca un DELETE de plataforma). Sigue ahí: corrígela o bórrala a mano (SQL/SDK) y repite la pasada.`,
|
|
172
205
|
});
|
|
173
206
|
}
|
|
174
207
|
// Cierre 5 de D2 (decisión del dueño §2026-10-16 (10), opción A): la PARTICIÓN NEGADA — ≥ 1 `renewal-unsafe` o
|
|
@@ -182,7 +215,7 @@ export function relationsReconcileLines(report) {
|
|
|
182
215
|
const later = evolves.length > 0 ? ` OJO, ese estado EVOLUCIONA: ${evolves.slice(0, 5).join('; ')}${evolves.length > 5 ? '; …' : ''}.` : '';
|
|
183
216
|
lines.push({
|
|
184
217
|
level: 'error',
|
|
185
|
-
message: `partición NEGADA ${refusal.partition}: ${causes.length} causa(s) que
|
|
218
|
+
message: `partición NEGADA ${refusal.partition}: ${causes.length} causa(s) por las que la pasada no puede escribir en ella sin conceder de más (una ventana entre dos Write, una tupla que el paquete no sabe leer, o una cadena de grupos anidados más larga que el techo del destino) — ` +
|
|
186
219
|
`${causes.slice(0, 5).join('; ')}${causes.length > 5 ? '; …' : ''}. Cero escrituras y cero borrados en esta partición ` +
|
|
187
220
|
`(también en la pasada real${report.prune === true ? ' y con --prune' : ''}): su destino queda COMO ESTABA, con la deriva ` +
|
|
188
221
|
`previa a la vista (por escribir ${refusal.pending.write}, por renovar ${refusal.pending.update}, por borrar ` +
|
|
@@ -287,6 +320,8 @@ export function relationsReconcileLines(report) {
|
|
|
287
320
|
pendingRenewals === 0 &&
|
|
288
321
|
(report.partitionsRefused?.length ?? 0) === 0 &&
|
|
289
322
|
(report.denyRefused?.length ?? 0) === 0 &&
|
|
323
|
+
(report.denyReach?.findings.length ?? 0) === 0 &&
|
|
324
|
+
(report.denyReach?.stale.length ?? 0) === 0 &&
|
|
290
325
|
!(report.massDelete && !massDeleteLifted) &&
|
|
291
326
|
!(report.frozen?.lapsed === true) &&
|
|
292
327
|
(!report.dryRun || cambios === 0);
|
|
@@ -461,6 +496,19 @@ export default class AuthzRelationsReconcile extends BaseCommand {
|
|
|
461
496
|
else {
|
|
462
497
|
this.logger.info(`Config del destino: leída de authz_relations_config (${toConfig.objectTypes.length} tipo(s)): la deriva del modelo se vigila.`);
|
|
463
498
|
}
|
|
499
|
+
// Lote RC · sublote (8): el DETECTOR del alcance de los denies, solo en el verificador (`--dry-run`): línea base = la
|
|
500
|
+
// config persistida (`toConfig`), config nueva = la que SIRVE este proceso (`relations.config`). Sin `relations.config`
|
|
501
|
+
// en el proceso no hay con qué comparar: se dice (punto 7) y la pasada sigue.
|
|
502
|
+
let denyReach;
|
|
503
|
+
if (this.dryRun === true) {
|
|
504
|
+
const current = this.app.config?.get('authorization')?.relations?.config;
|
|
505
|
+
if (current && Array.isArray(current.objectTypes)) {
|
|
506
|
+
denyReach = { baseline: toConfig, current };
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
this.logger.info('alcance de denies: NO se vigila en esta pasada (este proceso no tiene relations.config en config/authorization.ts con el que comparar).');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
464
512
|
// L-1 · J1: la pasada que ESCRIBE corre bajo la ventana durable del
|
|
465
513
|
// manager de roles (la misma fila `id = 2`, toda la flota); `--dry-run` no.
|
|
466
514
|
// Los drivers los construye `runRelationsReconcile`, DESPUÉS del guard.
|
|
@@ -475,6 +523,7 @@ export default class AuthzRelationsReconcile extends BaseCommand {
|
|
|
475
523
|
toKey: this.to,
|
|
476
524
|
fromKey,
|
|
477
525
|
...(toConfig ? { toConfig } : {}),
|
|
526
|
+
...(denyReach ? { denyReach } : {}),
|
|
478
527
|
});
|
|
479
528
|
}
|
|
480
529
|
catch (error) {
|
|
@@ -583,4 +632,3 @@ __decorate([
|
|
|
583
632
|
flags.string({ description: 'Partition (tenant) scope uuid; required with --partition-type' }),
|
|
584
633
|
__metadata("design:type", Object)
|
|
585
634
|
], AuthzRelationsReconcile.prototype, "partitionUuid", void 0);
|
|
586
|
-
//# sourceMappingURL=authz_relations_reconcile.js.map
|
|
@@ -207,7 +207,11 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
207
207
|
'Revísala en la cola (el consumidor puede volver a notificar el cambio) o reconcilia.');
|
|
208
208
|
}
|
|
209
209
|
if (report.busy) {
|
|
210
|
-
|
|
210
|
+
// Rotación 2 · R2-01b: la rama sigue saliendo 0 —respuesta del dueño en el cierre 2: la línea y `--max-age` son el
|
|
211
|
+
// detector; el caso fija exit 0 exacto (T-05)—, y la línea dice lo que puede ser.
|
|
212
|
+
this.logger.info('Otra pasada del relay tiene el lease de la cola: esta no ha aplicado nada (el relay es escritor único). ' +
|
|
213
|
+
'En MySQL, durante 2.5.x, el lease puede tenerlo OTRO despliegue del mismo servidor (el nombre de transición no ' +
|
|
214
|
+
'lleva la base; se retira en 2.6): si se repite en cada pasada, la cola no avanza — vigílala con --max-age.');
|
|
211
215
|
if (report.dead.length > 0)
|
|
212
216
|
this.exitCode = 1;
|
|
213
217
|
return;
|
|
@@ -252,8 +256,23 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
252
256
|
// Qué fue de cada entrada, una a una (`entries`): la pasada no es atómica
|
|
253
257
|
// y un contador no permite retomar nada. Lo aparcado ya se dijo arriba.
|
|
254
258
|
for (const entry of report.entries) {
|
|
255
|
-
if (entry.status === 'applied')
|
|
256
|
-
|
|
259
|
+
if (entry.status === 'applied') {
|
|
260
|
+
// Asentar · cierre 1 (auditor ⚪ 8): la auditoría dice el padre APLICADO, no solo el encolado.
|
|
261
|
+
if (entry.reason === 'superseded') {
|
|
262
|
+
this.logger.warning(`aplicado SIN arista (superseded: el nodo ya no existe y su detached está en la cola): ${linea(entry)}`);
|
|
263
|
+
}
|
|
264
|
+
else if (entry.reason === 'consumer-parent' && entry.appliedParent) {
|
|
265
|
+
// Asentar · cierre 2 (C1-1 (b), firma del dueño 2026-10-21; O-1 del re-ataque): exit ≠ 0. La carrera S1 y una
|
|
266
|
+
// caché rancia con dos movimientos del nodo dentro de su TTL son INDISTINGUIBLES para el relay, y un cron no lee
|
|
267
|
+
// avisos: la carrera hace saltar la alarma una vez; la caché, cada vez que pase.
|
|
268
|
+
this.logger.warning(`aplicado con el padre del CONSUMIDOR ${entry.appliedParent.type}:${entry.appliedParent.uuid ?? ''} ` +
|
|
269
|
+
`(no el encolado; otra entrada del nodo lo respalda): ${linea(entry)} — sale ≠ 0: comprueba que el resolutor ` +
|
|
270
|
+
'del relay lee lo CONFIRMADO, sin caché ni réplica atrasada (authz:reconcile --dry-run lo contrasta)');
|
|
271
|
+
this.exitCode = 1;
|
|
272
|
+
}
|
|
273
|
+
else
|
|
274
|
+
this.logger.log(`aplicado: ${linea(entry)}`);
|
|
275
|
+
}
|
|
257
276
|
if (entry.status === 'deferred') {
|
|
258
277
|
const motivo = entry.reason?.startsWith('parked:')
|
|
259
278
|
? `aplazado por APARCADO (${entry.reason}): no se aplica hasta que alguien mire esa entrada`
|
|
@@ -267,8 +286,38 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
267
286
|
notEvaluatedLines();
|
|
268
287
|
if (report.failures.length > 0) {
|
|
269
288
|
for (const item of report.failures) {
|
|
270
|
-
|
|
271
|
-
|
|
289
|
+
const target = `#${item.id} (${item.change.op} ${item.change.child.type}:${item.change.child.uuid ?? ''})`;
|
|
290
|
+
// Cierre-2 de L11 (⚪ C2; punto 7): el fallo de DESPLIEGUE tiene su prefijo ESTABLE —el backend SÍ respondió, lo
|
|
291
|
+
// que no cuadra es el despliegue con la config—; «el backend no respondió» queda para el 503 de verdad. Desde R3
|
|
292
|
+
// la marca la ponen también el store/modelo inexistente, las credenciales (401/403) y el 4xx desconocido.
|
|
293
|
+
if (item.deployment === true) {
|
|
294
|
+
this.logger.error(`fallo de DESPLIEGUE ${target}: ${item.error} — el despliegue no es el que espera la config (modelo fijado, ` +
|
|
295
|
+
'store, credenciales o un 4xx del servidor): este fallo NO consume intento (corrige el despliegue —' +
|
|
296
|
+
'openfga:provision publica el modelo de la config— y repite)');
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
// Cierre de R3 (⚪ R3-A7; punto 7): la carrera y el límite de resolución son transitorios, pero el backend SÍ
|
|
300
|
+
// respondió — cada uno con su prefijo ESTABLE.
|
|
301
|
+
if (item.transient && item.code === 'E_AUTHZ_WRITE_CONFLICT') {
|
|
302
|
+
this.logger.error(`CARRERA en ${target}: ${item.error} — otro escritor (p. ej. un grant concurrente) tocó las mismas tuplas: ` +
|
|
303
|
+
'este fallo NO consume intento (la pasada siguiente relee y re-aplica)');
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (item.transient && item.code === 'E_AUTHZ_RESOLUTION_LIMIT') {
|
|
307
|
+
this.logger.error(`límite de RESOLUCIÓN en ${target}: ${item.error} — el backend respondió que el grafo excede lo que ` +
|
|
308
|
+
'resuelve (ver «Limits» en el README): este fallo NO consume intento');
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
// Lote «antes de RC» · F4 (⚪ RC-2 del auditor de R3; punto 7): un deadline vencido —el timeout del cliente, o un
|
|
312
|
+
// 504 / `deadline_exceeded` de una ESCRITURA, que es indeterminada desde el cierre de R3— no es «el backend no
|
|
313
|
+
// respondió»: con un 504 SÍ respondió (o un proxy), y la escritura pudo aplicarse. Prefijo ESTABLE propio.
|
|
314
|
+
if (item.transient && item.code === 'E_AUTHZ_BACKEND_TIMEOUT') {
|
|
315
|
+
this.logger.error(`PLAZO VENCIDO en ${target}: ${item.error} — venció el deadline (timeout del cliente, o un 504/deadline_exceeded ` +
|
|
316
|
+
'del servidor o de un proxy): si era una escritura pudo aplicarse o no (INDETERMINADA); la pasada siguiente ' +
|
|
317
|
+
'relee el store y re-aplica: este fallo NO consume intento');
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
this.logger.error(`falló ${target}: ${item.error}` +
|
|
272
321
|
(item.transient ? ' — el backend no respondió: este fallo NO consume intento' : ''));
|
|
273
322
|
}
|
|
274
323
|
this.logger.warning('Lo que DEPENDE de un cambio fallido no se adelanta (el orden del árbol importa: aparece como «aplazado»), ' +
|
|
@@ -341,4 +390,3 @@ __decorate([
|
|
|
341
390
|
}),
|
|
342
391
|
__metadata("design:type", Object)
|
|
343
392
|
], AuthzScopesRelay.prototype, "maxAge", void 0);
|
|
344
|
-
//# sourceMappingURL=authz_scopes_relay.js.map
|
|
@@ -69,7 +69,8 @@ export function requeueLines(input) {
|
|
|
69
69
|
level: 'error',
|
|
70
70
|
message: 'Otra pasada del relay tiene el lease de la cola: con --apply no se ha reencolado NADA (todo o nada: reencolar y no ' +
|
|
71
71
|
'aplicar dejaría la entrada con un solo intento, que la siguiente pasada gastaría). Reintenta (--wait-lease-ms espera ' +
|
|
72
|
-
'a que la suelten), o reencola sin --apply y deja que la próxima pasada la aplique.'
|
|
72
|
+
'a que la suelten), o reencola sin --apply y deja que la próxima pasada la aplique. En MySQL, durante 2.5.x, el ' +
|
|
73
|
+
'lease puede tenerlo OTRO despliegue del mismo servidor (el nombre de transición no lleva la base; se retira en 2.6).',
|
|
73
74
|
});
|
|
74
75
|
return { lines, clean: false };
|
|
75
76
|
}
|
|
@@ -90,7 +91,20 @@ export function requeueLines(input) {
|
|
|
90
91
|
const relay = report.relay;
|
|
91
92
|
let clean = true;
|
|
92
93
|
for (const entry of relay.entries) {
|
|
93
|
-
|
|
94
|
+
// Asentar · cierre 2 (gemelo del relay, C1-1): el padre APLICADO cuando no es el encolado, con su línea propia. No
|
|
95
|
+
// cambia `clean`: aquí hay un operador delante, y es justo la salida de una aparcada por DRIFT (re-notificar el
|
|
96
|
+
// padre actual y reencolar) la que termina en esta línea.
|
|
97
|
+
if (entry.status === 'applied' && entry.reason === 'consumer-parent' && entry.appliedParent) {
|
|
98
|
+
lines.push({
|
|
99
|
+
level: 'warning',
|
|
100
|
+
message: `aplicada con el padre del CONSUMIDOR ${entry.appliedParent.type}:${entry.appliedParent.uuid ?? ''} ` +
|
|
101
|
+
`(no el encolado; otra entrada del nodo lo respalda): ${linea(entry)}`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else if (entry.status === 'applied' && entry.reason === 'superseded') {
|
|
105
|
+
lines.push({ level: 'warning', message: `aplicada SIN arista (superseded: el nodo ya no existe y su detached está en la cola): ${linea(entry)}` });
|
|
106
|
+
}
|
|
107
|
+
else if (entry.status === 'applied')
|
|
94
108
|
lines.push({ level: 'log', message: `aplicada: ${linea(entry)}` });
|
|
95
109
|
else if (entry.status === 'deferred') {
|
|
96
110
|
clean = false;
|
|
@@ -227,4 +241,3 @@ __decorate([
|
|
|
227
241
|
}),
|
|
228
242
|
__metadata("design:type", Object)
|
|
229
243
|
], AuthzScopesRequeue.prototype, "waitLeaseMs", void 0);
|
|
230
|
-
//# sourceMappingURL=authz_scopes_requeue.js.map
|
package/build/commands/main.d.ts
CHANGED
|
@@ -14,4 +14,3 @@ export { OpenFgaProvision, AuthzCatalogDiff, AuthzCatalogSync, AuthzCatalogPrune
|
|
|
14
14
|
export declare function getMetaData(): Promise<CommandMetaData[]>;
|
|
15
15
|
/** Contrato de loader de ace (`LoadersContract.getCommand`): la clase, o `null` si el nombre no es nuestro. */
|
|
16
16
|
export declare function getCommand(metaData: CommandMetaData): Promise<any>;
|
|
17
|
-
//# sourceMappingURL=main.d.ts.map
|
package/build/commands/main.js
CHANGED
|
@@ -83,6 +83,16 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
83
83
|
*/
|
|
84
84
|
prepare(): Promise<void>;
|
|
85
85
|
run(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* **La RELECTURA de lo publicado** (lote «antes de RC» · L11, diseño 1.3 D del analista; condición de aceptación C12 del
|
|
88
|
+
* tester): tras `writeAuthorizationModel` se LEE el modelo por su id y se compara ENTERO (`slice: 'all'`) con el que
|
|
89
|
+
* genera la config. Ancla el canonicalizador de L11 contra el servidor REAL en cada publicación: un servidor que
|
|
90
|
+
* serialice distinto (otra versión, un campo nuevo) se descubre en el despliegue, no con un 500 en la primera petición
|
|
91
|
+
* de producción. Si la relectura falla o difiere: exit 1, prefijo ESTABLE «provision RELECTURA FALLIDA:» (punto 7) y la
|
|
92
|
+
* letra que no miente (H11 c): el modelo YA está publicado en el store, y NO hay que fijarlo — por eso la línea
|
|
93
|
+
* `OPENFGA_MODEL_ID=` NO se imprime. Devuelve si se puede fijar.
|
|
94
|
+
*/
|
|
95
|
+
private reread;
|
|
86
96
|
/**
|
|
87
97
|
* **La guarda contra la MUTILACIÓN del modelo** (plan 2.5 · lote D · D2; analista §4.6, juez ronda 2 · R2-4 c y H3;
|
|
88
98
|
* cierre 1 de D2 · 🟡 3 y 🟠 2). `openfga:provision --store-id` publica desde la config del PROCESO: si ese proceso ya
|
|
@@ -174,4 +184,3 @@ export declare function countLiveDenies(client: any, dropped: string[]): Promise
|
|
|
174
184
|
count: number;
|
|
175
185
|
read: number;
|
|
176
186
|
}>;
|
|
177
|
-
//# sourceMappingURL=openfga_provision.d.ts.map
|
|
@@ -105,7 +105,7 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
105
105
|
}
|
|
106
106
|
const { OpenFgaClient } = await import('@openfga/sdk');
|
|
107
107
|
// El subpath `/openfga` es la única puerta al SDK (D9).
|
|
108
|
-
const { openFgaFactsModel, provisionOpenFgaStore, assertFactsModelPublishable } = await import('../src/openfga.js');
|
|
108
|
+
const { openFgaFactsModel, provisionOpenFgaStore, assertFactsModelPublishable, factsModelDifferences } = await import('../src/openfga.js');
|
|
109
109
|
// Los holders del consumidor viven en SU config, no en el paquete.
|
|
110
110
|
const holderTypes = config?.holderTypes ?? {};
|
|
111
111
|
// Los PERMISOS del modelo (c2r) salen de los catálogos del config. Se
|
|
@@ -130,7 +130,8 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
130
130
|
// esto salía como error crudo del SDK con una request ya hecha (⚪ N1
|
|
131
131
|
// del auditor de alpha.4). Cero requests si no es publicable.
|
|
132
132
|
assertFactsModelPublishable(holderTypes, permissions, undefined, relations);
|
|
133
|
-
|
|
133
|
+
// Injerto 15 del juez (H12): sin reintentos a escondidas (desde el SDK 0.9.6 un cliente sin `retryParams` reintenta).
|
|
134
|
+
const client = new OpenFgaClient({ apiUrl, storeId: this.storeId, retryParams: { maxRetry: 0 } });
|
|
134
135
|
const next = openFgaFactsModel(holderTypes, permissions, relations);
|
|
135
136
|
// Lote D · D2: la guarda contra la MUTILACIÓN del modelo (R2-4 c / H3) — ANTES de escribir nada. Cierre 1 de D2:
|
|
136
137
|
// juzga el ESTADO del store (🟡 3) y devuelve las relaciones que hay que RE-CONTAR justo antes de publicar (🟠 2).
|
|
@@ -147,6 +148,8 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
147
148
|
if (relations)
|
|
148
149
|
this.logger.info(`Tipos de relación fusionados: ${relations.objectTypes.map((t) => t.type).join(', ')}`);
|
|
149
150
|
this.logger.log(`OPENFGA_STORE_ID=${this.storeId}`);
|
|
151
|
+
if (!(await this.reread(OpenFgaClient, factsModelDifferences, apiUrl, this.storeId, model.authorization_model_id, holderTypes, relations)))
|
|
152
|
+
return;
|
|
150
153
|
this.logger.log(`OPENFGA_MODEL_ID=${model.authorization_model_id}`);
|
|
151
154
|
return;
|
|
152
155
|
}
|
|
@@ -158,8 +161,46 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
158
161
|
this.logger.log('Añade al .env (junto a AUTHZ_DRIVER=openfga):');
|
|
159
162
|
this.logger.log(`OPENFGA_URL=${apiUrl}`);
|
|
160
163
|
this.logger.log(`OPENFGA_STORE_ID=${storeId}`);
|
|
164
|
+
if (!(await this.reread(OpenFgaClient, factsModelDifferences, apiUrl, storeId, modelId, holderTypes, relations)))
|
|
165
|
+
return;
|
|
161
166
|
this.logger.log(`OPENFGA_MODEL_ID=${modelId}`);
|
|
162
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* **La RELECTURA de lo publicado** (lote «antes de RC» · L11, diseño 1.3 D del analista; condición de aceptación C12 del
|
|
170
|
+
* tester): tras `writeAuthorizationModel` se LEE el modelo por su id y se compara ENTERO (`slice: 'all'`) con el que
|
|
171
|
+
* genera la config. Ancla el canonicalizador de L11 contra el servidor REAL en cada publicación: un servidor que
|
|
172
|
+
* serialice distinto (otra versión, un campo nuevo) se descubre en el despliegue, no con un 500 en la primera petición
|
|
173
|
+
* de producción. Si la relectura falla o difiere: exit 1, prefijo ESTABLE «provision RELECTURA FALLIDA:» (punto 7) y la
|
|
174
|
+
* letra que no miente (H11 c): el modelo YA está publicado en el store, y NO hay que fijarlo — por eso la línea
|
|
175
|
+
* `OPENFGA_MODEL_ID=` NO se imprime. Devuelve si se puede fijar.
|
|
176
|
+
*/
|
|
177
|
+
async reread(OpenFgaClient, factsModelDifferences, apiUrl, storeId, modelId, holderTypes, relations) {
|
|
178
|
+
let differences;
|
|
179
|
+
try {
|
|
180
|
+
const client = new OpenFgaClient({ apiUrl, storeId, authorizationModelId: modelId, retryParams: { maxRetry: 0 } });
|
|
181
|
+
const response = await client.readAuthorizationModel();
|
|
182
|
+
differences = factsModelDifferences(response?.authorization_model, { holderTypes, relations, slice: 'all' }).differences.map((d) => d.path);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
this.logger.error(`provision RELECTURA FALLIDA: el modelo ${modelId} SE PUBLICÓ en el store ${storeId}, pero no se pudo releer y ` +
|
|
186
|
+
`comparar con el que genera la config (${String(error?.message ?? error)}). NO lo fijes (OPENFGA_MODEL_ID): los ` +
|
|
187
|
+
'drivers lo comparan al arrancar y, sin esta comprobación, podrían negarse a servir. Repite el comando cuando el ' +
|
|
188
|
+
'servidor responda.');
|
|
189
|
+
this.exitCode = 1;
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
if (differences.length === 0) {
|
|
193
|
+
// Punto 7 de CLAUDE.md (cierre de L11, 🟡-1 del tester): la rama de éxito también dice que corrió, con prefijo ESTABLE.
|
|
194
|
+
this.logger.success(`provision RELECTURA OK: el modelo ${modelId} releído del store ${storeId} es IGUAL (entero) al que genera la config; ya se puede fijar.`);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
this.logger.error(`provision RELECTURA FALLIDA: el modelo ${modelId} SE PUBLICÓ en el store ${storeId}, pero lo que el servidor devuelve ` +
|
|
198
|
+
`NO es igual al que genera la config (L11): ${differences.slice(0, 20).join('; ')}${differences.length > 20 ? ' …' : ''}. ` +
|
|
199
|
+
'NO lo fijes (OPENFGA_MODEL_ID): los drivers openfga lo compararían igual al arrancar y responderían 500 E_AUTHZ_CONFIG. ' +
|
|
200
|
+
'Suele ser un servidor de otra versión que serializa el modelo distinto: compruébalo (CI prueba OpenFGA v1.19.0) y repórtalo.');
|
|
201
|
+
this.exitCode = 1;
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
163
204
|
/**
|
|
164
205
|
* **La guarda contra la MUTILACIÓN del modelo** (plan 2.5 · lote D · D2; analista §4.6, juez ronda 2 · R2-4 c y H3;
|
|
165
206
|
* cierre 1 de D2 · 🟡 3 y 🟠 2). `openfga:provision --store-id` publica desde la config del PROCESO: si ese proceso ya
|
|
@@ -411,4 +452,3 @@ export async function countLiveDenies(client, dropped) {
|
|
|
411
452
|
} while (continuationToken);
|
|
412
453
|
return { live, count, read };
|
|
413
454
|
}
|
|
414
|
-
//# sourceMappingURL=openfga_provision.js.map
|
package/build/configure.d.ts
CHANGED
package/build/configure.js
CHANGED