@jantstack/adonis-authz 1.1.0 → 2.4.0-alpha.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/LICENSE +1 -1
- package/README.md +837 -51
- package/build/commands/authz_catalog_diff.d.ts +28 -0
- package/build/commands/authz_catalog_diff.d.ts.map +1 -0
- package/build/commands/authz_catalog_diff.js +67 -0
- package/build/commands/authz_catalog_diff.js.map +1 -0
- package/build/commands/authz_catalog_prune_orphans.d.ts +78 -0
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -0
- package/build/commands/authz_catalog_prune_orphans.js +136 -0
- package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
- package/build/commands/authz_catalog_sync.d.ts +37 -0
- package/build/commands/authz_catalog_sync.d.ts.map +1 -0
- package/build/commands/authz_catalog_sync.js +81 -0
- package/build/commands/authz_catalog_sync.js.map +1 -0
- package/build/commands/authz_freeze.d.ts +44 -0
- package/build/commands/authz_freeze.d.ts.map +1 -0
- package/build/commands/authz_freeze.js +95 -0
- package/build/commands/authz_freeze.js.map +1 -0
- package/build/commands/authz_reconcile.d.ts +102 -0
- package/build/commands/authz_reconcile.d.ts.map +1 -0
- package/build/commands/authz_reconcile.js +294 -0
- package/build/commands/authz_reconcile.js.map +1 -0
- package/build/commands/authz_relations_reconcile.d.ts +73 -0
- package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
- package/build/commands/authz_relations_reconcile.js +225 -0
- package/build/commands/authz_relations_reconcile.js.map +1 -0
- package/build/commands/authz_scopes_relay.d.ts +47 -0
- package/build/commands/authz_scopes_relay.d.ts.map +1 -0
- package/build/commands/authz_scopes_relay.js +141 -0
- package/build/commands/authz_scopes_relay.js.map +1 -0
- package/build/commands/authz_unfreeze.d.ts +37 -0
- package/build/commands/authz_unfreeze.d.ts.map +1 -0
- package/build/commands/authz_unfreeze.js +92 -0
- package/build/commands/authz_unfreeze.js.map +1 -0
- package/build/commands/main.d.ts +8 -1
- package/build/commands/main.d.ts.map +1 -1
- package/build/commands/main.js +8 -1
- package/build/commands/main.js.map +1 -1
- package/build/commands/openfga_provision.d.ts +46 -4
- package/build/commands/openfga_provision.d.ts.map +1 -1
- package/build/commands/openfga_provision.js +91 -7
- package/build/commands/openfga_provision.js.map +1 -1
- package/build/configure.d.ts +11 -0
- package/build/configure.d.ts.map +1 -1
- package/build/configure.js +37 -1
- package/build/configure.js.map +1 -1
- package/build/index.d.ts +75 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +67 -5
- package/build/index.js.map +1 -1
- package/build/providers/authz_provider.d.ts +26 -2
- package/build/providers/authz_provider.d.ts.map +1 -1
- package/build/providers/authz_provider.js +48 -2
- package/build/providers/authz_provider.js.map +1 -1
- package/build/services/relations.d.ts +14 -0
- package/build/services/relations.d.ts.map +1 -0
- package/build/services/relations.js +17 -0
- package/build/services/relations.js.map +1 -0
- package/build/src/catalog/catalog.d.ts +289 -0
- package/build/src/catalog/catalog.d.ts.map +1 -0
- package/build/src/catalog/catalog.js +859 -0
- package/build/src/catalog/catalog.js.map +1 -0
- package/build/src/catalog/catalog_cache.d.ts +324 -0
- package/build/src/catalog/catalog_cache.d.ts.map +1 -0
- package/build/src/catalog/catalog_cache.js +666 -0
- package/build/src/catalog/catalog_cache.js.map +1 -0
- package/build/src/clock.d.ts +24 -0
- package/build/src/clock.d.ts.map +1 -0
- package/build/src/clock.js +7 -0
- package/build/src/clock.js.map +1 -0
- package/build/src/define_config.d.ts +201 -7
- package/build/src/define_config.d.ts.map +1 -1
- package/build/src/define_config.js.map +1 -1
- package/build/src/drivers/database_driver.d.ts +324 -15
- package/build/src/drivers/database_driver.d.ts.map +1 -1
- package/build/src/drivers/database_driver.js +1107 -128
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/database_relations_driver.d.ts +75 -0
- package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/database_relations_driver.js +450 -0
- package/build/src/drivers/database_relations_driver.js.map +1 -0
- package/build/src/drivers/openfga_driver.d.ts +963 -55
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +2693 -372
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/openfga_facts.d.ts +369 -0
- package/build/src/drivers/openfga_facts.d.ts.map +1 -0
- package/build/src/drivers/openfga_facts.js +813 -0
- package/build/src/drivers/openfga_facts.js.map +1 -0
- package/build/src/drivers/openfga_relations_driver.d.ts +120 -0
- package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/openfga_relations_driver.js +466 -0
- package/build/src/drivers/openfga_relations_driver.js.map +1 -0
- package/build/src/errors.d.ts +586 -0
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +583 -0
- package/build/src/errors.js.map +1 -1
- package/build/src/expiry.d.ts +27 -0
- package/build/src/expiry.d.ts.map +1 -0
- package/build/src/expiry.js +50 -0
- package/build/src/expiry.js.map +1 -0
- package/build/src/freeze.d.ts +120 -0
- package/build/src/freeze.d.ts.map +1 -0
- package/build/src/freeze.js +172 -0
- package/build/src/freeze.js.map +1 -0
- package/build/src/hierarchical_resolver.d.ts +56 -0
- package/build/src/hierarchical_resolver.d.ts.map +1 -0
- package/build/src/hierarchical_resolver.js +87 -0
- package/build/src/hierarchical_resolver.js.map +1 -0
- package/build/src/{middleware → http}/app_access_middleware.d.ts +8 -6
- package/build/src/http/app_access_middleware.d.ts.map +1 -0
- package/build/src/{middleware → http}/app_access_middleware.js +10 -26
- package/build/src/http/app_access_middleware.js.map +1 -0
- package/build/src/http/resource_access_middleware.d.ts +105 -0
- package/build/src/http/resource_access_middleware.d.ts.map +1 -0
- package/build/src/http/resource_access_middleware.js +81 -0
- package/build/src/http/resource_access_middleware.js.map +1 -0
- package/build/src/identity.d.ts +228 -0
- package/build/src/identity.d.ts.map +1 -0
- package/build/src/identity.js +457 -0
- package/build/src/identity.js.map +1 -0
- package/build/src/manager.d.ts +536 -7
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +2676 -23
- package/build/src/manager.js.map +1 -1
- package/build/src/memoize_ancestors.d.ts +23 -0
- package/build/src/memoize_ancestors.d.ts.map +1 -0
- package/build/src/memoize_ancestors.js +42 -0
- package/build/src/memoize_ancestors.js.map +1 -0
- package/build/src/models/authz_assignment.d.ts +11 -11
- package/build/src/models/authz_assignment.d.ts.map +1 -1
- package/build/src/models/authz_deny.d.ts +11 -11
- package/build/src/models/authz_deny.d.ts.map +1 -1
- package/build/src/models/authz_permission.d.ts +17 -11
- package/build/src/models/authz_permission.d.ts.map +1 -1
- package/build/src/models/authz_permission.js +4 -0
- package/build/src/models/authz_permission.js.map +1 -1
- package/build/src/models/authz_role.d.ts +19 -12
- package/build/src/models/authz_role.d.ts.map +1 -1
- package/build/src/models/authz_role.js +6 -1
- package/build/src/models/authz_role.js.map +1 -1
- package/build/src/models/authz_role_permission.d.ts +11 -11
- package/build/src/models/authz_role_permission.d.ts.map +1 -1
- package/build/src/openfga.d.ts +29 -0
- package/build/src/openfga.d.ts.map +1 -0
- package/build/src/openfga.js +26 -0
- package/build/src/openfga.js.map +1 -0
- package/build/src/reconcile.d.ts +37 -0
- package/build/src/reconcile.d.ts.map +1 -0
- package/build/src/reconcile.js +69 -0
- package/build/src/reconcile.js.map +1 -0
- package/build/src/relation_partition_trigger.d.ts +8 -0
- package/build/src/relation_partition_trigger.d.ts.map +1 -0
- package/build/src/relation_partition_trigger.js +85 -0
- package/build/src/relation_partition_trigger.js.map +1 -0
- package/build/src/relations/define_relations_config.d.ts +58 -0
- package/build/src/relations/define_relations_config.d.ts.map +1 -0
- package/build/src/relations/define_relations_config.js +144 -0
- package/build/src/relations/define_relations_config.js.map +1 -0
- package/build/src/relations/manager.d.ts +38 -0
- package/build/src/relations/manager.d.ts.map +1 -0
- package/build/src/relations/manager.js +156 -0
- package/build/src/relations/manager.js.map +1 -0
- package/build/src/relations/reconcile.d.ts +62 -0
- package/build/src/relations/reconcile.d.ts.map +1 -0
- package/build/src/relations/reconcile.js +138 -0
- package/build/src/relations/reconcile.js.map +1 -0
- package/build/src/relations_config_store.d.ts +22 -0
- package/build/src/relations_config_store.d.ts.map +1 -0
- package/build/src/relations_config_store.js +74 -0
- package/build/src/relations_config_store.js.map +1 -0
- package/build/src/scope_outbox.d.ts +69 -0
- package/build/src/scope_outbox.d.ts.map +1 -0
- package/build/src/scope_outbox.js +291 -0
- package/build/src/scope_outbox.js.map +1 -0
- package/build/src/shared/backend_guard.d.ts +106 -0
- package/build/src/shared/backend_guard.d.ts.map +1 -0
- package/build/src/shared/backend_guard.js +246 -0
- package/build/src/shared/backend_guard.js.map +1 -0
- package/build/src/shared/sql_expiry.d.ts +53 -0
- package/build/src/shared/sql_expiry.d.ts.map +1 -0
- package/build/src/shared/sql_expiry.js +66 -0
- package/build/src/shared/sql_expiry.js.map +1 -0
- package/build/src/sql_descendants.d.ts +97 -0
- package/build/src/sql_descendants.d.ts.map +1 -0
- package/build/src/sql_descendants.js +203 -0
- package/build/src/sql_descendants.js.map +1 -0
- package/build/src/testing/contract.d.ts +212 -7
- package/build/src/testing/contract.d.ts.map +1 -1
- package/build/src/testing/contract.js +3449 -24
- package/build/src/testing/contract.js.map +1 -1
- package/build/src/testing/main.d.ts +10 -2
- package/build/src/testing/main.d.ts.map +1 -1
- package/build/src/testing/main.js +5 -1
- package/build/src/testing/main.js.map +1 -1
- package/build/src/testing/migration_contract.d.ts +284 -0
- package/build/src/testing/migration_contract.d.ts.map +1 -0
- package/build/src/testing/migration_contract.js +586 -0
- package/build/src/testing/migration_contract.js.map +1 -0
- package/build/src/testing/relations_contract.d.ts +51 -0
- package/build/src/testing/relations_contract.d.ts.map +1 -0
- package/build/src/testing/relations_contract.js +654 -0
- package/build/src/testing/relations_contract.js.map +1 -0
- package/build/src/testing/relations_reconcile_contract.d.ts +24 -0
- package/build/src/testing/relations_reconcile_contract.d.ts.map +1 -0
- package/build/src/testing/relations_reconcile_contract.js +172 -0
- package/build/src/testing/relations_reconcile_contract.js.map +1 -0
- package/build/src/testing/scope_tree.d.ts +65 -0
- package/build/src/testing/scope_tree.d.ts.map +1 -0
- package/build/src/testing/scope_tree.js +145 -0
- package/build/src/testing/scope_tree.js.map +1 -0
- package/build/src/traits/authz_scopes.d.ts +30 -6
- package/build/src/traits/authz_scopes.d.ts.map +1 -1
- package/build/src/traits/authz_scopes.js +30 -18
- package/build/src/traits/authz_scopes.js.map +1 -1
- package/build/src/traits/has_uuid.d.ts +12 -12
- package/build/src/traits/has_uuid.d.ts.map +1 -1
- package/build/src/types.d.ts +1313 -28
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js +20 -4
- package/build/src/types.js.map +1 -1
- package/build/stubs/config/app_acl.stub +4 -2
- package/build/stubs/config/authorization.stub +168 -14
- package/build/stubs/migration.stub +183 -13
- package/build/stubs/scopes_outbox_migration.stub +57 -0
- package/package.json +14 -7
- package/build/commands/openfga_import.d.ts +0 -28
- package/build/commands/openfga_import.d.ts.map +0 -1
- package/build/commands/openfga_import.js +0 -74
- package/build/commands/openfga_import.js.map +0 -1
- package/build/src/catalog.d.ts +0 -3
- package/build/src/catalog.d.ts.map +0 -1
- package/build/src/catalog.js +0 -103
- package/build/src/catalog.js.map +0 -1
- package/build/src/middleware/app_access_middleware.d.ts.map +0 -1
- package/build/src/middleware/app_access_middleware.js.map +0 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
|
+
import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
/**
|
|
4
|
+
* Compara los catálogos declarados en `config/authorization.ts` (`catalogs`)
|
|
5
|
+
* con las tablas `authz_*` y sale con código ≠ 0 si hay diferencias: roles o
|
|
6
|
+
* permisos que faltan, vínculos que faltan, vínculos SOBRANTES (un permiso
|
|
7
|
+
* que el config ya no da y la base sigue dando: L0.9) y roles AMBIGUOS (dos
|
|
8
|
+
* homónimos visibles en la misma cadena, 3D · M2). Pensado para CI: un
|
|
9
|
+
* config que no coincide con producción no pasa.
|
|
10
|
+
*
|
|
11
|
+
* node ace authz:catalog:diff
|
|
12
|
+
* node ace authz:catalog:diff --fail-on-shadows
|
|
13
|
+
*
|
|
14
|
+
* Los roles locales ENSOMBRECIDOS por una definición más autorizada (un
|
|
15
|
+
* global, o el de un ancestro) se LISTAN y no cuentan como deriva (3F · S3):
|
|
16
|
+
* un tenant no puede dejar en rojo el gate de CI de la plataforma. El precio
|
|
17
|
+
* es que nadie se entera por CI de que las rutas por slug de ese subárbol
|
|
18
|
+
* están muertas, así que `--fail-on-shadows` lo convierte en deriva para
|
|
19
|
+
* quien sí quiera enterarse (3G · X3).
|
|
20
|
+
*/
|
|
21
|
+
export default class AuthzCatalogDiff extends BaseCommand {
|
|
22
|
+
static commandName: string;
|
|
23
|
+
static description: string;
|
|
24
|
+
static options: CommandOptions;
|
|
25
|
+
failOnShadows: boolean;
|
|
26
|
+
run(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=authz_catalog_diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_catalog_diff.d.ts","sourceRoot":"","sources":["../../commands/authz_catalog_diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,MAAM,CAAC,WAAW,SAAuB;IACzC,MAAM,CAAC,WAAW,SAAsF;IAExG,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAMO,aAAa,EAAE,OAAO,CAAA;IAExB,GAAG;CA0BV"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
11
|
+
/**
|
|
12
|
+
* Compara los catálogos declarados en `config/authorization.ts` (`catalogs`)
|
|
13
|
+
* con las tablas `authz_*` y sale con código ≠ 0 si hay diferencias: roles o
|
|
14
|
+
* permisos que faltan, vínculos que faltan, vínculos SOBRANTES (un permiso
|
|
15
|
+
* que el config ya no da y la base sigue dando: L0.9) y roles AMBIGUOS (dos
|
|
16
|
+
* homónimos visibles en la misma cadena, 3D · M2). Pensado para CI: un
|
|
17
|
+
* config que no coincide con producción no pasa.
|
|
18
|
+
*
|
|
19
|
+
* node ace authz:catalog:diff
|
|
20
|
+
* node ace authz:catalog:diff --fail-on-shadows
|
|
21
|
+
*
|
|
22
|
+
* Los roles locales ENSOMBRECIDOS por una definición más autorizada (un
|
|
23
|
+
* global, o el de un ancestro) se LISTAN y no cuentan como deriva (3F · S3):
|
|
24
|
+
* un tenant no puede dejar en rojo el gate de CI de la plataforma. El precio
|
|
25
|
+
* es que nadie se entera por CI de que las rutas por slug de ese subárbol
|
|
26
|
+
* están muertas, así que `--fail-on-shadows` lo convierte en deriva para
|
|
27
|
+
* quien sí quiera enterarse (3G · X3).
|
|
28
|
+
*/
|
|
29
|
+
export default class AuthzCatalogDiff extends BaseCommand {
|
|
30
|
+
static commandName = 'authz:catalog:diff';
|
|
31
|
+
static description = 'Compare the catalogs declared in config with the authz_* tables (exit 1 on drift)';
|
|
32
|
+
static options = {
|
|
33
|
+
startApp: true,
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const config = this.app.config.get('authorization');
|
|
37
|
+
const catalogs = config?.catalogs;
|
|
38
|
+
if (!Array.isArray(catalogs) || catalogs.length === 0) {
|
|
39
|
+
this.logger.error('No hay catálogos declarados: añade `catalogs: [() => import(...)]` a config/authorization.ts');
|
|
40
|
+
this.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { runCatalogDiff } = await import('../src/catalog/catalog.js');
|
|
44
|
+
// Con el resolutor del config el diff puede juzgar además si dos roles
|
|
45
|
+
// LOCALES homónimos son visibles en la misma cadena (3D · M2 d).
|
|
46
|
+
const { inSync, lines } = await runCatalogDiff(catalogs, {
|
|
47
|
+
resolveChain: config?.scopes?.resolveChain,
|
|
48
|
+
failOnShadows: this.failOnShadows === true,
|
|
49
|
+
});
|
|
50
|
+
for (const line of lines)
|
|
51
|
+
this.logger.log(line);
|
|
52
|
+
if (inSync) {
|
|
53
|
+
this.logger.success('Catálogo en sync con la base.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.logger.error('El catálogo NO está en sync: ejecuta `node ace authz:catalog:sync`.');
|
|
57
|
+
this.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
__decorate([
|
|
61
|
+
flags.boolean({
|
|
62
|
+
name: 'fail-on-shadows',
|
|
63
|
+
description: 'Exit 1 too when a local role is shadowed by a more authoritative one (global or ancestor)',
|
|
64
|
+
}),
|
|
65
|
+
__metadata("design:type", Boolean)
|
|
66
|
+
], AuthzCatalogDiff.prototype, "failOnShadows", void 0);
|
|
67
|
+
//# sourceMappingURL=authz_catalog_diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_catalog_diff.js","sourceRoot":"","sources":["../../commands/authz_catalog_diff.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAGvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,MAAM,CAAC,WAAW,GAAG,oBAAoB,CAAA;IACzC,MAAM,CAAC,WAAW,GAAG,mFAAmF,CAAA;IAExG,MAAM,CAAC,OAAO,GAAmB;QAC/B,QAAQ,EAAE,IAAI;KACf,CAAA;IAQD,KAAK,CAAC,GAAG;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAQ,CAAA;QAC1D,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,CAAA;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,8FAA8F,CAC/F,CAAA;YACD,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;YACjB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAA;QACpE,uEAAuE;QACvE,iEAAiE;QACjE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE;YACvD,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;YAC1C,aAAa,EAAE,IAAI,CAAC,aAAa,KAAK,IAAI;SAC3C,CAAC,CAAA;QACF,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC/C,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAA;YACpD,OAAM;QACR,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAA;QACxF,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;IACnB,CAAC;;AA3BO;IAJP,KAAK,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,2FAA2F;KACzG,CAAC;;uDAC4B"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
|
+
import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
/** Un huérfano tal como lo reporta `manager.pruneOrphanRoles()`. */
|
|
4
|
+
interface OrphanRow {
|
|
5
|
+
role: {
|
|
6
|
+
uuid: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
scopeType: string;
|
|
9
|
+
rank: number;
|
|
10
|
+
};
|
|
11
|
+
owner: {
|
|
12
|
+
type: string;
|
|
13
|
+
uuid?: string | null;
|
|
14
|
+
};
|
|
15
|
+
permissions: string[];
|
|
16
|
+
/** Hechos vigentes del rol, o `undefined` si el driver no sabe contarlos (3b-2j). */
|
|
17
|
+
assignments: number | undefined;
|
|
18
|
+
/** `true` concede, `false` no concede SEGURO, `undefined` **no se sabe** (3b-2j). */
|
|
19
|
+
stillGranting: boolean | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Las líneas del listado, en TRES cubos (3b-2j, decisión del dueño del
|
|
23
|
+
* 2026-08-31 (3) · consecuencia 2). Es una función pura para que el reparto
|
|
24
|
+
* —lo único del comando que decide algo— tenga su caso.
|
|
25
|
+
*
|
|
26
|
+
* - `stillGranting === false`: el driver ha contado sus hechos y son cero.
|
|
27
|
+
* Es el único cubo del que se puede decir «no concede»: línea normal.
|
|
28
|
+
* - `true`: concede hoy. Aparte y con aviso, desde 3b-0b · AA1.
|
|
29
|
+
* - `undefined`: el driver no implementa `countRoleAssignments`, así que
|
|
30
|
+
* NADIE lo sabe. Va con los anteriores —aparte y con aviso—, porque «no
|
|
31
|
+
* lo sé» no puede leerse como «no concede» justo antes de un borrado.
|
|
32
|
+
*/
|
|
33
|
+
export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, skipped: Set<string>): Array<{
|
|
34
|
+
level: 'log' | 'warning';
|
|
35
|
+
message: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Lista —y con `--force` borra— los roles LOCALES cuyo owner el árbol del
|
|
39
|
+
* consumidor YA NO conoce (3b-0 · Z2).
|
|
40
|
+
*
|
|
41
|
+
* Un rol así está DORMIDO, y «dormido» significa exactamente esto
|
|
42
|
+
* (3b-0b · AA1): no es visible desde ningún scope vivo cuya cadena NO pase
|
|
43
|
+
* por su owner. NO significa que no conceda: un descendiente vivo cuya ruta
|
|
44
|
+
* materializada siga pasando por el owner cumple la regla de visibilidad
|
|
45
|
+
* (invariante 18), y desde ahí el rol concede, es membresía y se puede
|
|
46
|
+
* asignar. Por eso este comando puede estar revocando permisos VIVOS, y por
|
|
47
|
+
* eso los huérfanos que aún tienen asignaciones vigentes se listan aparte,
|
|
48
|
+
* con aviso — y desde 3b-2j también los que el driver no sabe contar (un
|
|
49
|
+
* driver sin `countRoleAssignments`): «no lo sé» no es «no concede».
|
|
50
|
+
*
|
|
51
|
+
* Lo que el rol dormido hace en todo caso es ocupar su
|
|
52
|
+
* `(slug, nivel)` allí donde todavía se le vea, y `deleteScopedRole` no lo
|
|
53
|
+
* alcanza (resuelve el owner en fresco: 422 `E_AUTHZ_UNKNOWN_SCOPE`). Esta
|
|
54
|
+
* es su salida.
|
|
55
|
+
*
|
|
56
|
+
* node ace authz:catalog:prune-orphans # --dry-run: solo lista
|
|
57
|
+
* node ace authz:catalog:prune-orphans --force # purga de verdad
|
|
58
|
+
* node ace authz:catalog:prune-orphans --force --allow-mass-purge
|
|
59
|
+
*
|
|
60
|
+
* Es una operación de PLATAFORMA, no una escritura que dispare un tenant: no
|
|
61
|
+
* lleva actor ni mide rangos. Hasta 3G esta limpieza la arrastraba
|
|
62
|
+
* `scopes.detached` —que SÍ dispara un tenant, sobre un scope que ya no
|
|
63
|
+
* resuelve—, y de ahí salieron tres de las cuatro regresiones de la Fase 3.
|
|
64
|
+
*
|
|
65
|
+
* `--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, o
|
|
67
|
+
* más del 50 % de los roles locales): es una decisión humana, no un default.
|
|
68
|
+
*/
|
|
69
|
+
export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
70
|
+
static commandName: string;
|
|
71
|
+
static description: string;
|
|
72
|
+
static options: CommandOptions;
|
|
73
|
+
force: boolean | undefined;
|
|
74
|
+
allowMassPurge: boolean | undefined;
|
|
75
|
+
run(): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=authz_catalog_prune_orphans.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_catalog_prune_orphans.d.ts","sourceRoot":"","sources":["../../commands/authz_catalog_prune_orphans.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEzD,oEAAoE;AACpE,UAAU,SAAS;IACjB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;IAC7C,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,qFAAqF;IACrF,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,qFAAqF;IACrF,aAAa,EAAE,OAAO,GAAG,SAAS,CAAA;CACnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,EAAE,EACpB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,EACnB,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GACnB,KAAK,CAAC;IAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAoCtD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,OAAO,OAAO,wBAAyB,SAAQ,WAAW;IAC/D,MAAM,CAAC,WAAW,SAAgC;IAClD,MAAM,CAAC,WAAW,SAAkF;IAEpG,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAM1B,cAAc,EAAE,OAAO,GAAG,SAAS,CAAA;IAErC,GAAG;CAuCV"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
11
|
+
/**
|
|
12
|
+
* Las líneas del listado, en TRES cubos (3b-2j, decisión del dueño del
|
|
13
|
+
* 2026-08-31 (3) · consecuencia 2). Es una función pura para que el reparto
|
|
14
|
+
* —lo único del comando que decide algo— tenga su caso.
|
|
15
|
+
*
|
|
16
|
+
* - `stillGranting === false`: el driver ha contado sus hechos y son cero.
|
|
17
|
+
* Es el único cubo del que se puede decir «no concede»: línea normal.
|
|
18
|
+
* - `true`: concede hoy. Aparte y con aviso, desde 3b-0b · AA1.
|
|
19
|
+
* - `undefined`: el driver no implementa `countRoleAssignments`, así que
|
|
20
|
+
* NADIE lo sabe. Va con los anteriores —aparte y con aviso—, porque «no
|
|
21
|
+
* lo sé» no puede leerse como «no concede» justo antes de un borrado.
|
|
22
|
+
*/
|
|
23
|
+
export function orphanLines(orphans, purged, skipped) {
|
|
24
|
+
const estado = (o) => skipped.has(o.role.uuid) ? 'saltado (el owner volvió)' : purged.has(o.role.uuid) ? 'purgado' : 'huérfano';
|
|
25
|
+
const linea = (o) => `${o.role.slug}@${o.role.scopeType} (uuid ${o.role.uuid}, rank ${o.role.rank}) — owner ` +
|
|
26
|
+
`${o.owner.type}:${o.owner.uuid ?? ''} ya no existe en el árbol; permisos: ${o.permissions.join(', ') || '—'}` +
|
|
27
|
+
(o.stillGranting === true ? `; asignaciones VIGENTES: ${o.assignments}` : '');
|
|
28
|
+
const lines = [];
|
|
29
|
+
for (const o of orphans.filter((x) => x.stillGranting === false)) {
|
|
30
|
+
lines.push({ level: 'log', message: `${estado(o)}: ${linea(o)}` });
|
|
31
|
+
}
|
|
32
|
+
const vivos = orphans.filter((x) => x.stillGranting === true);
|
|
33
|
+
for (const o of vivos)
|
|
34
|
+
lines.push({ level: 'warning', message: `${estado(o)} · TODAVÍA CONCEDE: ${linea(o)}` });
|
|
35
|
+
const dudosos = orphans.filter((x) => x.stillGranting === undefined);
|
|
36
|
+
for (const o of dudosos) {
|
|
37
|
+
lines.push({ level: 'warning', message: `${estado(o)} · NO SE SABE SI CONCEDE: ${linea(o)}` });
|
|
38
|
+
}
|
|
39
|
+
if (vivos.length > 0) {
|
|
40
|
+
lines.push({
|
|
41
|
+
level: 'warning',
|
|
42
|
+
message: `${vivos.length} rol(es) huérfano(s) siguen teniendo asignaciones vigentes: un rol dormido NO deja de conceder si ` +
|
|
43
|
+
`algún scope vivo conserva al owner en su cadena (rutas materializadas, borrado en dos pasos). Purgarlos revoca ` +
|
|
44
|
+
`permisos que hoy funcionan.`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (dudosos.length > 0) {
|
|
48
|
+
lines.push({
|
|
49
|
+
level: 'warning',
|
|
50
|
+
message: `${dudosos.length} rol(es) huérfano(s) SIN demostración de que no concedan: el driver configurado no implementa ` +
|
|
51
|
+
`'countRoleAssignments' (el método del puerto que cuenta los hechos vigentes de un rol), así que nadie sabe si ` +
|
|
52
|
+
`están concediendo. Se listan aparte a propósito: 'no lo sé' no es 'no concede', y --force los borra igual.`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Lista —y con `--force` borra— los roles LOCALES cuyo owner el árbol del
|
|
59
|
+
* consumidor YA NO conoce (3b-0 · Z2).
|
|
60
|
+
*
|
|
61
|
+
* Un rol así está DORMIDO, y «dormido» significa exactamente esto
|
|
62
|
+
* (3b-0b · AA1): no es visible desde ningún scope vivo cuya cadena NO pase
|
|
63
|
+
* por su owner. NO significa que no conceda: un descendiente vivo cuya ruta
|
|
64
|
+
* materializada siga pasando por el owner cumple la regla de visibilidad
|
|
65
|
+
* (invariante 18), y desde ahí el rol concede, es membresía y se puede
|
|
66
|
+
* asignar. Por eso este comando puede estar revocando permisos VIVOS, y por
|
|
67
|
+
* eso los huérfanos que aún tienen asignaciones vigentes se listan aparte,
|
|
68
|
+
* con aviso — y desde 3b-2j también los que el driver no sabe contar (un
|
|
69
|
+
* driver sin `countRoleAssignments`): «no lo sé» no es «no concede».
|
|
70
|
+
*
|
|
71
|
+
* Lo que el rol dormido hace en todo caso es ocupar su
|
|
72
|
+
* `(slug, nivel)` allí donde todavía se le vea, y `deleteScopedRole` no lo
|
|
73
|
+
* alcanza (resuelve el owner en fresco: 422 `E_AUTHZ_UNKNOWN_SCOPE`). Esta
|
|
74
|
+
* es su salida.
|
|
75
|
+
*
|
|
76
|
+
* node ace authz:catalog:prune-orphans # --dry-run: solo lista
|
|
77
|
+
* node ace authz:catalog:prune-orphans --force # purga de verdad
|
|
78
|
+
* node ace authz:catalog:prune-orphans --force --allow-mass-purge
|
|
79
|
+
*
|
|
80
|
+
* Es una operación de PLATAFORMA, no una escritura que dispare un tenant: no
|
|
81
|
+
* lleva actor ni mide rangos. Hasta 3G esta limpieza la arrastraba
|
|
82
|
+
* `scopes.detached` —que SÍ dispara un tenant, sobre un scope que ya no
|
|
83
|
+
* resuelve—, y de ahí salieron tres de las cuatro regresiones de la Fase 3.
|
|
84
|
+
*
|
|
85
|
+
* `--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, o
|
|
87
|
+
* más del 50 % de los roles locales): es una decisión humana, no un default.
|
|
88
|
+
*/
|
|
89
|
+
export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
90
|
+
static commandName = 'authz:catalog:prune-orphans';
|
|
91
|
+
static description = 'List (or, with --force, purge) local roles whose owner scope no longer exists';
|
|
92
|
+
static options = {
|
|
93
|
+
startApp: true,
|
|
94
|
+
};
|
|
95
|
+
async run() {
|
|
96
|
+
const { default: authorization } = await import('../services/main.js');
|
|
97
|
+
const { orphans, purged, skipped, massPurge, dryRun } = await authorization.pruneOrphanRoles({
|
|
98
|
+
force: this.force === true,
|
|
99
|
+
allowMassPurge: this.allowMassPurge === true,
|
|
100
|
+
});
|
|
101
|
+
// Los que todavía conceden —y los que NADIE puede demostrar que no
|
|
102
|
+
// concedan— van aparte, con aviso: purgarlos es revocar permisos vivos,
|
|
103
|
+
// no recoger basura (3b-0b · AA1 + 3b-2j).
|
|
104
|
+
for (const { level, message } of orphanLines(orphans, new Set(purged.map((role) => role.uuid)), new Set(skipped.map(({ role }) => role.uuid)))) {
|
|
105
|
+
this.logger[level](message);
|
|
106
|
+
}
|
|
107
|
+
if (orphans.length === 0) {
|
|
108
|
+
this.logger.success('No hay roles locales huérfanos: todos los owners resuelven.');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (dryRun) {
|
|
112
|
+
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. ` +
|
|
114
|
+
`Con --force esto se rechaza (E_AUTHZ_MASS_PURGE_REFUSED); comprueba el resolutor antes de añadir --allow-mass-purge.`);
|
|
115
|
+
}
|
|
116
|
+
this.logger.warning(`${orphans.length} rol(es) local(es) con el owner fuera del árbol. No se ha borrado nada: repite con --force.`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (skipped.length > 0) {
|
|
120
|
+
this.logger.warning(`${skipped.length} rol(es) saltado(s): su owner volvió al árbol durante la pasada.`);
|
|
121
|
+
}
|
|
122
|
+
this.logger.success(`${purged.length} rol(es) local(es) huérfano(s) purgado(s).`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
__decorate([
|
|
126
|
+
flags.boolean({ description: 'Actually purge the orphan roles (default is a dry run)' }),
|
|
127
|
+
__metadata("design:type", Object)
|
|
128
|
+
], AuthzCatalogPruneOrphans.prototype, "force", void 0);
|
|
129
|
+
__decorate([
|
|
130
|
+
flags.boolean({
|
|
131
|
+
name: 'allow-mass-purge',
|
|
132
|
+
description: 'Allow a pass that would purge every owner (or more than half the local roles) — check your resolveChain first',
|
|
133
|
+
}),
|
|
134
|
+
__metadata("design:type", Object)
|
|
135
|
+
], AuthzCatalogPruneOrphans.prototype, "allowMassPurge", void 0);
|
|
136
|
+
//# sourceMappingURL=authz_catalog_prune_orphans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_catalog_prune_orphans.js","sourceRoot":"","sources":["../../commands/authz_catalog_prune_orphans.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
|
+
import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
import type { AuthorizationDriver } from '../src/types.js';
|
|
4
|
+
import type { SyncCatalogOptions } from '../src/catalog/catalog.js';
|
|
5
|
+
/**
|
|
6
|
+
* Las opciones con las que este comando sincroniza, en una función PURA
|
|
7
|
+
* (mismo patrón que `reconcileLines`/`unfreezePlan`): es la única decisión
|
|
8
|
+
* del comando y así tiene su caso sin montar un ace.
|
|
9
|
+
*
|
|
10
|
+
* **La proyección del driver ACTIVO viaja con el sync** (3b-8 · A1). En modo
|
|
11
|
+
* `facts` el store guarda un espejo del catálogo (`role:<uuid>#permits_<P>`)
|
|
12
|
+
* y el README promete que este comando lo reescribe — es el camino de
|
|
13
|
+
* recuperación documentado. Hasta 3b-8 el comando llamaba a `syncCatalogs`
|
|
14
|
+
* SIN la proyección: `authz_*` quedaba bien y el espejo sin tocar, o sea que
|
|
15
|
+
* un permiso quitado del catálogo SEGUÍA concediendo (fail-open) y un rol
|
|
16
|
+
* nuevo no concedía nada. Un driver sin espejo (`database`) no declara
|
|
17
|
+
* `catalogProjection` y el sync va sin ella, como siempre.
|
|
18
|
+
*/
|
|
19
|
+
export declare function catalogSyncOptions(driver: AuthorizationDriver, keepLinks: boolean | undefined): SyncCatalogOptions;
|
|
20
|
+
/**
|
|
21
|
+
* Sincroniza a las tablas `authz_*` los catálogos declarados en
|
|
22
|
+
* `config/authorization.ts` (`catalogs`), en orden. Transaccional por
|
|
23
|
+
* catálogo; con la poda de vínculos activa por defecto (L0.9): un permiso que
|
|
24
|
+
* el config quita de un rol deja de concederse. Nunca borra roles ni
|
|
25
|
+
* permisos.
|
|
26
|
+
*
|
|
27
|
+
* node ace authz:catalog:sync
|
|
28
|
+
* node ace authz:catalog:sync --keep-links # solo aditivo (1.x)
|
|
29
|
+
*/
|
|
30
|
+
export default class AuthzCatalogSync extends BaseCommand {
|
|
31
|
+
static commandName: string;
|
|
32
|
+
static description: string;
|
|
33
|
+
static options: CommandOptions;
|
|
34
|
+
keepLinks: boolean | undefined;
|
|
35
|
+
run(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=authz_catalog_sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_catalog_sync.d.ts","sourceRoot":"","sources":["../../commands/authz_catalog_sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAEnE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,EAC3B,SAAS,EAAE,OAAO,GAAG,SAAS,GAC7B,kBAAkB,CAOpB;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,MAAM,CAAC,WAAW,SAAuB;IACzC,MAAM,CAAC,WAAW,SAAiE;IAEnF,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;IAEhC,GAAG;CAuCV"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
11
|
+
/**
|
|
12
|
+
* Las opciones con las que este comando sincroniza, en una función PURA
|
|
13
|
+
* (mismo patrón que `reconcileLines`/`unfreezePlan`): es la única decisión
|
|
14
|
+
* del comando y así tiene su caso sin montar un ace.
|
|
15
|
+
*
|
|
16
|
+
* **La proyección del driver ACTIVO viaja con el sync** (3b-8 · A1). En modo
|
|
17
|
+
* `facts` el store guarda un espejo del catálogo (`role:<uuid>#permits_<P>`)
|
|
18
|
+
* y el README promete que este comando lo reescribe — es el camino de
|
|
19
|
+
* recuperación documentado. Hasta 3b-8 el comando llamaba a `syncCatalogs`
|
|
20
|
+
* SIN la proyección: `authz_*` quedaba bien y el espejo sin tocar, o sea que
|
|
21
|
+
* un permiso quitado del catálogo SEGUÍA concediendo (fail-open) y un rol
|
|
22
|
+
* nuevo no concedía nada. Un driver sin espejo (`database`) no declara
|
|
23
|
+
* `catalogProjection` y el sync va sin ella, como siempre.
|
|
24
|
+
*/
|
|
25
|
+
export function catalogSyncOptions(driver, keepLinks) {
|
|
26
|
+
return {
|
|
27
|
+
prune: keepLinks ? 'none' : 'links',
|
|
28
|
+
...(typeof driver.catalogProjection === 'function'
|
|
29
|
+
? { projection: driver.catalogProjection() }
|
|
30
|
+
: {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sincroniza a las tablas `authz_*` los catálogos declarados en
|
|
35
|
+
* `config/authorization.ts` (`catalogs`), en orden. Transaccional por
|
|
36
|
+
* catálogo; con la poda de vínculos activa por defecto (L0.9): un permiso que
|
|
37
|
+
* el config quita de un rol deja de concederse. Nunca borra roles ni
|
|
38
|
+
* permisos.
|
|
39
|
+
*
|
|
40
|
+
* node ace authz:catalog:sync
|
|
41
|
+
* node ace authz:catalog:sync --keep-links # solo aditivo (1.x)
|
|
42
|
+
*/
|
|
43
|
+
export default class AuthzCatalogSync extends BaseCommand {
|
|
44
|
+
static commandName = 'authz:catalog:sync';
|
|
45
|
+
static description = 'Sync the catalogs declared in config into the authz_* tables';
|
|
46
|
+
static options = {
|
|
47
|
+
startApp: true,
|
|
48
|
+
};
|
|
49
|
+
async run() {
|
|
50
|
+
const config = this.app.config.get('authorization');
|
|
51
|
+
const catalogs = config?.catalogs;
|
|
52
|
+
if (!Array.isArray(catalogs) || catalogs.length === 0) {
|
|
53
|
+
this.logger.error('No hay catálogos declarados: añade `catalogs: [() => import(...)]` a config/authorization.ts');
|
|
54
|
+
this.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const { syncCatalogs } = await import('../src/catalog/catalog.js');
|
|
58
|
+
// El driver ACTIVO decide si el sync lleva proyección (3b-8 · A1): en
|
|
59
|
+
// `facts` sin ella este comando NO reescribía el espejo del store.
|
|
60
|
+
const { default: authorization } = await import('../services/main.js');
|
|
61
|
+
const { count, shadowedByGlobal, assignableAtViolations } = await syncCatalogs(catalogs, catalogSyncOptions(await authorization.driver(), this.keepLinks));
|
|
62
|
+
// 3E · P1 b / P6: el sync ya no aborta por lo que un tenant hizo en su
|
|
63
|
+
// scope, pero tampoco lo silencia. Un despliegue que ensombrece roles de
|
|
64
|
+
// tenants —o que deja vínculos fuera del `assignableAt` nuevo— tiene que
|
|
65
|
+
// salir por pantalla y quedar en el log del deploy.
|
|
66
|
+
for (const role of shadowedByGlobal) {
|
|
67
|
+
this.logger.warning(`rol local ENSOMBRECIDO por el global del spec: ${role.slug}@${role.scopeType} (uuid ${role.uuid}, owner ${role.owner}). ` +
|
|
68
|
+
`El global gana; en esa cadena el slug pasa a 422 E_AUTHZ_AMBIGUOUS_ROLE hasta que se purgue uno.`);
|
|
69
|
+
}
|
|
70
|
+
for (const violation of assignableAtViolations) {
|
|
71
|
+
this.logger.warning(`vínculo fuera de assignableAt: ${violation.role.slug}@${violation.role.scopeType} (owner ${violation.role.owner}) → ` +
|
|
72
|
+
`${violation.permission}. No se ha borrado (lo asignado sigue concediendo): quítalo o amplía el assignableAt.`);
|
|
73
|
+
}
|
|
74
|
+
this.logger.success(`${count} catálogo(s) sincronizado(s)` + (this.keepLinks ? ' (sin poda de vínculos).' : '.'));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
__decorate([
|
|
78
|
+
flags.boolean({ description: 'Do not prune role→permission links missing from the spec' }),
|
|
79
|
+
__metadata("design:type", Object)
|
|
80
|
+
], AuthzCatalogSync.prototype, "keepLinks", void 0);
|
|
81
|
+
//# sourceMappingURL=authz_catalog_sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_catalog_sync.js","sourceRoot":"","sources":["../../commands/authz_catalog_sync.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAKvD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA2B,EAC3B,SAA8B;IAE9B,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;QACnC,GAAG,CAAC,OAAO,MAAM,CAAC,iBAAiB,KAAK,UAAU;YAChD,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,iBAAiB,EAAE,EAAE;YAC5C,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,MAAM,CAAC,WAAW,GAAG,oBAAoB,CAAA;IACzC,MAAM,CAAC,WAAW,GAAG,8DAA8D,CAAA;IAEnF,MAAM,CAAC,OAAO,GAAmB;QAC/B,QAAQ,EAAE,IAAI;KACf,CAAA;IAKD,KAAK,CAAC,GAAG;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAQ,CAAA;QAC1D,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,CAAA;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,8FAA8F,CAC/F,CAAA;YACD,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;YACjB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAA;QAClE,sEAAsE;QACtE,mEAAmE;QACnE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;QACtE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,GAAG,MAAM,YAAY,CAC5E,QAAQ,EACR,kBAAkB,CAAC,MAAM,aAAa,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CACjE,CAAA;QACD,uEAAuE;QACvE,yEAAyE;QACzE,yEAAyE;QACzE,oDAAoD;QACpD,KAAK,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,OAAO,CACjB,kDAAkD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,UAAU,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,KAAK,KAAK;gBACxH,kGAAkG,CACrG,CAAA;QACH,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,sBAAsB,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,OAAO,CACjB,kCAAkC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,SAAS,WAAW,SAAS,CAAC,IAAI,CAAC,KAAK,MAAM;gBACpH,GAAG,SAAS,CAAC,UAAU,uFAAuF,CACjH,CAAA;QACH,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,CACjB,GAAG,KAAK,8BAA8B,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAAC,CAC7F,CAAA;IACH,CAAC;;AAxCO;IADP,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;;mDACrD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
|
+
import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
/**
|
|
4
|
+
* **Abre la ventana de CUTOVER: congela las escrituras del motor** (3b-7;
|
|
5
|
+
* condición explícita del juez del panel 3 — el intervalo entre la última
|
|
6
|
+
* pasada de `authz:reconcile` y el cambio de `config.default` es donde
|
|
7
|
+
* ocurre la pérdida, lo decide un humano y dura minutos u horas).
|
|
8
|
+
*
|
|
9
|
+
* node ace authz:freeze --reason="cutover a openfga"
|
|
10
|
+
* node ace authz:freeze --reason="cutover" --lease-ms=600000 # opt-in: caduca sola
|
|
11
|
+
* … node ace authz:reconcile --to=openfga --from=database … # corre DENTRO de la ventana
|
|
12
|
+
* … cambiar config.default y redesplegar …
|
|
13
|
+
* node ace authz:unfreeze
|
|
14
|
+
*
|
|
15
|
+
* Mientras la ventana está abierta, toda ESCRITURA del manager —en TODOS los
|
|
16
|
+
* procesos que comparten `authz_*`— es 503 `E_AUTHZ_FROZEN` reintentable;
|
|
17
|
+
* las lecturas siguen. `authz:reconcile` reconoce esta ventana como contexto
|
|
18
|
+
* propio: corre dentro sin tomar otro freeze y NO la levanta al terminar —
|
|
19
|
+
* cerrar el intervalo pasada→cutover es exactamente para lo que existe.
|
|
20
|
+
*
|
|
21
|
+
* **La ventana del operador NO caduca por defecto** (decisión de este lote,
|
|
22
|
+
* justificada en el informe): un cutover no tiene duración conocida de
|
|
23
|
+
* antemano —duración de la ventana y tolerancia a la parada son magnitudes
|
|
24
|
+
* independientes, el argumento que tumbó el TTL fijo— y una ventana que
|
|
25
|
+
* caduca en mitad del cutover devuelve en SILENCIO el fail-open que este
|
|
26
|
+
* mecanismo existe para cerrar. El precio, declarado: si te olvidas de
|
|
27
|
+
* `authz:unfreeze`, la flota no escribe hasta que alguien lo ejecute — pero
|
|
28
|
+
* es un incidente RUIDOSO (cada 503 nombra el motivo y el comando que lo
|
|
29
|
+
* levanta), no una pérdida invisible. `--lease-ms` es el opt-in contrario:
|
|
30
|
+
* la ventana caduca sola pasados esos ms (no hay proceso que renueve: este
|
|
31
|
+
* comando termina), con SU contra declarado — si el cutover tarda más que el
|
|
32
|
+
* lease, los escritores vuelven SOLOS y sin aviso a mitad del cutover.
|
|
33
|
+
*
|
|
34
|
+
* Operación de PLATAFORMA: sin actor, sin rangos, no se expone por HTTP.
|
|
35
|
+
*/
|
|
36
|
+
export default class AuthzFreeze extends BaseCommand {
|
|
37
|
+
static commandName: string;
|
|
38
|
+
static description: string;
|
|
39
|
+
static options: CommandOptions;
|
|
40
|
+
reason: string | undefined;
|
|
41
|
+
leaseMs: number | undefined;
|
|
42
|
+
run(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=authz_freeze.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_freeze.d.ts","sourceRoot":"","sources":["../../commands/authz_freeze.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,MAAM,CAAC,WAAW,SAAiB;IACnC,MAAM,CAAC,WAAW,SAAuG;IAEzH,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAGO,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAO1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAE7B,GAAG;CAuCV"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
11
|
+
/**
|
|
12
|
+
* **Abre la ventana de CUTOVER: congela las escrituras del motor** (3b-7;
|
|
13
|
+
* condición explícita del juez del panel 3 — el intervalo entre la última
|
|
14
|
+
* pasada de `authz:reconcile` y el cambio de `config.default` es donde
|
|
15
|
+
* ocurre la pérdida, lo decide un humano y dura minutos u horas).
|
|
16
|
+
*
|
|
17
|
+
* node ace authz:freeze --reason="cutover a openfga"
|
|
18
|
+
* node ace authz:freeze --reason="cutover" --lease-ms=600000 # opt-in: caduca sola
|
|
19
|
+
* … node ace authz:reconcile --to=openfga --from=database … # corre DENTRO de la ventana
|
|
20
|
+
* … cambiar config.default y redesplegar …
|
|
21
|
+
* node ace authz:unfreeze
|
|
22
|
+
*
|
|
23
|
+
* Mientras la ventana está abierta, toda ESCRITURA del manager —en TODOS los
|
|
24
|
+
* procesos que comparten `authz_*`— es 503 `E_AUTHZ_FROZEN` reintentable;
|
|
25
|
+
* las lecturas siguen. `authz:reconcile` reconoce esta ventana como contexto
|
|
26
|
+
* propio: corre dentro sin tomar otro freeze y NO la levanta al terminar —
|
|
27
|
+
* cerrar el intervalo pasada→cutover es exactamente para lo que existe.
|
|
28
|
+
*
|
|
29
|
+
* **La ventana del operador NO caduca por defecto** (decisión de este lote,
|
|
30
|
+
* justificada en el informe): un cutover no tiene duración conocida de
|
|
31
|
+
* antemano —duración de la ventana y tolerancia a la parada son magnitudes
|
|
32
|
+
* independientes, el argumento que tumbó el TTL fijo— y una ventana que
|
|
33
|
+
* caduca en mitad del cutover devuelve en SILENCIO el fail-open que este
|
|
34
|
+
* mecanismo existe para cerrar. El precio, declarado: si te olvidas de
|
|
35
|
+
* `authz:unfreeze`, la flota no escribe hasta que alguien lo ejecute — pero
|
|
36
|
+
* es un incidente RUIDOSO (cada 503 nombra el motivo y el comando que lo
|
|
37
|
+
* levanta), no una pérdida invisible. `--lease-ms` es el opt-in contrario:
|
|
38
|
+
* la ventana caduca sola pasados esos ms (no hay proceso que renueve: este
|
|
39
|
+
* comando termina), con SU contra declarado — si el cutover tarda más que el
|
|
40
|
+
* lease, los escritores vuelven SOLOS y sin aviso a mitad del cutover.
|
|
41
|
+
*
|
|
42
|
+
* Operación de PLATAFORMA: sin actor, sin rangos, no se expone por HTTP.
|
|
43
|
+
*/
|
|
44
|
+
export default class AuthzFreeze extends BaseCommand {
|
|
45
|
+
static commandName = 'authz:freeze';
|
|
46
|
+
static description = 'Open the cutover window: freeze every engine write (503 retryable) fleet-wide until authz:unfreeze';
|
|
47
|
+
static options = {
|
|
48
|
+
startApp: true,
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
if (!this.reason) {
|
|
52
|
+
this.logger.error("Falta --reason: 'node ace authz:freeze --reason=\"cutover a openfga\"'. Cada escritura rechazada se lo " +
|
|
53
|
+
'enseña a quien la intentó: una ventana sin motivo es un 503 misterioso para todos tus admins.');
|
|
54
|
+
this.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const { default: authorization } = await import('../services/main.js');
|
|
58
|
+
try {
|
|
59
|
+
const token = await authorization.freeze(this.reason, {
|
|
60
|
+
kind: 'operator',
|
|
61
|
+
leaseMs: this.leaseMs === undefined ? null : this.leaseMs,
|
|
62
|
+
});
|
|
63
|
+
this.logger.success(`Ventana de cutover ABIERTA (fence ${token.fence}): las escrituras del motor responden 503 reintentable ` +
|
|
64
|
+
`en toda la flota; las lecturas siguen.`);
|
|
65
|
+
if (this.leaseMs === undefined) {
|
|
66
|
+
this.logger.info('La ventana NO caduca sola: ciérrala con `node ace authz:unfreeze` cuando el cutover termine.');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this.logger.warning(`La ventana caduca SOLA en ${this.leaseMs} ms y nadie la renueva: si el cutover tarda más, ` +
|
|
70
|
+
'los escritores vuelven sin aviso. Ciérrala antes con `node ace authz:unfreeze`.');
|
|
71
|
+
}
|
|
72
|
+
this.logger.info('Lo que la ventana NO congela: syncAuthzCatalog, manager.driver() y el árbol SQL del consumidor.');
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error?.code === 'E_AUTHZ_FREEZE_HELD') {
|
|
76
|
+
this.logger.error(error.message);
|
|
77
|
+
this.exitCode = 1;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
__decorate([
|
|
85
|
+
flags.string({ description: 'Why the window is open — every rejected write shows it to its caller' }),
|
|
86
|
+
__metadata("design:type", Object)
|
|
87
|
+
], AuthzFreeze.prototype, "reason", void 0);
|
|
88
|
+
__decorate([
|
|
89
|
+
flags.number({
|
|
90
|
+
name: 'lease-ms',
|
|
91
|
+
description: 'Opt-in expiry: the window lifts ITSELF after this many ms (nobody renews it — beware a cutover slower than the lease)',
|
|
92
|
+
}),
|
|
93
|
+
__metadata("design:type", Object)
|
|
94
|
+
], AuthzFreeze.prototype, "leaseMs", void 0);
|
|
95
|
+
//# sourceMappingURL=authz_freeze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz_freeze.js","sourceRoot":"","sources":["../../commands/authz_freeze.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,MAAM,CAAC,WAAW,GAAG,cAAc,CAAA;IACnC,MAAM,CAAC,WAAW,GAAG,oGAAoG,CAAA;IAEzH,MAAM,CAAC,OAAO,GAAmB;QAC/B,QAAQ,EAAE,IAAI;KACf,CAAA;IAYD,KAAK,CAAC,GAAG;QACP,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,yGAAyG;gBACvG,+FAA+F,CAClG,CAAA;YACD,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;YACjB,OAAM;QACR,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAA;QACtE,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE;gBACpD,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO;aAC1D,CAAC,CAAA;YACF,IAAI,CAAC,MAAM,CAAC,OAAO,CACjB,qCAAqC,KAAK,CAAC,KAAK,yDAAyD;gBACvG,wCAAwC,CAC3C,CAAA;YACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8FAA8F,CAAC,CAAA;YAClH,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,OAAO,CACjB,6BAA6B,IAAI,CAAC,OAAO,mDAAmD;oBAC1F,iFAAiF,CACpF,CAAA;YACH,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,iGAAiG,CAClG,CAAA;QACH,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,IAAI,KAAK,EAAE,IAAI,KAAK,qBAAqB,EAAE,CAAC;gBAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;gBAChC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;gBACjB,OAAM;YACR,CAAC;YACD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;;AA/CO;IADP,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,sEAAsE,EAAE,CAAC;;2CACpE;AAO1B;IALP,KAAK,CAAC,MAAM,CAAC;QACZ,IAAI,EAAE,UAAU;QAChB,WAAW,EACT,uHAAuH;KAC1H,CAAC;;4CACiC"}
|