@jantstack/adonis-authz 2.5.0-alpha.2 → 2.5.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +743 -28
- package/build/commands/authz_catalog_diff.d.ts +0 -1
- package/build/commands/authz_catalog_diff.js +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts +0 -1
- package/build/commands/authz_catalog_prune_orphans.js +0 -1
- package/build/commands/authz_catalog_sync.d.ts +0 -1
- package/build/commands/authz_catalog_sync.js +0 -1
- package/build/commands/authz_freeze.d.ts +0 -1
- package/build/commands/authz_freeze.js +0 -1
- package/build/commands/authz_reconcile.d.ts +0 -1
- package/build/commands/authz_reconcile.js +0 -1
- package/build/commands/authz_relations_reconcile.d.ts +6 -1
- package/build/commands/authz_relations_reconcile.js +163 -5
- package/build/commands/authz_scopes_relay.d.ts +0 -1
- package/build/commands/authz_scopes_relay.js +49 -5
- package/build/commands/authz_scopes_requeue.d.ts +0 -1
- package/build/commands/authz_scopes_requeue.js +14 -2
- package/build/commands/authz_unfreeze.d.ts +0 -1
- package/build/commands/authz_unfreeze.js +0 -1
- package/build/commands/main.d.ts +0 -1
- package/build/commands/main.js +0 -1
- package/build/commands/openfga_provision.d.ts +72 -1
- package/build/commands/openfga_provision.js +236 -4
- package/build/configure.d.ts +0 -1
- package/build/configure.js +0 -1
- package/build/index.d.ts +11 -9
- package/build/index.js +12 -5
- package/build/providers/authz_provider.d.ts +0 -1
- package/build/providers/authz_provider.js +0 -1
- package/build/services/main.d.ts +0 -1
- package/build/services/main.js +0 -1
- package/build/services/relations.d.ts +0 -1
- package/build/services/relations.js +0 -1
- package/build/src/catalog/catalog.d.ts +0 -1
- package/build/src/catalog/catalog.js +0 -1
- package/build/src/catalog/catalog_cache.d.ts +13 -1
- package/build/src/catalog/catalog_cache.js +25 -1
- package/build/src/clock.d.ts +0 -1
- package/build/src/clock.js +0 -1
- package/build/src/define_config.d.ts +0 -1
- package/build/src/define_config.js +24 -1
- package/build/src/drivers/database_driver.d.ts +6 -1
- package/build/src/drivers/database_driver.js +22 -2
- package/build/src/drivers/database_relations_driver.d.ts +46 -5
- package/build/src/drivers/database_relations_driver.js +297 -59
- package/build/src/drivers/openfga_driver.d.ts +159 -26
- package/build/src/drivers/openfga_driver.js +980 -231
- package/build/src/drivers/openfga_facts.d.ts +112 -1
- package/build/src/drivers/openfga_facts.js +351 -11
- package/build/src/drivers/openfga_relations_driver.d.ts +109 -8
- package/build/src/drivers/openfga_relations_driver.js +817 -53
- package/build/src/errors.d.ts +233 -1
- package/build/src/errors.js +283 -1
- package/build/src/expiry.d.ts +51 -2
- package/build/src/expiry.js +96 -2
- package/build/src/freeze.d.ts +0 -1
- package/build/src/freeze.js +0 -1
- package/build/src/hierarchical_resolver.d.ts +0 -1
- package/build/src/hierarchical_resolver.js +0 -1
- package/build/src/http/app_access_middleware.d.ts +0 -1
- package/build/src/http/app_access_middleware.js +0 -1
- package/build/src/http/resource_access_middleware.d.ts +0 -1
- package/build/src/http/resource_access_middleware.js +0 -1
- package/build/src/identity.d.ts +2 -1
- package/build/src/identity.js +4 -1
- package/build/src/manager.d.ts +0 -1
- package/build/src/manager.js +164 -31
- package/build/src/memoize_ancestors.d.ts +0 -1
- package/build/src/memoize_ancestors.js +0 -1
- package/build/src/models/authz_assignment.d.ts +5 -6
- package/build/src/models/authz_assignment.js +0 -1
- package/build/src/models/authz_deny.d.ts +5 -6
- package/build/src/models/authz_deny.js +0 -1
- package/build/src/models/authz_permission.d.ts +5 -6
- package/build/src/models/authz_permission.js +0 -1
- package/build/src/models/authz_role.d.ts +5 -6
- package/build/src/models/authz_role.js +0 -1
- package/build/src/models/authz_role_permission.d.ts +5 -6
- package/build/src/models/authz_role_permission.js +0 -1
- package/build/src/openfga.d.ts +3 -4
- package/build/src/openfga.js +5 -3
- package/build/src/reconcile.d.ts +0 -1
- package/build/src/reconcile.js +0 -1
- package/build/src/relation_partition_trigger.d.ts +0 -1
- package/build/src/relation_partition_trigger.js +0 -1
- package/build/src/relations/check_many.d.ts +1 -2
- package/build/src/relations/check_many.js +6 -5
- package/build/src/relations/define_relations_config.d.ts +140 -5
- package/build/src/relations/define_relations_config.js +205 -6
- package/build/src/relations/deny_reach.d.ts +102 -0
- package/build/src/relations/deny_reach.js +230 -0
- package/build/src/relations/group_depth.d.ts +215 -0
- package/build/src/relations/group_depth.js +453 -0
- package/build/src/relations/manager.d.ts +46 -8
- package/build/src/relations/manager.js +178 -16
- package/build/src/relations/page.d.ts +19 -0
- package/build/src/relations/page.js +74 -0
- package/build/src/relations/reconcile.d.ts +274 -2
- package/build/src/relations/reconcile.js +1006 -162
- package/build/src/relations/tuple_key.d.ts +0 -1
- package/build/src/relations/tuple_key.js +0 -1
- package/build/src/relations/tuple_order.d.ts +127 -0
- package/build/src/relations/tuple_order.js +599 -0
- package/build/src/relations_config_store.d.ts +57 -2
- package/build/src/relations_config_store.js +155 -5
- package/build/src/relations_reconcile.d.ts +0 -1
- package/build/src/relations_reconcile.js +0 -1
- package/build/src/scope_outbox.d.ts +0 -1
- package/build/src/scope_outbox.js +0 -1
- package/build/src/shared/backend_guard.d.ts +60 -1
- package/build/src/shared/backend_guard.js +179 -2
- package/build/src/shared/sql_expiry.d.ts +0 -1
- package/build/src/shared/sql_expiry.js +0 -1
- package/build/src/shared/transaction_guard.d.ts +0 -1
- package/build/src/shared/transaction_guard.js +0 -1
- package/build/src/sql_descendants.d.ts +0 -1
- package/build/src/sql_descendants.js +0 -1
- package/build/src/testing/contract.d.ts +7 -1
- package/build/src/testing/contract.js +77 -1
- package/build/src/testing/main.d.ts +1 -2
- package/build/src/testing/main.js +1 -2
- package/build/src/testing/migration_contract.d.ts +0 -1
- package/build/src/testing/migration_contract.js +0 -1
- package/build/src/testing/relations_contract.d.ts +25 -1
- package/build/src/testing/relations_contract.js +996 -49
- package/build/src/testing/relations_reconcile_contract.d.ts +9 -1
- package/build/src/testing/relations_reconcile_contract.js +207 -1
- package/build/src/testing/scope_outbox_contract.d.ts +0 -1
- package/build/src/testing/scope_outbox_contract.js +0 -1
- package/build/src/testing/scope_tree.d.ts +0 -1
- package/build/src/testing/scope_tree.js +0 -1
- package/build/src/testing/test_clock.d.ts +0 -1
- package/build/src/testing/test_clock.js +0 -1
- package/build/src/traits/authz_scopes.d.ts +0 -1
- package/build/src/traits/authz_scopes.js +0 -1
- package/build/src/traits/has_uuid.d.ts +5 -6
- package/build/src/traits/has_uuid.js +0 -1
- package/build/src/types.d.ts +170 -15
- package/build/src/types.js +6 -1
- package/build/stubs/config/authorization.stub +22 -6
- package/build/stubs/main.d.ts +0 -1
- package/build/stubs/main.js +0 -1
- package/package.json +4 -3
- package/build/commands/authz_catalog_diff.d.ts.map +0 -1
- package/build/commands/authz_catalog_diff.js.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.js.map +0 -1
- package/build/commands/authz_catalog_sync.d.ts.map +0 -1
- package/build/commands/authz_catalog_sync.js.map +0 -1
- package/build/commands/authz_freeze.d.ts.map +0 -1
- package/build/commands/authz_freeze.js.map +0 -1
- package/build/commands/authz_reconcile.d.ts.map +0 -1
- package/build/commands/authz_reconcile.js.map +0 -1
- package/build/commands/authz_relations_reconcile.d.ts.map +0 -1
- package/build/commands/authz_relations_reconcile.js.map +0 -1
- package/build/commands/authz_scopes_relay.d.ts.map +0 -1
- package/build/commands/authz_scopes_relay.js.map +0 -1
- package/build/commands/authz_scopes_requeue.d.ts.map +0 -1
- package/build/commands/authz_scopes_requeue.js.map +0 -1
- package/build/commands/authz_unfreeze.d.ts.map +0 -1
- package/build/commands/authz_unfreeze.js.map +0 -1
- package/build/commands/main.d.ts.map +0 -1
- package/build/commands/main.js.map +0 -1
- package/build/commands/openfga_provision.d.ts.map +0 -1
- package/build/commands/openfga_provision.js.map +0 -1
- package/build/configure.d.ts.map +0 -1
- package/build/configure.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/providers/authz_provider.d.ts.map +0 -1
- package/build/providers/authz_provider.js.map +0 -1
- package/build/services/main.d.ts.map +0 -1
- package/build/services/main.js.map +0 -1
- package/build/services/relations.d.ts.map +0 -1
- package/build/services/relations.js.map +0 -1
- package/build/src/catalog/catalog.d.ts.map +0 -1
- package/build/src/catalog/catalog.js.map +0 -1
- package/build/src/catalog/catalog_cache.d.ts.map +0 -1
- package/build/src/catalog/catalog_cache.js.map +0 -1
- package/build/src/clock.d.ts.map +0 -1
- package/build/src/clock.js.map +0 -1
- package/build/src/define_config.d.ts.map +0 -1
- package/build/src/define_config.js.map +0 -1
- package/build/src/drivers/database_driver.d.ts.map +0 -1
- package/build/src/drivers/database_driver.js.map +0 -1
- package/build/src/drivers/database_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/database_relations_driver.js.map +0 -1
- package/build/src/drivers/openfga_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_driver.js.map +0 -1
- package/build/src/drivers/openfga_facts.d.ts.map +0 -1
- package/build/src/drivers/openfga_facts.js.map +0 -1
- package/build/src/drivers/openfga_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_relations_driver.js.map +0 -1
- package/build/src/errors.d.ts.map +0 -1
- package/build/src/errors.js.map +0 -1
- package/build/src/expiry.d.ts.map +0 -1
- package/build/src/expiry.js.map +0 -1
- package/build/src/freeze.d.ts.map +0 -1
- package/build/src/freeze.js.map +0 -1
- package/build/src/hierarchical_resolver.d.ts.map +0 -1
- package/build/src/hierarchical_resolver.js.map +0 -1
- package/build/src/http/app_access_middleware.d.ts.map +0 -1
- package/build/src/http/app_access_middleware.js.map +0 -1
- package/build/src/http/resource_access_middleware.d.ts.map +0 -1
- package/build/src/http/resource_access_middleware.js.map +0 -1
- package/build/src/identity.d.ts.map +0 -1
- package/build/src/identity.js.map +0 -1
- package/build/src/manager.d.ts.map +0 -1
- package/build/src/manager.js.map +0 -1
- package/build/src/memoize_ancestors.d.ts.map +0 -1
- package/build/src/memoize_ancestors.js.map +0 -1
- package/build/src/models/authz_assignment.d.ts.map +0 -1
- package/build/src/models/authz_assignment.js.map +0 -1
- package/build/src/models/authz_deny.d.ts.map +0 -1
- package/build/src/models/authz_deny.js.map +0 -1
- package/build/src/models/authz_permission.d.ts.map +0 -1
- package/build/src/models/authz_permission.js.map +0 -1
- package/build/src/models/authz_role.d.ts.map +0 -1
- package/build/src/models/authz_role.js.map +0 -1
- package/build/src/models/authz_role_permission.d.ts.map +0 -1
- package/build/src/models/authz_role_permission.js.map +0 -1
- package/build/src/openfga.d.ts.map +0 -1
- package/build/src/openfga.js.map +0 -1
- package/build/src/reconcile.d.ts.map +0 -1
- package/build/src/reconcile.js.map +0 -1
- package/build/src/relation_partition_trigger.d.ts.map +0 -1
- package/build/src/relation_partition_trigger.js.map +0 -1
- package/build/src/relations/check_many.d.ts.map +0 -1
- package/build/src/relations/check_many.js.map +0 -1
- package/build/src/relations/define_relations_config.d.ts.map +0 -1
- package/build/src/relations/define_relations_config.js.map +0 -1
- package/build/src/relations/manager.d.ts.map +0 -1
- package/build/src/relations/manager.js.map +0 -1
- package/build/src/relations/reconcile.d.ts.map +0 -1
- package/build/src/relations/reconcile.js.map +0 -1
- package/build/src/relations/tuple_key.d.ts.map +0 -1
- package/build/src/relations/tuple_key.js.map +0 -1
- package/build/src/relations_config_store.d.ts.map +0 -1
- package/build/src/relations_config_store.js.map +0 -1
- package/build/src/relations_reconcile.d.ts.map +0 -1
- package/build/src/relations_reconcile.js.map +0 -1
- package/build/src/scope_outbox.d.ts.map +0 -1
- package/build/src/scope_outbox.js.map +0 -1
- package/build/src/shared/backend_guard.d.ts.map +0 -1
- package/build/src/shared/backend_guard.js.map +0 -1
- package/build/src/shared/sql_expiry.d.ts.map +0 -1
- package/build/src/shared/sql_expiry.js.map +0 -1
- package/build/src/shared/transaction_guard.d.ts.map +0 -1
- package/build/src/shared/transaction_guard.js.map +0 -1
- package/build/src/sql_descendants.d.ts.map +0 -1
- package/build/src/sql_descendants.js.map +0 -1
- package/build/src/testing/contract.d.ts.map +0 -1
- package/build/src/testing/contract.js.map +0 -1
- package/build/src/testing/main.d.ts.map +0 -1
- package/build/src/testing/main.js.map +0 -1
- package/build/src/testing/migration_contract.d.ts.map +0 -1
- package/build/src/testing/migration_contract.js.map +0 -1
- package/build/src/testing/relations_contract.d.ts.map +0 -1
- package/build/src/testing/relations_contract.js.map +0 -1
- package/build/src/testing/relations_reconcile_contract.d.ts.map +0 -1
- package/build/src/testing/relations_reconcile_contract.js.map +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts.map +0 -1
- package/build/src/testing/scope_outbox_contract.js.map +0 -1
- package/build/src/testing/scope_tree.d.ts.map +0 -1
- package/build/src/testing/scope_tree.js.map +0 -1
- package/build/src/testing/test_clock.d.ts.map +0 -1
- package/build/src/testing/test_clock.js.map +0 -1
- package/build/src/traits/authz_scopes.d.ts.map +0 -1
- package/build/src/traits/authz_scopes.js.map +0 -1
- package/build/src/traits/has_uuid.d.ts.map +0 -1
- package/build/src/traits/has_uuid.js.map +0 -1
- package/build/src/types.d.ts.map +0 -1
- package/build/src/types.js.map +0 -1
- package/build/stubs/main.d.ts.map +0 -1
- package/build/stubs/main.js.map +0 -1
|
@@ -41,6 +41,12 @@ export declare function relationsReconcileLines(report: RelationsReconcileReport
|
|
|
41
41
|
}>;
|
|
42
42
|
clean: boolean;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* **La pasada que revienta A MITAD dice qué quedó escrito, por partición** (plan 2.5 · lote D · D2; roadmap 2.5, 🟡 3
|
|
46
|
+
* del auditor de alpha.6): con el reporte parcial que viaja en el error (`error.report`, `partial: true`). Prefijo
|
|
47
|
+
* ESTABLE «pasada PARCIAL:» (punto 7). Función PURA.
|
|
48
|
+
*/
|
|
49
|
+
export declare function partialReportLines(report: RelationsReconcileReport): string[];
|
|
44
50
|
/**
|
|
45
51
|
* **`authz:relations:reconcile`** (Fase 4, lote 4-6) — migra las tuplas de
|
|
46
52
|
* RELACIÓN de un `RelationsDriver` a otro, idempotente y bidireccional, nunca
|
|
@@ -86,4 +92,3 @@ export default class AuthzRelationsReconcile extends BaseCommand {
|
|
|
86
92
|
partitionUuid: string | undefined;
|
|
87
93
|
run(): Promise<void>;
|
|
88
94
|
}
|
|
89
|
-
//# sourceMappingURL=authz_relations_reconcile.d.ts.map
|
|
@@ -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í.
|
|
@@ -63,6 +64,55 @@ export function relationsReconcileLines(report) {
|
|
|
63
64
|
message: `relaciones: escritas ${report.written}, actualizadas ${report.updated}, borradas ${report.deleted}, ` +
|
|
64
65
|
`iguales ${report.unchanged}, sobran ${report.extra}`,
|
|
65
66
|
});
|
|
67
|
+
// Lote D · D2: los denies, en su PROPIA línea (prefijo estable «denies:»), solo si la pasada tocó o vio alguno. Los
|
|
68
|
+
// contadores de arriba son allows + denies; esta línea es el subconjunto (analista §4.7).
|
|
69
|
+
const denies = report.denies;
|
|
70
|
+
if (denies && Object.values(denies).some((n) => n > 0)) {
|
|
71
|
+
lines.push({
|
|
72
|
+
level: 'log',
|
|
73
|
+
message: `denies: escritos ${denies.written}, rehechos ${denies.updated}, borrados ${denies.deleted}, ` +
|
|
74
|
+
`iguales ${denies.unchanged}, sobran ${denies.extra}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Lote D · D2 (S6/R2-5): un deny que la pasada NO puede migrar. Solo llega en `--dry-run` (la real lanza 500 con la
|
|
78
|
+
// misma letra): es DERIVA, una línea por tipo/relación y motivo, con el prefijo ESTABLE «deny NO migrable».
|
|
79
|
+
for (const refusal of report.denyRefused ?? []) {
|
|
80
|
+
lines.push({
|
|
81
|
+
level: 'error',
|
|
82
|
+
message: `deny NO migrable (${refusal.reason}) ${refusal.objectType}#${refusal.relation}: ${refusal.message}. ` +
|
|
83
|
+
'La pasada real se NIEGA (500 E_AUTHZ_DENY_MIGRATION_REFUSED) sin escribir nada — ni los allows: escribirlos sin ' +
|
|
84
|
+
// Cierre 1 de D2 · 🟡 6: la receta de SU motivo (la decide el plan, `refusal.recipe`), no una genérica.
|
|
85
|
+
`el deny concedería lo que el origen niega. Receta: ${refusal.recipe}`,
|
|
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
|
+
}
|
|
66
116
|
// R-15: la caducada del origen es la pérdida DECLARADA de la migración (como
|
|
67
117
|
// `expired` en roles, 3b-8 · B2): se dice, no es deriva.
|
|
68
118
|
if (report.skipped.expired > 0) {
|
|
@@ -108,6 +158,7 @@ export function relationsReconcileLines(report) {
|
|
|
108
158
|
// exit 0 y el consejo falso «repite con --prune» (que tampoco lo quitaba).
|
|
109
159
|
// El verificador queda rojo HASTA que se ejecute la receta — y dice qué y cómo.
|
|
110
160
|
let destResidue = 0;
|
|
161
|
+
let pendingRenewals = 0;
|
|
111
162
|
for (const detail of report.details ?? []) {
|
|
112
163
|
const what = `(${detail.reason}) ${detail.side}: ${detail.tuple} — ${detail.message}`;
|
|
113
164
|
if (detail.side === 'origen') {
|
|
@@ -115,10 +166,19 @@ export function relationsReconcileLines(report) {
|
|
|
115
166
|
level: 'error',
|
|
116
167
|
message: detail.reason === 'userset-unsupported'
|
|
117
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.`
|
|
118
|
-
:
|
|
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.`,
|
|
119
172
|
});
|
|
120
173
|
continue;
|
|
121
174
|
}
|
|
175
|
+
if (detail.reason === 'renewal-unsafe') {
|
|
176
|
+
// Cierre 2 de D2 · 🟠 N1: la membresía NO se renovó (su grupo transporta un deny y el destino renueva con dos
|
|
177
|
+
// Write). Deriva con su receta; prefijo ESTABLE «sin renovar (renewal-unsafe) destino:» (punto 7).
|
|
178
|
+
pendingRenewals += 1;
|
|
179
|
+
lines.push({ level: 'error', message: `sin renovar ${what}.` });
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
122
182
|
if (detail.pruned === true) {
|
|
123
183
|
// El paquete ya no lo emite (cierre 2 · N3): un reporte de un harness de terceros anterior.
|
|
124
184
|
lines.push({ level: 'log', message: `BORRADA por --prune ${what}.` });
|
|
@@ -135,9 +195,45 @@ export function relationsReconcileLines(report) {
|
|
|
135
195
|
level: 'error',
|
|
136
196
|
message: detail.reason === 'userset-unsupported'
|
|
137
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.`
|
|
138
|
-
: detail.reason === '
|
|
139
|
-
? `sobra ${what}. Es una
|
|
140
|
-
:
|
|
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.`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
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
|
|
208
|
+
// `deny-conditional`: cero escrituras y cero borrados en ella; el resto migra. Deriva (exit ≠ 0) con el prefijo ESTABLE
|
|
209
|
+
// «partición NEGADA» (punto 7): qué partición, por qué, que su destino queda COMO ESTABA, la deriva que queda a la vista
|
|
210
|
+
// y la receta (honesta: sin atomicRenewal no hay forma sin ventana).
|
|
211
|
+
for (const refusal of report.partitionsRefused ?? []) {
|
|
212
|
+
const causes = refusal.causes.map((cause) => `(${cause.reason}) ${cause.tuple}`);
|
|
213
|
+
// Cierre 6 de D2 · ⚪ D5-3 (E3): «COMO ESTABA» no es estático — cuándo empieza a conceder el destino y qué.
|
|
214
|
+
const evolves = (refusal.evolves ?? []).map((e) => `a partir de ${e.at} el destino concederá a ${e.member} lo que cortan ${e.cut.join(', ')} (si otro camino se lo da; el origen lo sigue negando)`);
|
|
215
|
+
const later = evolves.length > 0 ? ` OJO, ese estado EVOLUCIONA: ${evolves.slice(0, 5).join('; ')}${evolves.length > 5 ? '; …' : ''}.` : '';
|
|
216
|
+
lines.push({
|
|
217
|
+
level: 'error',
|
|
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) — ` +
|
|
219
|
+
`${causes.slice(0, 5).join('; ')}${causes.length > 5 ? '; …' : ''}. Cero escrituras y cero borrados en esta partición ` +
|
|
220
|
+
`(también en la pasada real${report.prune === true ? ' y con --prune' : ''}): su destino queda COMO ESTABA, con la deriva ` +
|
|
221
|
+
`previa a la vista (por escribir ${refusal.pending.write}, por renovar ${refusal.pending.update}, por borrar ` +
|
|
222
|
+
`${refusal.pending.delete}).${later} El resto de particiones migra. Receta: ${refusal.recipe}`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
// Cierre 2 de D2 · ⚪ N3/🟡 N3: el RESIDUO del orden — membresías de un ciclo del grafo de precedencias. El estado final
|
|
226
|
+
// es el correcto (no es deriva): se AVISA, con el prefijo ESTABLE «orden SIN garantía». Cierre 5 de D2 (⚪ D4-4): desde
|
|
227
|
+
// el cierre 4 una RENOVACIÓN de membresía entra en el grafo (en `now` ni entra ni sale: lo hace cuando vence la
|
|
228
|
+
// caducidad más corta), así que la letra lo dice, y dice que el corte puede conceder en un instante POSTERIOR.
|
|
229
|
+
if ((report.orderResidue?.length ?? 0) > 0) {
|
|
230
|
+
lines.push({
|
|
231
|
+
level: 'warning',
|
|
232
|
+
message: `orden SIN garantía: ${report.orderResidue.length} membresía(s) de esta pasada se contradicen en el orden (el mismo ` +
|
|
233
|
+
'sujeto entra o sale de grupos que se cortan entre sí —o una RENOVACIÓN, que entra o sale cuando vence la caducidad ' +
|
|
234
|
+
'más corta—: cada uno trae un deny que corta un allow del otro); un corte entre ellas puede conceder, ya o en un ' +
|
|
235
|
+
'instante posterior, lo que ni el estado inicial ni el final concedían en ese instante. El estado final es el ' +
|
|
236
|
+
`correcto. Si eso importa, aplícalas en dos pasadas. (${report.orderResidue.slice(0, 5).join('; ')}${report.orderResidue.length > 5 ? '; …' : ''})`,
|
|
141
237
|
});
|
|
142
238
|
}
|
|
143
239
|
// R-17: el seguro de borrado masivo. En una pasada REAL sin --allow-mass-delete
|
|
@@ -221,11 +317,38 @@ export function relationsReconcileLines(report) {
|
|
|
221
317
|
(report.skipped.invalid ?? 0) === 0 &&
|
|
222
318
|
(report.skipped.usersetUnsupported ?? 0) === 0 &&
|
|
223
319
|
destResidue === 0 &&
|
|
320
|
+
pendingRenewals === 0 &&
|
|
321
|
+
(report.partitionsRefused?.length ?? 0) === 0 &&
|
|
322
|
+
(report.denyRefused?.length ?? 0) === 0 &&
|
|
323
|
+
(report.denyReach?.findings.length ?? 0) === 0 &&
|
|
324
|
+
(report.denyReach?.stale.length ?? 0) === 0 &&
|
|
224
325
|
!(report.massDelete && !massDeleteLifted) &&
|
|
225
326
|
!(report.frozen?.lapsed === true) &&
|
|
226
327
|
(!report.dryRun || cambios === 0);
|
|
227
328
|
return { lines, clean };
|
|
228
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* **La pasada que revienta A MITAD dice qué quedó escrito, por partición** (plan 2.5 · lote D · D2; roadmap 2.5, 🟡 3
|
|
332
|
+
* del auditor de alpha.6): con el reporte parcial que viaja en el error (`error.report`, `partial: true`). Prefijo
|
|
333
|
+
* ESTABLE «pasada PARCIAL:» (punto 7). Función PURA.
|
|
334
|
+
*/
|
|
335
|
+
export function partialReportLines(report) {
|
|
336
|
+
const lines = [
|
|
337
|
+
`pasada PARCIAL: escritas ${report.written}, actualizadas ${report.updated}, borradas ${report.deleted} antes del fallo ` +
|
|
338
|
+
'(la pasada es idempotente: repítela para terminar).',
|
|
339
|
+
];
|
|
340
|
+
// Cierre 2 de D2 · 🟠 N1: la escritura que FALLÓ ya había aplicado parte — una renovación de `openfga` (delete + write)
|
|
341
|
+
// cuyo write no entró: los contadores no la cuentan, pero el destino CAMBIÓ (jamás «sin escribir nada»).
|
|
342
|
+
if (report.written + report.updated + report.deleted === 0) {
|
|
343
|
+
lines.push('pasada PARCIAL: la escritura que falló YA había aplicado parte (una renovación del destino: el delete entró y el write ' +
|
|
344
|
+
'no) — el destino CAMBIÓ aunque los contadores digan 0; repite la pasada.');
|
|
345
|
+
}
|
|
346
|
+
for (const row of report.partitions ?? []) {
|
|
347
|
+
lines.push(`pasada PARCIAL: partición ${row.partition} · ${row.status === 'done' ? 'TERMINADA' : row.status === 'failed' ? 'REVENTÓ aquí' : row.status === 'refused' ? 'NEGADA (cero escrituras)' : 'sin tocar'}` +
|
|
348
|
+
` (escritas ${row.written}, actualizadas ${row.updated}, borradas ${row.deleted}; denies escritos ${row.denies.written})`);
|
|
349
|
+
}
|
|
350
|
+
return lines;
|
|
351
|
+
}
|
|
229
352
|
/**
|
|
230
353
|
* **`authz:relations:reconcile`** (Fase 4, lote 4-6) — migra las tuplas de
|
|
231
354
|
* RELACIÓN de un `RelationsDriver` a otro, idempotente y bidireccional, nunca
|
|
@@ -373,6 +496,19 @@ export default class AuthzRelationsReconcile extends BaseCommand {
|
|
|
373
496
|
else {
|
|
374
497
|
this.logger.info(`Config del destino: leída de authz_relations_config (${toConfig.objectTypes.length} tipo(s)): la deriva del modelo se vigila.`);
|
|
375
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
|
+
}
|
|
376
512
|
// L-1 · J1: la pasada que ESCRIBE corre bajo la ventana durable del
|
|
377
513
|
// manager de roles (la misma fila `id = 2`, toda la flota); `--dry-run` no.
|
|
378
514
|
// Los drivers los construye `runRelationsReconcile`, DESPUÉS del guard.
|
|
@@ -387,6 +523,7 @@ export default class AuthzRelationsReconcile extends BaseCommand {
|
|
|
387
523
|
toKey: this.to,
|
|
388
524
|
fromKey,
|
|
389
525
|
...(toConfig ? { toConfig } : {}),
|
|
526
|
+
...(denyReach ? { denyReach } : {}),
|
|
390
527
|
});
|
|
391
528
|
}
|
|
392
529
|
catch (error) {
|
|
@@ -397,6 +534,28 @@ export default class AuthzRelationsReconcile extends BaseCommand {
|
|
|
397
534
|
this.exitCode = 1;
|
|
398
535
|
return;
|
|
399
536
|
}
|
|
537
|
+
// Lote D · D2 (S6/R2-5): la puerta del deny del DESTINO negó la pasada en la fase de plan, con CERO escrituras.
|
|
538
|
+
// La letra del error empieza por el prefijo estable «deny NO migrable:».
|
|
539
|
+
if (error?.code === 'E_AUTHZ_DENY_MIGRATION_REFUSED') {
|
|
540
|
+
this.logger.error(String(error.message ?? error));
|
|
541
|
+
this.exitCode = 1;
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
// Lote D · D2: la pasada reventó A MITAD — el reporte parcial por partición viaja en el error.
|
|
545
|
+
if (error?.report?.partial === true) {
|
|
546
|
+
for (const line of partialReportLines(error.report))
|
|
547
|
+
this.logger.error(line);
|
|
548
|
+
this.logger.error(String(error?.message ?? error));
|
|
549
|
+
this.exitCode = 1;
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
// ⚪ 10 (cierre 1 de D2): reventó ANTES de escribir nada — el reporte viaja con `partial: false`. La línea del
|
|
553
|
+
// error con su prefijo ESTABLE, sin «PARCIAL» (la marca de escritura parcial tampoco está).
|
|
554
|
+
if (error?.report && error.report.partial === false) {
|
|
555
|
+
this.logger.error(`pasada FALLIDA sin escribir nada: ${String(error?.message ?? error)} (la pasada es idempotente: repítela).`);
|
|
556
|
+
this.exitCode = 1;
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
400
559
|
// L-1 · J1: un freeze vivo de OTRA pasada (423): dos migraciones no se
|
|
401
560
|
// pisan; el mensaje nombra al dueño y cómo se levanta.
|
|
402
561
|
if (error?.code === 'E_AUTHZ_FREEZE_HELD' || error?.code === 'E_AUTHZ_OPERATOR_WINDOW_EXPIRED') {
|
|
@@ -473,4 +632,3 @@ __decorate([
|
|
|
473
632
|
flags.string({ description: 'Partition (tenant) scope uuid; required with --partition-type' }),
|
|
474
633
|
__metadata("design:type", Object)
|
|
475
634
|
], AuthzRelationsReconcile.prototype, "partitionUuid", void 0);
|
|
476
|
-
//# sourceMappingURL=authz_relations_reconcile.js.map
|
|
@@ -252,8 +252,23 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
252
252
|
// Qué fue de cada entrada, una a una (`entries`): la pasada no es atómica
|
|
253
253
|
// y un contador no permite retomar nada. Lo aparcado ya se dijo arriba.
|
|
254
254
|
for (const entry of report.entries) {
|
|
255
|
-
if (entry.status === 'applied')
|
|
256
|
-
|
|
255
|
+
if (entry.status === 'applied') {
|
|
256
|
+
// Asentar · cierre 1 (auditor ⚪ 8): la auditoría dice el padre APLICADO, no solo el encolado.
|
|
257
|
+
if (entry.reason === 'superseded') {
|
|
258
|
+
this.logger.warning(`aplicado SIN arista (superseded: el nodo ya no existe y su detached está en la cola): ${linea(entry)}`);
|
|
259
|
+
}
|
|
260
|
+
else if (entry.reason === 'consumer-parent' && entry.appliedParent) {
|
|
261
|
+
// 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
|
|
262
|
+
// caché rancia con dos movimientos del nodo dentro de su TTL son INDISTINGUIBLES para el relay, y un cron no lee
|
|
263
|
+
// avisos: la carrera hace saltar la alarma una vez; la caché, cada vez que pase.
|
|
264
|
+
this.logger.warning(`aplicado con el padre del CONSUMIDOR ${entry.appliedParent.type}:${entry.appliedParent.uuid ?? ''} ` +
|
|
265
|
+
`(no el encolado; otra entrada del nodo lo respalda): ${linea(entry)} — sale ≠ 0: comprueba que el resolutor ` +
|
|
266
|
+
'del relay lee lo CONFIRMADO, sin caché ni réplica atrasada (authz:reconcile --dry-run lo contrasta)');
|
|
267
|
+
this.exitCode = 1;
|
|
268
|
+
}
|
|
269
|
+
else
|
|
270
|
+
this.logger.log(`aplicado: ${linea(entry)}`);
|
|
271
|
+
}
|
|
257
272
|
if (entry.status === 'deferred') {
|
|
258
273
|
const motivo = entry.reason?.startsWith('parked:')
|
|
259
274
|
? `aplazado por APARCADO (${entry.reason}): no se aplica hasta que alguien mire esa entrada`
|
|
@@ -267,8 +282,38 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
267
282
|
notEvaluatedLines();
|
|
268
283
|
if (report.failures.length > 0) {
|
|
269
284
|
for (const item of report.failures) {
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
const target = `#${item.id} (${item.change.op} ${item.change.child.type}:${item.change.child.uuid ?? ''})`;
|
|
286
|
+
// Cierre-2 de L11 (⚪ C2; punto 7): el fallo de DESPLIEGUE tiene su prefijo ESTABLE —el backend SÍ respondió, lo
|
|
287
|
+
// que no cuadra es el despliegue con la config—; «el backend no respondió» queda para el 503 de verdad. Desde R3
|
|
288
|
+
// la marca la ponen también el store/modelo inexistente, las credenciales (401/403) y el 4xx desconocido.
|
|
289
|
+
if (item.deployment === true) {
|
|
290
|
+
this.logger.error(`fallo de DESPLIEGUE ${target}: ${item.error} — el despliegue no es el que espera la config (modelo fijado, ` +
|
|
291
|
+
'store, credenciales o un 4xx del servidor): este fallo NO consume intento (corrige el despliegue —' +
|
|
292
|
+
'openfga:provision publica el modelo de la config— y repite)');
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// Cierre de R3 (⚪ R3-A7; punto 7): la carrera y el límite de resolución son transitorios, pero el backend SÍ
|
|
296
|
+
// respondió — cada uno con su prefijo ESTABLE.
|
|
297
|
+
if (item.transient && item.code === 'E_AUTHZ_WRITE_CONFLICT') {
|
|
298
|
+
this.logger.error(`CARRERA en ${target}: ${item.error} — otro escritor (p. ej. un grant concurrente) tocó las mismas tuplas: ` +
|
|
299
|
+
'este fallo NO consume intento (la pasada siguiente relee y re-aplica)');
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (item.transient && item.code === 'E_AUTHZ_RESOLUTION_LIMIT') {
|
|
303
|
+
this.logger.error(`límite de RESOLUCIÓN en ${target}: ${item.error} — el backend respondió que el grafo excede lo que ` +
|
|
304
|
+
'resuelve (ver «Limits» en el README): este fallo NO consume intento');
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
// Lote «antes de RC» · F4 (⚪ RC-2 del auditor de R3; punto 7): un deadline vencido —el timeout del cliente, o un
|
|
308
|
+
// 504 / `deadline_exceeded` de una ESCRITURA, que es indeterminada desde el cierre de R3— no es «el backend no
|
|
309
|
+
// respondió»: con un 504 SÍ respondió (o un proxy), y la escritura pudo aplicarse. Prefijo ESTABLE propio.
|
|
310
|
+
if (item.transient && item.code === 'E_AUTHZ_BACKEND_TIMEOUT') {
|
|
311
|
+
this.logger.error(`PLAZO VENCIDO en ${target}: ${item.error} — venció el deadline (timeout del cliente, o un 504/deadline_exceeded ` +
|
|
312
|
+
'del servidor o de un proxy): si era una escritura pudo aplicarse o no (INDETERMINADA); la pasada siguiente ' +
|
|
313
|
+
'relee el store y re-aplica: este fallo NO consume intento');
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
this.logger.error(`falló ${target}: ${item.error}` +
|
|
272
317
|
(item.transient ? ' — el backend no respondió: este fallo NO consume intento' : ''));
|
|
273
318
|
}
|
|
274
319
|
this.logger.warning('Lo que DEPENDE de un cambio fallido no se adelanta (el orden del árbol importa: aparece como «aplazado»), ' +
|
|
@@ -341,4 +386,3 @@ __decorate([
|
|
|
341
386
|
}),
|
|
342
387
|
__metadata("design:type", Object)
|
|
343
388
|
], AuthzScopesRelay.prototype, "maxAge", void 0);
|
|
344
|
-
//# sourceMappingURL=authz_scopes_relay.js.map
|
|
@@ -90,7 +90,20 @@ export function requeueLines(input) {
|
|
|
90
90
|
const relay = report.relay;
|
|
91
91
|
let clean = true;
|
|
92
92
|
for (const entry of relay.entries) {
|
|
93
|
-
|
|
93
|
+
// Asentar · cierre 2 (gemelo del relay, C1-1): el padre APLICADO cuando no es el encolado, con su línea propia. No
|
|
94
|
+
// cambia `clean`: aquí hay un operador delante, y es justo la salida de una aparcada por DRIFT (re-notificar el
|
|
95
|
+
// padre actual y reencolar) la que termina en esta línea.
|
|
96
|
+
if (entry.status === 'applied' && entry.reason === 'consumer-parent' && entry.appliedParent) {
|
|
97
|
+
lines.push({
|
|
98
|
+
level: 'warning',
|
|
99
|
+
message: `aplicada con el padre del CONSUMIDOR ${entry.appliedParent.type}:${entry.appliedParent.uuid ?? ''} ` +
|
|
100
|
+
`(no el encolado; otra entrada del nodo lo respalda): ${linea(entry)}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else if (entry.status === 'applied' && entry.reason === 'superseded') {
|
|
104
|
+
lines.push({ level: 'warning', message: `aplicada SIN arista (superseded: el nodo ya no existe y su detached está en la cola): ${linea(entry)}` });
|
|
105
|
+
}
|
|
106
|
+
else if (entry.status === 'applied')
|
|
94
107
|
lines.push({ level: 'log', message: `aplicada: ${linea(entry)}` });
|
|
95
108
|
else if (entry.status === 'deferred') {
|
|
96
109
|
clean = false;
|
|
@@ -227,4 +240,3 @@ __decorate([
|
|
|
227
240
|
}),
|
|
228
241
|
__metadata("design:type", Object)
|
|
229
242
|
], 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
|
@@ -55,6 +55,13 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
55
55
|
url: string | undefined;
|
|
56
56
|
storeId: string | undefined;
|
|
57
57
|
name: string | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* **La salida humana de la guarda contra la MUTILACIÓN del modelo** (plan 2.5 · lote D · D2; analista §4.6, juez
|
|
60
|
+
* ronda 2 · R2-4 c). Sin ella, `--store-id` con un modelo nuevo que QUITA un `denied_<rel>` que el modelo actual del
|
|
61
|
+
* store tiene es exit 1 sin escribir nada. Con ella, el comando BARRE el store y solo publica si no queda NI UN deny
|
|
62
|
+
* vivo de esas relaciones: la bandera nunca autoriza apagar denies vivos.
|
|
63
|
+
*/
|
|
64
|
+
allowDropDeny: boolean | undefined;
|
|
58
65
|
/**
|
|
59
66
|
* Arranca la app hasta `boot()` — sin `start()` — para que `config/*`
|
|
60
67
|
* exista. Va en `prepare()` y no en `run()` porque `BaseCommand.exec()`
|
|
@@ -76,6 +83,46 @@ export default class OpenFgaProvision extends BaseCommand {
|
|
|
76
83
|
*/
|
|
77
84
|
prepare(): Promise<void>;
|
|
78
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;
|
|
96
|
+
/**
|
|
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;
|
|
98
|
+
* cierre 1 de D2 · 🟡 3 y 🟠 2). `openfga:provision --store-id` publica desde la config del PROCESO: si ese proceso ya
|
|
99
|
+
* no declara el opt-in de un tipo (un refactor, otra rama del config, un binario viejo), el modelo nuevo no trae
|
|
100
|
+
* `denied_<rel>` y TODOS los denies de ese tipo dejan de bloquear en cuanto un pod lo resuelve — fail-open en silencio.
|
|
101
|
+
*
|
|
102
|
+
* - **El ESTADO del store manda** (🟡 3): se leen TODOS los modelos del store (`readAuthorizationModels`, paginado y
|
|
103
|
+
* acotado por `PROVISION_MODEL_HISTORY_MAX`) y se barre por las `<tipo>#denied_<rel>` que ALGÚN modelo declaró y el
|
|
104
|
+
* nuevo no (una tupla `denied_*` solo puede existir si un modelo la declaró al escribirla). Un deny VIVO de esas
|
|
105
|
+
* relaciones ⇒ exit 1 con o sin la bandera. Hasta el cierre se comparaba solo con el ÚLTIMO modelo: un modelo
|
|
106
|
+
* intermedio sin `denied_*` (un ensayo de rollback con alpha.10) desactivaba la guarda.
|
|
107
|
+
* - **La INTENCIÓN la dice la bandera**: si el nuevo quita respecto del ÚLTIMO modelo, sin `--allow-drop-deny` es exit
|
|
108
|
+
* 1 aunque no quede ningún deny (retirar un opt-in se pide en voz alta).
|
|
109
|
+
* - Devuelve las relaciones a RE-CONTAR antes de publicar (🟠 2), o `null` si negó. Todas las ramas fallidas empiezan
|
|
110
|
+
* por el prefijo ESTABLE «provision NEGADO:» (punto 7), también un barrido que falla a medias (⚪ 10 c).
|
|
111
|
+
*/
|
|
112
|
+
private guardDroppedDenies;
|
|
113
|
+
/**
|
|
114
|
+
* El barrido; `null` (y exit 1 con el prefijo estable) si falla a medias (⚪ 10 c). Cierre 2 de D2 · 🟡 N4: en STREAMING
|
|
115
|
+
* —página a página, sin cargar el store en memoria y SIN cota de tuplas—: hasta el cierre 2, un store de más de 1 M
|
|
116
|
+
* tuplas dejaba `provision --store-id` en exit 1 PARA SIEMPRE tras retirar un opt-in (la historia de modelos no se
|
|
117
|
+
* borra en FGA), sin bandera que lo salvara. Lo que sigue siendo ruidoso: un servidor que no avanza (token repetido).
|
|
118
|
+
*/
|
|
119
|
+
private sweep;
|
|
120
|
+
/**
|
|
121
|
+
* **El RECUENTO justo antes de publicar** (cierre 1 de D2 · 🟠 2, TOCTOU): otro barrido de las mismas relaciones; un
|
|
122
|
+
* deny que llegó DURANTE o DESPUÉS del primero ⇒ exit 1 y CERO modelos. Es la señal de que la flota sigue en
|
|
123
|
+
* `deny: 'write'` (el paso 0 del procedimiento no se hizo).
|
|
124
|
+
*/
|
|
125
|
+
private recountBeforePublish;
|
|
79
126
|
/**
|
|
80
127
|
* Los permisos del modelo, o `null` (y exit 1) si no hay ninguno: un modelo
|
|
81
128
|
* `facts` sin permisos no puede responder a nada, y descubrirlo aquí es
|
|
@@ -112,4 +159,28 @@ export declare function permissionsOfCatalogs(config: any): Promise<string[]>;
|
|
|
112
159
|
export declare function relationsConfigOf(config: any): {
|
|
113
160
|
objectTypes: readonly any[];
|
|
114
161
|
} | undefined;
|
|
115
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Cierre 1 de D2 · 🟡 3: tope de la HISTORIA de modelos que `provision --store-id` lee para juzgar el estado del store
|
|
164
|
+
* (cada modelo publicado es una versión): pasarse es exit 1 (`provision NEGADO:`), nunca juzgar con una historia cortada.
|
|
165
|
+
*/
|
|
166
|
+
export declare const PROVISION_MODEL_HISTORY_MAX = 10000;
|
|
167
|
+
/**
|
|
168
|
+
* **Las `<tipo>#denied_<rel>` de TIPOS DE RELACIÓN que el modelo actual declara y el nuevo no** (plan 2.5 · lote D ·
|
|
169
|
+
* D2): la mutilación que la guarda de `openfga:provision` niega. Función PURA (dos modelos JSON en la forma de
|
|
170
|
+
* `readLatestAuthorizationModel` / `openFgaFactsModel`). Los `denied_<P>` de `scope` (roles) quedan fuera: retirar un
|
|
171
|
+
* permiso del catálogo no es retirar el opt-in de un tipo.
|
|
172
|
+
*/
|
|
173
|
+
export declare function droppedDenyRelations(current: any, next: any): string[];
|
|
174
|
+
/**
|
|
175
|
+
* **El barrido de `--allow-drop-deny`, en STREAMING** (cierre 2 de D2 · 🟡 N4): cuenta las tuplas vivas cuyo
|
|
176
|
+
* `<tipo>#<relación>` está en `dropped` leyendo el store ENTERO por páginas (`Read({})`: un `Read` por prefijo de objeto
|
|
177
|
+
* es 200 con cero tuplas) sin acumularlo — guarda el recuento y TRES ejemplos para la letra, nada más — y SIN cota de
|
|
178
|
+
* tuplas (hasta el cierre 2, 1 M ⇒ exit 1 para siempre). Exhaustivo o ruidoso: una página sin `tuples` o un
|
|
179
|
+
* `continuation_token` que se repite (el servidor no avanza) LANZA (el comando lo dice como «el barrido falló a medias»).
|
|
180
|
+
* Exportada para el caso que la juzga sin servidor.
|
|
181
|
+
*/
|
|
182
|
+
export declare function countLiveDenies(client: any, dropped: string[]): Promise<{
|
|
183
|
+
live: string[];
|
|
184
|
+
count: number;
|
|
185
|
+
read: number;
|
|
186
|
+
}>;
|