@jantstack/adonis-authz 2.0.0-alpha.1 → 2.4.0-alpha.10
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 +934 -40
- package/build/commands/authz_catalog_diff.d.ts.map +1 -1
- package/build/commands/authz_catalog_diff.js +1 -2
- package/build/commands/authz_catalog_diff.js.map +1 -1
- 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 +135 -0
- package/build/commands/authz_catalog_prune_orphans.js.map +1 -0
- package/build/commands/authz_catalog_sync.d.ts +17 -0
- package/build/commands/authz_catalog_sync.d.ts.map +1 -1
- package/build/commands/authz_catalog_sync.js +27 -4
- package/build/commands/authz_catalog_sync.js.map +1 -1
- 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 +94 -0
- package/build/commands/authz_freeze.js.map +1 -0
- package/build/commands/authz_reconcile.d.ts +111 -0
- package/build/commands/authz_reconcile.d.ts.map +1 -0
- package/build/commands/authz_reconcile.js +417 -0
- package/build/commands/authz_reconcile.js.map +1 -0
- package/build/commands/authz_relations_reconcile.d.ts +144 -0
- package/build/commands/authz_relations_reconcile.d.ts.map +1 -0
- package/build/commands/authz_relations_reconcile.js +438 -0
- package/build/commands/authz_relations_reconcile.js.map +1 -0
- package/build/commands/authz_scopes_relay.d.ts +97 -0
- package/build/commands/authz_scopes_relay.d.ts.map +1 -0
- package/build/commands/authz_scopes_relay.js +344 -0
- package/build/commands/authz_scopes_relay.js.map +1 -0
- package/build/commands/authz_scopes_requeue.d.ts +80 -0
- package/build/commands/authz_scopes_requeue.d.ts.map +1 -0
- package/build/commands/authz_scopes_requeue.js +230 -0
- package/build/commands/authz_scopes_requeue.js.map +1 -0
- package/build/commands/authz_unfreeze.d.ts +45 -0
- package/build/commands/authz_unfreeze.d.ts.map +1 -0
- package/build/commands/authz_unfreeze.js +157 -0
- package/build/commands/authz_unfreeze.js.map +1 -0
- package/build/commands/main.d.ts +16 -8
- package/build/commands/main.d.ts.map +1 -1
- package/build/commands/main.js +59 -6
- package/build/commands/main.js.map +1 -1
- package/build/commands/openfga_provision.d.ts +92 -4
- package/build/commands/openfga_provision.d.ts.map +1 -1
- package/build/commands/openfga_provision.js +155 -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 +50 -11
- package/build/index.d.ts.map +1 -1
- package/build/index.js +49 -7
- package/build/index.js.map +1 -1
- package/build/providers/authz_provider.d.ts +31 -2
- package/build/providers/authz_provider.d.ts.map +1 -1
- package/build/providers/authz_provider.js +65 -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.d.ts → catalog/catalog.d.ts} +41 -2
- package/build/src/catalog/catalog.d.ts.map +1 -0
- package/build/src/{catalog.js → catalog/catalog.js} +120 -14
- package/build/src/catalog/catalog.js.map +1 -0
- package/build/src/{catalog_cache.d.ts → catalog/catalog_cache.d.ts} +46 -22
- package/build/src/catalog/catalog_cache.d.ts.map +1 -0
- package/build/src/{catalog_cache.js → catalog/catalog_cache.js} +53 -43
- package/build/src/catalog/catalog_cache.js.map +1 -0
- package/build/src/define_config.d.ts +206 -3
- package/build/src/define_config.d.ts.map +1 -1
- package/build/src/define_config.js +11 -0
- package/build/src/define_config.js.map +1 -1
- package/build/src/drivers/database_driver.d.ts +128 -7
- package/build/src/drivers/database_driver.d.ts.map +1 -1
- package/build/src/drivers/database_driver.js +510 -24
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/database_relations_driver.d.ts +140 -0
- package/build/src/drivers/database_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/database_relations_driver.js +748 -0
- package/build/src/drivers/database_relations_driver.js.map +1 -0
- package/build/src/drivers/openfga_driver.d.ts +777 -122
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +2228 -477
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/openfga_facts.d.ts +384 -0
- package/build/src/drivers/openfga_facts.d.ts.map +1 -0
- package/build/src/drivers/openfga_facts.js +836 -0
- package/build/src/drivers/openfga_facts.js.map +1 -0
- package/build/src/drivers/openfga_relations_driver.d.ts +186 -0
- package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -0
- package/build/src/drivers/openfga_relations_driver.js +724 -0
- package/build/src/drivers/openfga_relations_driver.js.map +1 -0
- package/build/src/errors.d.ts +430 -6
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +453 -6
- package/build/src/errors.js.map +1 -1
- package/build/src/freeze.d.ts +200 -0
- package/build/src/freeze.d.ts.map +1 -0
- package/build/src/freeze.js +306 -0
- package/build/src/freeze.js.map +1 -0
- package/build/src/http/app_access_middleware.d.ts.map +1 -0
- 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 +74 -1
- package/build/src/identity.d.ts.map +1 -1
- package/build/src/identity.js +100 -2
- package/build/src/identity.js.map +1 -1
- package/build/src/manager.d.ts +549 -6
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +2243 -193
- package/build/src/manager.js.map +1 -1
- package/build/src/models/authz_assignment.d.ts +7 -7
- package/build/src/models/authz_assignment.d.ts.map +1 -1
- package/build/src/models/authz_deny.d.ts +7 -7
- package/build/src/models/authz_deny.d.ts.map +1 -1
- package/build/src/models/authz_permission.d.ts +7 -7
- package/build/src/models/authz_permission.d.ts.map +1 -1
- package/build/src/models/authz_role.d.ts +7 -7
- package/build/src/models/authz_role.d.ts.map +1 -1
- package/build/src/models/authz_role_permission.d.ts +7 -7
- package/build/src/models/authz_role_permission.d.ts.map +1 -1
- package/build/src/openfga.d.ts +18 -2
- package/build/src/openfga.d.ts.map +1 -1
- package/build/src/openfga.js +15 -1
- package/build/src/openfga.js.map +1 -1
- 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 +93 -0
- package/build/src/relation_partition_trigger.js.map +1 -0
- package/build/src/relations/define_relations_config.d.ts +109 -0
- package/build/src/relations/define_relations_config.d.ts.map +1 -0
- package/build/src/relations/define_relations_config.js +211 -0
- package/build/src/relations/define_relations_config.js.map +1 -0
- package/build/src/relations/manager.d.ts +138 -0
- package/build/src/relations/manager.d.ts.map +1 -0
- package/build/src/relations/manager.js +494 -0
- package/build/src/relations/manager.js.map +1 -0
- package/build/src/relations/reconcile.d.ts +137 -0
- package/build/src/relations/reconcile.d.ts.map +1 -0
- package/build/src/relations/reconcile.js +294 -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 +123 -0
- package/build/src/scope_outbox.d.ts.map +1 -0
- package/build/src/scope_outbox.js +532 -0
- package/build/src/scope_outbox.js.map +1 -0
- package/build/src/{drivers → shared}/backend_guard.d.ts +42 -0
- package/build/src/shared/backend_guard.d.ts.map +1 -0
- package/build/src/{drivers → shared}/backend_guard.js +78 -1
- package/build/src/shared/backend_guard.js.map +1 -0
- package/build/src/{drivers → shared}/sql_expiry.d.ts +2 -0
- package/build/src/shared/sql_expiry.d.ts.map +1 -0
- package/build/src/{drivers → shared}/sql_expiry.js +4 -0
- package/build/src/shared/sql_expiry.js.map +1 -0
- package/build/src/shared/transaction_guard.d.ts +53 -0
- package/build/src/shared/transaction_guard.d.ts.map +1 -0
- package/build/src/shared/transaction_guard.js +74 -0
- package/build/src/shared/transaction_guard.js.map +1 -0
- package/build/src/sql_descendants.d.ts +55 -1
- package/build/src/sql_descendants.d.ts.map +1 -1
- package/build/src/sql_descendants.js +75 -1
- package/build/src/sql_descendants.js.map +1 -1
- package/build/src/testing/contract.d.ts +129 -4
- package/build/src/testing/contract.d.ts.map +1 -1
- package/build/src/testing/contract.js +915 -177
- 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 +293 -0
- package/build/src/testing/migration_contract.d.ts.map +1 -0
- package/build/src/testing/migration_contract.js +589 -0
- package/build/src/testing/migration_contract.js.map +1 -0
- package/build/src/testing/relations_contract.d.ts +73 -0
- package/build/src/testing/relations_contract.d.ts.map +1 -0
- package/build/src/testing/relations_contract.js +1238 -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 +220 -0
- package/build/src/testing/relations_reconcile_contract.js.map +1 -0
- package/build/src/testing/scope_outbox_contract.d.ts +98 -0
- package/build/src/testing/scope_outbox_contract.d.ts.map +1 -0
- package/build/src/testing/scope_outbox_contract.js +677 -0
- package/build/src/testing/scope_outbox_contract.js.map +1 -0
- package/build/src/traits/authz_scopes.js +1 -1
- package/build/src/traits/authz_scopes.js.map +1 -1
- package/build/src/traits/has_uuid.d.ts +8 -8
- package/build/src/traits/has_uuid.d.ts.map +1 -1
- package/build/src/types.d.ts +1537 -84
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js +65 -0
- package/build/src/types.js.map +1 -1
- package/build/stubs/config/authorization.stub +149 -5
- package/build/stubs/migration.stub +136 -0
- package/build/stubs/scopes_outbox_migration.stub +57 -0
- package/package.json +4 -2
- package/build/commands/openfga_import.d.ts +0 -34
- package/build/commands/openfga_import.d.ts.map +0 -1
- package/build/commands/openfga_import.js +0 -97
- package/build/commands/openfga_import.js.map +0 -1
- package/build/src/catalog.d.ts.map +0 -1
- package/build/src/catalog.js.map +0 -1
- package/build/src/catalog_cache.d.ts.map +0 -1
- package/build/src/catalog_cache.js.map +0 -1
- package/build/src/drivers/backend_guard.d.ts.map +0 -1
- package/build/src/drivers/backend_guard.js.map +0 -1
- package/build/src/drivers/sql_expiry.d.ts.map +0 -1
- package/build/src/drivers/sql_expiry.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
- /package/build/src/{middleware → http}/app_access_middleware.d.ts +0 -0
- /package/build/src/{middleware → http}/app_access_middleware.js +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **`authz:reconcile` de relaciones** (Fase 4, lote 4-5) — migra las tuplas de
|
|
3
|
+
* relación de un `RelationsDriver` a otro, idempotente y bidireccional, nunca
|
|
4
|
+
* silenciosa. Es el análogo de `manager.reconcile` de roles, pero para el
|
|
5
|
+
* puerto `RelationsDriver`: solo hay HECHOS de relación (no árbol ni catálogo),
|
|
6
|
+
* así que es más simple —lee el ORIGEN por `enumerateRelations`, lee el DESTINO
|
|
7
|
+
* ENTERO (la única forma de ver lo que SOBRA), y aplica writes y —con
|
|
8
|
+
* `prune`— deletes—.
|
|
9
|
+
*
|
|
10
|
+
* **La lección M1/M3/T4 de la 3b** (el agujero del 3b-4·C1): un contrato que
|
|
11
|
+
* solo cruza `report.skipped` se pasa PERDIENDO datos. El verificador de
|
|
12
|
+
* verdad es el CENSO (`src/testing/relations_reconcile_contract.ts`): busca
|
|
13
|
+
* cada hecho sembrado en el destino UNO A UNO por el camino DIRECTO
|
|
14
|
+
* (`listSubjects`), no le pregunta al origen lleno. Un tramo omisivo o una
|
|
15
|
+
* pérdida de `subject_relation` (un userset migrado como holder) o de
|
|
16
|
+
* partición se ve ROJA ahí, no en los contadores.
|
|
17
|
+
*
|
|
18
|
+
* Pureza (regla 2): este módulo vive en `src/relations/` y solo depende del
|
|
19
|
+
* puerto (`../types.js`), los errores y la config de relaciones. NO importa
|
|
20
|
+
* `manager` ni `drivers/*`: los drivers concretos los inyecta el llamante (la
|
|
21
|
+
* plataforma), como `manager.reconcile` recibe el destino por su clave.
|
|
22
|
+
*
|
|
23
|
+
* **Es un PRIMITIVO PRIVADO** (cierre del auditor de alpha.10 · R1): no sabe
|
|
24
|
+
* cuál es el driver activo ni toma la ventana durable, así que ya no sale de la
|
|
25
|
+
* raíz del paquete. La vía pública es `runRelationsReconcile`
|
|
26
|
+
* (`commands/authz_relations_reconcile.ts`), que resuelve origen y destino por
|
|
27
|
+
* clave, niega la dirección rancia y congela; la guardia de superficie de
|
|
28
|
+
* `command_flags.spec.ts` impide re-exportarlo por ningún subpath.
|
|
29
|
+
*/
|
|
30
|
+
import { AuthorizationConfigError, MassReconcileRefusedError, RelationTypeUnknownError, RelationUnknownError, UnsupportedOperationError, } from '../errors.js';
|
|
31
|
+
import { systemClock } from '../clock.js';
|
|
32
|
+
import { isActiveExpiry, sameInstant } from '../expiry.js';
|
|
33
|
+
import { isRelUserset } from '../types.js';
|
|
34
|
+
/** La clave estable de una tupla de relación (partición + objeto + relación + sujeto). */
|
|
35
|
+
function subjectKey(subject) {
|
|
36
|
+
if (isRelUserset(subject))
|
|
37
|
+
return `${subject.object.type}:${subject.object.id}#${subject.relation}`;
|
|
38
|
+
return `${subject.type}:${subject.uuid}`;
|
|
39
|
+
}
|
|
40
|
+
function scopeKeyOf(scope) {
|
|
41
|
+
return scope.type === 'app' ? 'app' : `${scope.type}|${scope.uuid}`;
|
|
42
|
+
}
|
|
43
|
+
function tupleKey(tuple) {
|
|
44
|
+
return `${scopeKeyOf(tuple.partition)}|${tuple.object.type}:${tuple.object.id}#${tuple.relation}@${subjectKey(tuple.subject)}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Enumera TODAS las tuplas de las particiones pedidas, repartidas por clave de
|
|
48
|
+
* partición. UNA pasada con `enumerateRelationsMany` si el driver lo trae
|
|
49
|
+
* (alpha.6: en `openfga`, un barrido del store en vez de K); si no, una
|
|
50
|
+
* llamada de `enumerateRelations` por partición. En los dos casos se pagina el
|
|
51
|
+
* cursor hasta agotarlo, con las mismas guardas ruidosas (un cursor que se
|
|
52
|
+
* repite o más de 100 000 páginas es un error, nunca una enumeración corta).
|
|
53
|
+
*/
|
|
54
|
+
async function enumerateAllMany(driver, partitions, role) {
|
|
55
|
+
if (!driver.enumerateRelations || !driver.capabilities?.enumerateRelations) {
|
|
56
|
+
throw new UnsupportedOperationError('enumerateRelations', `ser ${role} de authz:reconcile de relaciones`, 'relations', `El driver ${role} no sabe enumerar sus tuplas (capacidad 'enumerateRelations': false). ` +
|
|
57
|
+
`Sin poder leer ${role === 'destino' ? 'el destino no se ve qué sobra' : 'el origen no hay nada que migrar'}.`);
|
|
58
|
+
}
|
|
59
|
+
const out = new Map();
|
|
60
|
+
for (const partition of partitions)
|
|
61
|
+
out.set(scopeKeyOf(partition), []);
|
|
62
|
+
const collect = (tuples) => {
|
|
63
|
+
for (const tuple of tuples) {
|
|
64
|
+
const bucket = out.get(scopeKeyOf(tuple.partition));
|
|
65
|
+
// Una tupla de una partición que no se pidió no es de esta pasada: el
|
|
66
|
+
// puerto promete «solo las pedidas», y si un driver de terceros trae de
|
|
67
|
+
// más, no se migra lo ajeno.
|
|
68
|
+
if (bucket)
|
|
69
|
+
bucket.push(tuple);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const many = driver.enumerateRelationsMany;
|
|
73
|
+
const fetch = typeof many === 'function' && partitions.length > 1
|
|
74
|
+
? [(after) => many.call(driver, partitions, after ? { after } : undefined)]
|
|
75
|
+
: partitions.map((partition) => (after) => driver.enumerateRelations(partition, after ? { after } : undefined));
|
|
76
|
+
for (const pageOf of fetch) {
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
let after;
|
|
79
|
+
let pages = 0;
|
|
80
|
+
do {
|
|
81
|
+
const page = await pageOf(after);
|
|
82
|
+
collect(page.tuples);
|
|
83
|
+
after = page.cursor || undefined;
|
|
84
|
+
if (after) {
|
|
85
|
+
if (seen.has(after))
|
|
86
|
+
throw new Error(`enumerateRelations (${role}): el cursor '${after}' se repite; no avanza`);
|
|
87
|
+
seen.add(after);
|
|
88
|
+
if (++pages > 100_000)
|
|
89
|
+
throw new Error(`enumerateRelations (${role}): más de 100000 páginas sin agotar`);
|
|
90
|
+
}
|
|
91
|
+
} while (after);
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Migra las tuplas de relación de `from` a `to` en `partition`. Idempotente
|
|
97
|
+
* (una segunda pasada no escribe nada) y bidireccional (funciona en cualquier
|
|
98
|
+
* sentido). Nunca silenciosa: devuelve `written`/`deleted`/`unchanged`/`extra`.
|
|
99
|
+
*/
|
|
100
|
+
export async function reconcileRelations(options) {
|
|
101
|
+
const { from, to, dryRun = false, prune = false, allowMassDelete = false, toConfig } = options;
|
|
102
|
+
const now = (options.now ?? systemClock)();
|
|
103
|
+
const partitions = partitionsOf(options);
|
|
104
|
+
// alpha.6: origen y destino se enumeran UNA vez para todas las particiones.
|
|
105
|
+
const enumeratedByKey = await enumerateAllMany(from, partitions, 'origen');
|
|
106
|
+
const destByKeyAll = await enumerateAllMany(to, partitions, 'destino');
|
|
107
|
+
// Primero se DECIDE todo (K diffs, K seguros R-17) y luego se escribe: un
|
|
108
|
+
// seguro que salte en la partición 3 no deja las dos primeras a medias.
|
|
109
|
+
const plans = [];
|
|
110
|
+
for (const partition of partitions) {
|
|
111
|
+
const enumerated = enumeratedByKey.get(scopeKeyOf(partition)) ?? [];
|
|
112
|
+
const destTuples = destByKeyAll.get(scopeKeyOf(partition)) ?? [];
|
|
113
|
+
plans.push(planPartition(partition, enumerated, destTuples, { now, prune, toConfig }));
|
|
114
|
+
}
|
|
115
|
+
const report = {
|
|
116
|
+
dryRun,
|
|
117
|
+
written: 0,
|
|
118
|
+
updated: 0,
|
|
119
|
+
deleted: 0,
|
|
120
|
+
unchanged: 0,
|
|
121
|
+
extra: 0,
|
|
122
|
+
modelDrift: [],
|
|
123
|
+
massDelete: false,
|
|
124
|
+
skipped: { expired: 0, undeclared: 0 },
|
|
125
|
+
};
|
|
126
|
+
for (const plan of plans) {
|
|
127
|
+
// **El seguro de borrado masivo** (R-17, paridad con `authz:reconcile` de
|
|
128
|
+
// roles / AA2). `prune` + un ORIGEN sin una sola tupla utilizable + un destino
|
|
129
|
+
// con tuplas que borrar es la firma de un `--from` equivocado o de un store
|
|
130
|
+
// vacío: vaciar el destino entero se rechaza con 500 antes de tocar nada,
|
|
131
|
+
// salvo `allowMassDelete`. En `--dry-run` no lanza, lo marca. «Utilizable» =
|
|
132
|
+
// una tupla VIVA del origen que cabe en el destino (las que `skipped`
|
|
133
|
+
// recoge no migran, así que no cuentan como respaldo).
|
|
134
|
+
if (plan.massDelete && !dryRun && !allowMassDelete) {
|
|
135
|
+
throw new MassReconcileRefusedError(`authz:relations:reconcile --prune borraría ${plan.toDelete.length} tupla(s) de relación del destino` +
|
|
136
|
+
`${partitions.length > 1 ? ` (partición ${scopeKeyOf(plan.partition)})` : ''} y el ` +
|
|
137
|
+
`ORIGEN no ha aportado NI UNA utilizable (${plan.enumerated} leídas` +
|
|
138
|
+
`${toConfig ? ', todas de tipos que el destino no declara' : ''}` +
|
|
139
|
+
`${plan.skipped.expired ? `, ${plan.skipped.expired} caducada(s)` : ''}). Eso es la firma de un --from ` +
|
|
140
|
+
`equivocado o de un store vacío, no de un destino que sobra: esta pasada dejaría la partición sin ` +
|
|
141
|
+
`una sola relación. Comprueba el --from y el store; si de verdad quieres vaciarlo, --allow-mass-delete.` +
|
|
142
|
+
`${partitions.length > 1 ? ' No se ha escrito nada en ninguna partición.' : ''}`);
|
|
143
|
+
}
|
|
144
|
+
report.massDelete ||= plan.massDelete;
|
|
145
|
+
report.skipped.expired += plan.skipped.expired;
|
|
146
|
+
report.skipped.undeclared += plan.skipped.undeclared;
|
|
147
|
+
for (const type of plan.modelDrift)
|
|
148
|
+
if (!report.modelDrift.includes(type))
|
|
149
|
+
report.modelDrift.push(type);
|
|
150
|
+
}
|
|
151
|
+
for (const plan of plans) {
|
|
152
|
+
let written = plan.toWrite.length;
|
|
153
|
+
let updated = plan.toUpdate.length;
|
|
154
|
+
if (!dryRun) {
|
|
155
|
+
// La caducidad viaja EXPLÍCITA (`Date` la fija, `null` la quita): un
|
|
156
|
+
// `relate` sin opciones PRESERVARÍA la del destino (invariante 10).
|
|
157
|
+
// L-0: el DRIVER del destino aplica F-05 (`assertRelationDeclared`), así que
|
|
158
|
+
// sin `toConfig` —o con una que no coincida con la del driver— es SU 422 el
|
|
159
|
+
// que embuda la tupla no declarada: se cuenta en `skipped.undeclared` y se
|
|
160
|
+
// descuenta de `written`/`updated`. Nunca un throw a mitad de pasada por
|
|
161
|
+
// una tupla que el reporte puede contar, y nunca un silencio.
|
|
162
|
+
for (const tuple of plan.toWrite) {
|
|
163
|
+
if (await relateOrSkip(to, tuple))
|
|
164
|
+
continue;
|
|
165
|
+
report.skipped.undeclared += 1;
|
|
166
|
+
written -= 1;
|
|
167
|
+
}
|
|
168
|
+
for (const tuple of plan.toUpdate) {
|
|
169
|
+
if (await relateOrSkip(to, tuple))
|
|
170
|
+
continue;
|
|
171
|
+
report.skipped.undeclared += 1;
|
|
172
|
+
updated -= 1;
|
|
173
|
+
}
|
|
174
|
+
if (prune) {
|
|
175
|
+
for (const tuple of plan.toDelete) {
|
|
176
|
+
await to.unrelate(tuple.subject, tuple.relation, tuple.object, tuple.partition);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
report.written += written;
|
|
181
|
+
report.updated += updated;
|
|
182
|
+
report.deleted += prune ? plan.toDelete.length : 0;
|
|
183
|
+
report.unchanged += plan.unchanged;
|
|
184
|
+
report.extra += prune ? 0 : plan.toDelete.length;
|
|
185
|
+
}
|
|
186
|
+
return report;
|
|
187
|
+
}
|
|
188
|
+
/** Exactamente uno de `partition` / `partitions` (y `partitions` no vacío): lo demás es 500 antes de leer nada. */
|
|
189
|
+
function partitionsOf(options) {
|
|
190
|
+
const { partition, partitions } = options;
|
|
191
|
+
if (partition !== undefined && partitions !== undefined) {
|
|
192
|
+
throw new AuthorizationConfigError('reconcileRelations: pasa `partition` O `partitions`, no los dos.');
|
|
193
|
+
}
|
|
194
|
+
// alpha.10 · 5, gemelo en la API: `{ type: 'app', uuid: 'x' }` reconciliaba la
|
|
195
|
+
// RAÍZ en silencio (la clave de `app` ignora el uuid), igual que la pareja
|
|
196
|
+
// `--partition-type=app --partition-uuid=x` del comando.
|
|
197
|
+
for (const p of partition !== undefined ? [partition] : Array.isArray(partitions) ? partitions : []) {
|
|
198
|
+
if (p?.type === 'app' && p.uuid !== null && p.uuid !== undefined) {
|
|
199
|
+
throw new AuthorizationConfigError(`reconcileRelations: la partición raíz no lleva uuid (llegó { type: 'app', uuid: ${JSON.stringify(p.uuid)} }). ` +
|
|
200
|
+
'O la raíz ({ type: app, uuid: null }) o el tipo del tenant.');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (partition !== undefined)
|
|
204
|
+
return [partition];
|
|
205
|
+
if (!Array.isArray(partitions) || partitions.length === 0) {
|
|
206
|
+
throw new AuthorizationConfigError('reconcileRelations: pasa `partition` (una) o `partitions` (varias, al menos una): sin partición no hay nada que migrar, ' +
|
|
207
|
+
'y una lista vacía no es una pasada en verde.');
|
|
208
|
+
}
|
|
209
|
+
// Dos referencias con la misma clave son la misma partición: se migra una vez.
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
return partitions.filter((p) => {
|
|
212
|
+
const key = scopeKeyOf(p);
|
|
213
|
+
if (seen.has(key))
|
|
214
|
+
return false;
|
|
215
|
+
seen.add(key);
|
|
216
|
+
return true;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function planPartition(partition, enumerated, destTuples, context) {
|
|
220
|
+
const { now, prune, toConfig } = context;
|
|
221
|
+
// R-15: la caducada LLEGA del origen (el puerto lo exige) y aquí se DESCARTA
|
|
222
|
+
// contándola: no se migra ni respalda nada en el destino. Decidido con UN
|
|
223
|
+
// `now` para toda la pasada.
|
|
224
|
+
// L-0: la tupla cuyo tipo o relación el DESTINO no declara (F-05) se
|
|
225
|
+
// descarta AQUÍ cuando hay `toConfig` —antes del driver, así `--dry-run` y la
|
|
226
|
+
// pasada real dan los mismos números— y tampoco respalda nada en el destino
|
|
227
|
+
// (una que ya estuviera allí es `extra`, y `--prune` la barre). El tipo se
|
|
228
|
+
// reporta además en `modelDrift` (deriva del modelo fusionado).
|
|
229
|
+
const skipped = { expired: 0, undeclared: 0 };
|
|
230
|
+
const modelDrift = [];
|
|
231
|
+
const sourceTuples = [];
|
|
232
|
+
for (const tuple of enumerated) {
|
|
233
|
+
if (!isActiveExpiry(tuple.expiresAt ?? null, now)) {
|
|
234
|
+
skipped.expired += 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (toConfig && !declaredIn(toConfig, tuple.object.type)) {
|
|
238
|
+
skipped.undeclared += 1;
|
|
239
|
+
if (!modelDrift.includes(tuple.object.type))
|
|
240
|
+
modelDrift.push(tuple.object.type);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (toConfig && !toConfig.isDeclared(tuple.object.type, tuple.relation)) {
|
|
244
|
+
skipped.undeclared += 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
sourceTuples.push(tuple);
|
|
248
|
+
}
|
|
249
|
+
const sourceByKey = new Map();
|
|
250
|
+
for (const tuple of sourceTuples)
|
|
251
|
+
sourceByKey.set(tupleKey(tuple), tuple);
|
|
252
|
+
const destByKey = new Map();
|
|
253
|
+
for (const tuple of destTuples)
|
|
254
|
+
destByKey.set(tupleKey(tuple), tuple);
|
|
255
|
+
const toWrite = [];
|
|
256
|
+
const toUpdate = [];
|
|
257
|
+
let unchanged = 0;
|
|
258
|
+
for (const [key, tuple] of sourceByKey) {
|
|
259
|
+
const present = destByKey.get(key);
|
|
260
|
+
if (!present)
|
|
261
|
+
toWrite.push(tuple);
|
|
262
|
+
// Misma clave, OTRA caducidad ⇒ se reescribe con la del origen (R-15).
|
|
263
|
+
else if (!sameInstant(present.expiresAt ?? null, tuple.expiresAt ?? null))
|
|
264
|
+
toUpdate.push(tuple);
|
|
265
|
+
else
|
|
266
|
+
unchanged += 1;
|
|
267
|
+
}
|
|
268
|
+
const toDelete = destTuples.filter((tuple) => !sourceByKey.has(tupleKey(tuple)));
|
|
269
|
+
const massDelete = prune && toDelete.length > 0 && sourceTuples.length === 0;
|
|
270
|
+
return { partition, enumerated: enumerated.length, toWrite, toUpdate, toDelete, unchanged, skipped, modelDrift, massDelete };
|
|
271
|
+
}
|
|
272
|
+
/** ¿El tipo de objeto cabe en el modelo fusionado del destino? (`group` es built-in: `hasType` lo incluye.) */
|
|
273
|
+
function declaredIn(config, objectType) {
|
|
274
|
+
return config.hasType(objectType);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Escribe la tupla en el destino; `false` si el DRIVER la rechazó por F-05
|
|
278
|
+
* (tipo o relación no declarados: 422 `E_AUTHZ_RELATION_TYPE_UNKNOWN` /
|
|
279
|
+
* `E_AUTHZ_RELATION_UNKNOWN`). Cualquier otro error se propaga tal cual.
|
|
280
|
+
*/
|
|
281
|
+
async function relateOrSkip(to, tuple) {
|
|
282
|
+
try {
|
|
283
|
+
await to.relate(tuple.subject, tuple.relation, tuple.object, tuple.partition, {
|
|
284
|
+
expiresAt: tuple.expiresAt ?? null,
|
|
285
|
+
});
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error instanceof RelationTypeUnknownError || error instanceof RelationUnknownError)
|
|
290
|
+
return false;
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/relations/reconcile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RelationsConfig, RelationsConfigSpec } from './relations/define_relations_config.js';
|
|
2
|
+
export interface RelationsConfigStoreOptions {
|
|
3
|
+
connection?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* **Guarda (persiste) la config de relaciones** bajo el gate de versión: la
|
|
8
|
+
* valida (⚪4/F-05 de config), la serializa a JSON y hace UPSERT de la fila
|
|
9
|
+
* `id=1` dentro de `withAuthzCatalogWrite` —que sube la versión compartida como
|
|
10
|
+
* última sentencia (invariante 14): un sync en otro proceso la ve en la
|
|
11
|
+
* siguiente pregunta—. Devuelve la config congelada.
|
|
12
|
+
*/
|
|
13
|
+
export declare function saveRelationsConfig(spec: RelationsConfigSpec, options?: RelationsConfigStoreOptions): Promise<RelationsConfig>;
|
|
14
|
+
/** Lee el spec persistido (o `null` si no hay relaciones declaradas — opt-in). */
|
|
15
|
+
export declare function readRelationsConfigSpec(options?: RelationsConfigStoreOptions): Promise<RelationsConfigSpec | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Lee la config persistida ya VALIDADA (o `null`). Es lo que hace 🟡3: un
|
|
18
|
+
* proceso SIN `defineRelationsConfig` en memoria recupera aquí los tipos de
|
|
19
|
+
* relación de la base, así que el modelo que republique los lleva.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readRelationsConfig(options?: RelationsConfigStoreOptions): Promise<RelationsConfig | null>;
|
|
22
|
+
//# sourceMappingURL=relations_config_store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations_config_store.d.ts","sourceRoot":"","sources":["../../src/relations_config_store.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAMlG,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,mBAAmB,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,eAAe,CAAC,CAc1B;AAED,kFAAkF;AAClF,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAMrC;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAGjC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **La config de relaciones PERSISTIDA** (Fase 4, lote 4-5 · 🟡3) — vive en la
|
|
3
|
+
* tabla `authz_relations_config`, bajo el gate de versión (invariante 14), como
|
|
4
|
+
* el catálogo. Es la copia de la config de relaciones en la BASE, para que
|
|
5
|
+
* cualquier proceso —también uno SIN `defineRelationsConfig` en memoria— lea
|
|
6
|
+
* los tipos declarados. Hoy la CONSUME `authz:relations:reconcile` (la config
|
|
7
|
+
* del DESTINO, para vigilar la deriva del modelo fusionado en `--dry-run`).
|
|
8
|
+
*
|
|
9
|
+
* Este módulo vive en `src/` (NO en `src/relations/`, que la regla 2 de pureza
|
|
10
|
+
* mantiene aislado del `db`): es la ruta que TOCA la base, como `catalog.ts`.
|
|
11
|
+
* Importa el TIPO y el validador de `src/relations/` (la dirección permitida:
|
|
12
|
+
* `relations/` no importa hacia afuera; los demás sí importan de él).
|
|
13
|
+
*
|
|
14
|
+
* **Nota (Fase 4-8):** el modelo fusionado del store lo publica
|
|
15
|
+
* `openfga:provision` leyendo los tipos de relación del CONFIG estático
|
|
16
|
+
* (`relations.config`, como `permissions` sale de `catalogs`), no de esta
|
|
17
|
+
* tabla: el comando es `startApp: false` (bootstrap del appliance, sin base) y
|
|
18
|
+
* no puede leer la base. Por eso el antiguo `republishFusedModel` —el
|
|
19
|
+
* republicador basado en esta tabla, con CAS sobre `model_id`— se retiró en
|
|
20
|
+
* 4-8: no tenía llamador de producción (la carrera que protegía no ocurre;
|
|
21
|
+
* republicar el modelo es un paso manual de operador, escritor único). Ver el
|
|
22
|
+
* informe 4-8. La columna `model_id` queda sin uso (cleanup futuro).
|
|
23
|
+
*/
|
|
24
|
+
import { systemClock } from './clock.js';
|
|
25
|
+
import { guardSql } from './shared/backend_guard.js';
|
|
26
|
+
import db from '@adonisjs/lucid/services/db';
|
|
27
|
+
import { invalidateAuthzCatalog, withAuthzCatalogWrite } from './catalog/catalog_cache.js';
|
|
28
|
+
import { defineRelationsConfig } from './relations/define_relations_config.js';
|
|
29
|
+
const TABLE = 'authz_relations_config';
|
|
30
|
+
const ROW_ID = 1;
|
|
31
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
32
|
+
function connectionOf(options) {
|
|
33
|
+
return options?.connection ? db.connection(options.connection) : db;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* **Guarda (persiste) la config de relaciones** bajo el gate de versión: la
|
|
37
|
+
* valida (⚪4/F-05 de config), la serializa a JSON y hace UPSERT de la fila
|
|
38
|
+
* `id=1` dentro de `withAuthzCatalogWrite` —que sube la versión compartida como
|
|
39
|
+
* última sentencia (invariante 14): un sync en otro proceso la ve en la
|
|
40
|
+
* siguiente pregunta—. Devuelve la config congelada.
|
|
41
|
+
*/
|
|
42
|
+
export async function saveRelationsConfig(spec, options = {}) {
|
|
43
|
+
// Valida ANTES de escribir: una config inválida no ensucia la tabla.
|
|
44
|
+
const config = defineRelationsConfig(spec);
|
|
45
|
+
const json = JSON.stringify(spec);
|
|
46
|
+
await withAuthzCatalogWrite(async (trx) => {
|
|
47
|
+
const existing = await trx.from(TABLE).where('id', ROW_ID).first();
|
|
48
|
+
if (existing) {
|
|
49
|
+
await trx.from(TABLE).where('id', ROW_ID).update({ spec: json, updated_at: systemClock() });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
await trx.table(TABLE).insert({ id: ROW_ID, spec: json, model_id: null, updated_at: systemClock() });
|
|
53
|
+
}
|
|
54
|
+
}, options);
|
|
55
|
+
invalidateAuthzCatalog();
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
/** Lee el spec persistido (o `null` si no hay relaciones declaradas — opt-in). */
|
|
59
|
+
export async function readRelationsConfigSpec(options = {}) {
|
|
60
|
+
const row = await guardSql('relations-config', 'read', options.timeoutMs ?? DEFAULT_TIMEOUT_MS, () => connectionOf(options).from(TABLE).where('id', ROW_ID).first());
|
|
61
|
+
if (!row?.spec)
|
|
62
|
+
return null;
|
|
63
|
+
return JSON.parse(String(row.spec));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Lee la config persistida ya VALIDADA (o `null`). Es lo que hace 🟡3: un
|
|
67
|
+
* proceso SIN `defineRelationsConfig` en memoria recupera aquí los tipos de
|
|
68
|
+
* relación de la base, así que el modelo que republique los lleva.
|
|
69
|
+
*/
|
|
70
|
+
export async function readRelationsConfig(options = {}) {
|
|
71
|
+
const spec = await readRelationsConfigSpec(options);
|
|
72
|
+
return spec ? defineRelationsConfig(spec) : null;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=relations_config_store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations_config_store.js","sourceRoot":"","sources":["../../src/relations_config_store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACpD,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAC1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAG9E,MAAM,KAAK,GAAG,wBAAwB,CAAA;AACtC,MAAM,MAAM,GAAG,CAAC,CAAA;AAChB,MAAM,kBAAkB,GAAG,KAAK,CAAA;AAOhC,SAAS,YAAY,CAAC,OAAqC;IACzD,OAAO,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAyB,EACzB,UAAuC,EAAE;IAEzC,qEAAqE;IACrE,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,qBAAqB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACxC,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAA;QAClE,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,CAAC,CAAA;QAC7F,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,CAAC,CAAA;QACtG,CAAC;IACH,CAAC,EAAE,OAAO,CAAC,CAAA;IACX,sBAAsB,EAAE,CAAA;IACxB,OAAO,MAAM,CAAA;AACf,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,UAAuC,EAAE;IAEzC,MAAM,GAAG,GAAQ,MAAM,QAAQ,CAAC,kBAAkB,EAAE,MAAM,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB,EAAE,GAAG,EAAE,CACxG,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAC9D,CAAA;IACD,IAAI,CAAC,GAAG,EAAE,IAAI;QAAE,OAAO,IAAI,CAAA;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAwB,CAAA;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAuC,EAAE;IAEzC,MAAM,IAAI,GAAG,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAA;IACnD,OAAO,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAClD,CAAC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **La outbox del árbol sobre Lucid** (3b-2d; panel 2, cruce 4 · S5).
|
|
3
|
+
*
|
|
4
|
+
* El puerto es `ScopeOutbox` (`src/types.ts`) y el paquete NO impone tabla:
|
|
5
|
+
* cualquier implementación vale. Esta es la que se publica para quien no
|
|
6
|
+
* quiera escribir la suya, junto con su migración
|
|
7
|
+
* (`stubs/scopes_outbox_migration.stub`, que hay que COPIAR al proyecto:
|
|
8
|
+
* `configure` no la publica sola porque la outbox es opt-in).
|
|
9
|
+
*
|
|
10
|
+
* import { sqlScopeOutbox } from '@jantstack/adonis-authz'
|
|
11
|
+
*
|
|
12
|
+
* const outbox = sqlScopeOutbox()
|
|
13
|
+
* export default defineConfig({
|
|
14
|
+
* scopes: { resolveChain, outbox },
|
|
15
|
+
* ...
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // y en el sitio donde mueves tu árbol:
|
|
19
|
+
* await db.transaction(async (trx) => {
|
|
20
|
+
* await authorization.scopes.moved(unit, nuevaOrg, { within, actor, transaction: trx })
|
|
21
|
+
* await unit.useTransaction(trx).merge({ organizationId: nuevaOrg.uuid }).save()
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* Lo único que tiene que cumplir una implementación del puerto es que
|
|
25
|
+
* `enqueue` escriba DENTRO de la transacción que le pasan: es lo que hace
|
|
26
|
+
* que el cambio del árbol y su propagación al backend confirmen —o se vayan—
|
|
27
|
+
* juntos. Todo lo demás (nombres de columna, si las filas aplicadas se
|
|
28
|
+
* borran o se marcan, si hay reintento con backoff) es del consumidor.
|
|
29
|
+
*
|
|
30
|
+
* Aquí las filas aplicadas se MARCAN (`applied_at`), no se borran: la cola
|
|
31
|
+
* es también el registro de lo que se propagó y cuándo, y `authz:scopes:relay`
|
|
32
|
+
* no puede ser silencioso.
|
|
33
|
+
*/
|
|
34
|
+
import type { Clock } from './clock.js';
|
|
35
|
+
import type { ScopeOutbox } from './types.js';
|
|
36
|
+
export interface SqlScopeOutboxOptions {
|
|
37
|
+
/** Tabla de la cola (default `authz_scope_outbox`, la del stub). */
|
|
38
|
+
table?: string;
|
|
39
|
+
/** Nombre de la conexión Lucid (default: la primaria). */
|
|
40
|
+
connection?: string;
|
|
41
|
+
/** Deadline de cada consulta en ms (default 5000): vencido ⇒ 503. */
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Reloj de pared con el que se sellan `created_at` y `applied_at`
|
|
45
|
+
* (2.5 · J1). Default `() => new Date()`; inyectable para fijar el
|
|
46
|
+
* instante en tests.
|
|
47
|
+
*/
|
|
48
|
+
now?: Clock;
|
|
49
|
+
/**
|
|
50
|
+
* **Cuántas veces se reintenta una entrada antes de APARCARLA** (3b-2h ·
|
|
51
|
+
* 🔴 2; default 5). Alcanzado el tope, `pending()` deja de ofrecerla y sale
|
|
52
|
+
* por `dead()`: hay entradas que no se pueden aplicar NUNCA —el scope padre
|
|
53
|
+
* del `attached` encolado lo borró el tenant antes del relevo y no va a
|
|
54
|
+
* volver— y sin tope esa fila se reintenta en todas las pasadas para
|
|
55
|
+
* siempre. Aparcar no es olvidar: el relay la reporta en cada pasada y el
|
|
56
|
+
* comando sale ≠ 0 mientras haya alguna, porque el árbol del backend queda
|
|
57
|
+
* permanentemente divergente en ese nodo (lo reconcilia `authz:reconcile`,
|
|
58
|
+
* 3b-3, o un `scopes.detached`/`attached` nuevo del consumidor).
|
|
59
|
+
*/
|
|
60
|
+
maxAttempts?: number;
|
|
61
|
+
/**
|
|
62
|
+
* **Encolar sin `{ transaction }` es 500** (2.4.0-alpha.8; panel relay 2,
|
|
63
|
+
* cuestión 6; P13 del tester). Con `true`, `enqueue` sin la transacción
|
|
64
|
+
* ABIERTA del llamante es 500 `E_AUTHZ_CONFIG` ANTES del INSERT: la fila
|
|
65
|
+
* confirmaría sola, fuera de tu unidad de trabajo, y si tu transacción
|
|
66
|
+
* revierte después el relay aplica al store un cambio que en TU base no
|
|
67
|
+
* ocurrió — la fuga S5 por el camino de uso «correcto» (SQL dice un padre y
|
|
68
|
+
* el PDP otro, sin que tu base pueda enseñártelo). Default **`false`** en
|
|
69
|
+
* alpha.8 porque el contrato PUBLICADO (`runScopeOutboxContract`) encola sin
|
|
70
|
+
* transacción en `ctx()` y un default `true` pondría en rojo a todo tercero;
|
|
71
|
+
* **pasa a `true` en 2.5** (decisión del dueño), cuando el contrato pase la
|
|
72
|
+
* transacción en `ctx()`. Ponlo a `true` hoy: es la letra de la mitigación.
|
|
73
|
+
*/
|
|
74
|
+
requireTransaction?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Lo que se necesita del `db` de Lucid; inyectable para probar sin servidor. */
|
|
77
|
+
interface DatabaseLike {
|
|
78
|
+
from(table: string): any;
|
|
79
|
+
table(table: string): any;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* **Los nombres del lease de una cola** (2.4.0-alpha.5 · R6; tester del panel
|
|
83
|
+
* relay). Hasta alpha.4 el nombre era `authz_scope_outbox:<conexión>:<tabla>`
|
|
84
|
+
* recortado a 64 caracteres para MySQL: dos despliegues cuya conexión+tabla
|
|
85
|
+
* coincidieran en los primeros 64 COMPARTÍAN lease y se excluían para siempre
|
|
86
|
+
* — y el modo de fallo era `busy: true`, que no lanza: el relay de uno de los
|
|
87
|
+
* dos no corría nunca, en silencio. Desde alpha.5 el nombre es un hash
|
|
88
|
+
* ESTABLE del nombre completo (`current`, 59 caracteres), que no colisiona.
|
|
89
|
+
*
|
|
90
|
+
* **Transición**: `sqlScopeOutbox.acquire()` toma el lock viejo (`legacy`, el
|
|
91
|
+
* recortado) Y el nuevo durante alpha.5, porque en un rolling deploy un
|
|
92
|
+
* proceso con alpha.4 y otro con alpha.5 tienen que seguir excluyéndose
|
|
93
|
+
* (+0,56 ms medidos por `acquire`). El viejo se retira en 2.5 (CHANGELOG):
|
|
94
|
+
* mientras dure la transición, dos tablas con los mismos 64 primeros
|
|
95
|
+
* caracteres siguen compartiendo el `legacy` — la colisión solo se cierra
|
|
96
|
+
* del todo al retirarlo. Exportado para que el caso lo fije sin MySQL.
|
|
97
|
+
*/
|
|
98
|
+
/**
|
|
99
|
+
* **Retirada del lock viejo, atada a la versión (cierre alpha.6).** `acquire()` toma
|
|
100
|
+
* el nombre `legacy` SOLO mientras `SCOPE_OUTBOX_TAKES_LEGACY_LOCK` sea `true`, y
|
|
101
|
+
* `tests/scope_outbox.spec.ts` exige que ese flag valga exactamente
|
|
102
|
+
* «`package.json` ≤ `SCOPE_OUTBOX_LEGACY_LOCK_UNTIL`»: en cuanto alguien suba la
|
|
103
|
+
* versión por encima de la última que necesita la transición, la suite se pone en
|
|
104
|
+
* ROJO hasta que el flag pase a `false` (y el caso MySQL de R6 se ajuste). Nadie
|
|
105
|
+
* tiene que acordarse: el propio bump lo exige. Por qué no se retira en la misma
|
|
106
|
+
* versión que estrena el nombre nuevo: durante un despliegue rodante los procesos
|
|
107
|
+
* de alpha.4 sostienen el viejo y los nuevos el nuevo, y ninguno ve al otro —
|
|
108
|
+
* dos escritores justo entonces.
|
|
109
|
+
*/
|
|
110
|
+
export declare const SCOPE_OUTBOX_LEGACY_LOCK_UNTIL = "2.4.0-alpha.6";
|
|
111
|
+
/**
|
|
112
|
+
* `false` desde `2.4.0-alpha.7`: la transición terminó (decisión del dueño 2026-09-10: sin
|
|
113
|
+
* consumidores en MySQL desplegando desde alpha.4, la transición no protegía a nadie). Subir
|
|
114
|
+
* desde ≤ alpha.4 en MySQL exige parada completa del relay, no despliegue rodante.
|
|
115
|
+
*/
|
|
116
|
+
export declare const SCOPE_OUTBOX_TAKES_LEGACY_LOCK = false;
|
|
117
|
+
export declare function scopeOutboxLockNames(connection: string | undefined, table: string): {
|
|
118
|
+
legacy: string;
|
|
119
|
+
current: string;
|
|
120
|
+
};
|
|
121
|
+
export declare function sqlScopeOutbox(options?: SqlScopeOutboxOptions, database?: DatabaseLike): ScopeOutbox;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=scope_outbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope_outbox.d.ts","sourceRoot":"","sources":["../../src/scope_outbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAMvC,OAAO,KAAK,EAEV,WAAW,EAOZ,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,GAAG,CAAC,EAAE,KAAK,CAAA;IACX;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAgBD,iFAAiF;AACjF,UAAU,YAAY;IACpB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;IACxB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAA;CAC1B;AAiCD;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,kBAAkB,CAAA;AAC7D;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAQ,CAAA;AAEnD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAMvH;AAoGD,wBAAgB,cAAc,CAC5B,OAAO,GAAE,qBAA0B,EACnC,QAAQ,GAAE,YAA4C,GACrD,WAAW,CAqWb"}
|