@jantstack/adonis-authz 2.4.0-alpha.8 → 2.5.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -15
- package/build/commands/authz_catalog_diff.d.ts.map +1 -1
- package/build/commands/authz_catalog_diff.js +0 -1
- package/build/commands/authz_catalog_diff.js.map +1 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +1 -1
- package/build/commands/authz_catalog_prune_orphans.js +0 -1
- package/build/commands/authz_catalog_prune_orphans.js.map +1 -1
- package/build/commands/authz_freeze.d.ts.map +1 -1
- package/build/commands/authz_freeze.js +1 -2
- package/build/commands/authz_freeze.js.map +1 -1
- package/build/commands/authz_reconcile.d.ts +10 -3
- package/build/commands/authz_reconcile.d.ts.map +1 -1
- package/build/commands/authz_reconcile.js +168 -18
- package/build/commands/authz_reconcile.js.map +1 -1
- package/build/commands/authz_relations_reconcile.d.ts +66 -19
- package/build/commands/authz_relations_reconcile.d.ts.map +1 -1
- package/build/commands/authz_relations_reconcile.js +231 -44
- package/build/commands/authz_relations_reconcile.js.map +1 -1
- package/build/commands/authz_scopes_relay.d.ts.map +1 -1
- package/build/commands/authz_scopes_relay.js +82 -6
- package/build/commands/authz_scopes_relay.js.map +1 -1
- package/build/commands/authz_scopes_requeue.d.ts.map +1 -1
- package/build/commands/authz_scopes_requeue.js +0 -2
- package/build/commands/authz_scopes_requeue.js.map +1 -1
- package/build/commands/authz_unfreeze.d.ts +12 -4
- package/build/commands/authz_unfreeze.d.ts.map +1 -1
- package/build/commands/authz_unfreeze.js +74 -9
- package/build/commands/authz_unfreeze.js.map +1 -1
- package/build/index.d.ts +6 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -3
- package/build/index.js.map +1 -1
- package/build/providers/authz_provider.d.ts +1 -1
- package/build/providers/authz_provider.d.ts.map +1 -1
- package/build/providers/authz_provider.js +2 -1
- package/build/providers/authz_provider.js.map +1 -1
- package/build/src/define_config.d.ts +9 -0
- 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.map +1 -1
- package/build/src/drivers/database_driver.js +1 -0
- package/build/src/drivers/database_driver.js.map +1 -1
- package/build/src/drivers/database_relations_driver.d.ts +18 -6
- package/build/src/drivers/database_relations_driver.d.ts.map +1 -1
- package/build/src/drivers/database_relations_driver.js +91 -44
- package/build/src/drivers/database_relations_driver.js.map +1 -1
- package/build/src/drivers/openfga_driver.d.ts +62 -4
- package/build/src/drivers/openfga_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_driver.js +182 -28
- package/build/src/drivers/openfga_driver.js.map +1 -1
- package/build/src/drivers/openfga_relations_driver.d.ts +16 -4
- package/build/src/drivers/openfga_relations_driver.d.ts.map +1 -1
- package/build/src/drivers/openfga_relations_driver.js +71 -34
- package/build/src/drivers/openfga_relations_driver.js.map +1 -1
- package/build/src/errors.d.ts +50 -0
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +50 -0
- package/build/src/errors.js.map +1 -1
- package/build/src/freeze.d.ts +63 -4
- package/build/src/freeze.d.ts.map +1 -1
- package/build/src/freeze.js +107 -18
- package/build/src/freeze.js.map +1 -1
- package/build/src/identity.d.ts +120 -9
- package/build/src/identity.d.ts.map +1 -1
- package/build/src/identity.js +164 -12
- package/build/src/identity.js.map +1 -1
- package/build/src/manager.d.ts +64 -2
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +271 -35
- package/build/src/manager.js.map +1 -1
- package/build/src/openfga.d.ts +1 -1
- package/build/src/openfga.d.ts.map +1 -1
- package/build/src/openfga.js +1 -1
- package/build/src/openfga.js.map +1 -1
- package/build/src/relations/check_many.d.ts +17 -0
- package/build/src/relations/check_many.d.ts.map +1 -0
- package/build/src/relations/check_many.js +57 -0
- package/build/src/relations/check_many.js.map +1 -0
- package/build/src/relations/manager.d.ts +20 -0
- package/build/src/relations/manager.d.ts.map +1 -1
- package/build/src/relations/manager.js +84 -13
- package/build/src/relations/manager.js.map +1 -1
- package/build/src/relations/reconcile.d.ts +67 -0
- package/build/src/relations/reconcile.d.ts.map +1 -1
- package/build/src/relations/reconcile.js +123 -30
- package/build/src/relations/reconcile.js.map +1 -1
- package/build/src/relations/tuple_key.d.ts +25 -0
- package/build/src/relations/tuple_key.d.ts.map +1 -0
- package/build/src/relations/tuple_key.js +60 -0
- package/build/src/relations/tuple_key.js.map +1 -0
- package/build/src/shared/backend_guard.d.ts +30 -11
- package/build/src/shared/backend_guard.d.ts.map +1 -1
- package/build/src/shared/backend_guard.js +45 -15
- package/build/src/shared/backend_guard.js.map +1 -1
- package/build/src/shared/sql_expiry.d.ts +2 -0
- package/build/src/shared/sql_expiry.d.ts.map +1 -1
- package/build/src/shared/sql_expiry.js +4 -0
- package/build/src/shared/sql_expiry.js.map +1 -1
- package/build/src/testing/main.d.ts +3 -1
- package/build/src/testing/main.d.ts.map +1 -1
- package/build/src/testing/main.js +2 -1
- package/build/src/testing/main.js.map +1 -1
- package/build/src/testing/migration_contract.d.ts +9 -0
- package/build/src/testing/migration_contract.d.ts.map +1 -1
- package/build/src/testing/migration_contract.js +5 -2
- package/build/src/testing/migration_contract.js.map +1 -1
- package/build/src/testing/relations_contract.d.ts +14 -0
- package/build/src/testing/relations_contract.d.ts.map +1 -1
- package/build/src/testing/relations_contract.js +297 -21
- package/build/src/testing/relations_contract.js.map +1 -1
- package/build/src/testing/test_clock.d.ts +15 -0
- package/build/src/testing/test_clock.d.ts.map +1 -0
- package/build/src/testing/test_clock.js +65 -0
- package/build/src/testing/test_clock.js.map +1 -0
- package/build/src/types.d.ts +148 -9
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js.map +1 -1
- package/package.json +5 -1
package/build/src/manager.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AuthorizationConfig } from './define_config.js';
|
|
2
2
|
import type { FreezeKind, FreezeToken } from './freeze.js';
|
|
3
|
-
import type { AuthorizationDriver, AuthorizationDriverCapabilities, AuthorizedScopes, CatalogRole, CatalogRoleRef, DenyOptions, DenyRef, ExcludedSubtree, GrantOptions, GrantOutcome, RoleQuery, ScopedRoleChanges, ScopedRoleSpec, ReconcileOptions, ReconcileReport, ScopedWriteOptions, ScopeEnqueueReceipt, ScopeRequeueReport, ScopeRelayReport, ScopeRef, ScopeTreeWriteOptions, ScopeType, SubjectRef } from './types.js';
|
|
3
|
+
import type { AuthorizationDriver, AuthorizationDriverCapabilities, AuthorizedScopes, CatalogRole, CatalogRoleRef, DenyOptions, DenyRef, ExcludedSubtree, GrantOptions, GrantOutcome, RoleQuery, ScopedRoleChanges, ScopedRoleSpec, ReconcileOptions, ReconcileReport, ScopedWriteOptions, ScopeEnqueueReceipt, ScopeRequeueReport, ScopeRelayReport, ScopeRef, ScopeTreeWriteOptions, ScopeType, SubjectRef, RelationsDriverFactory } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Lo que devuelve `AuthorizationManager.forRequest()`: la misma API que el
|
|
6
6
|
* manager (lecturas, escrituras, `scopes.*`), con los ancestros memoizados
|
|
@@ -106,7 +106,27 @@ export interface FreezeStatus {
|
|
|
106
106
|
*/
|
|
107
107
|
export interface FrozenWindow {
|
|
108
108
|
fence: number;
|
|
109
|
+
/**
|
|
110
|
+
* El lease de la ventana. En la ventana del OPERADOR adoptada
|
|
111
|
+
* (2.4.0-alpha.10) es lo que le QUEDABA al adoptarla (`untilMs − ahora`);
|
|
112
|
+
* `null` solo si la ventana no caduca.
|
|
113
|
+
*/
|
|
109
114
|
leaseMs: number | null;
|
|
115
|
+
/**
|
|
116
|
+
* Solo en la ventana del OPERADOR adoptada (2.4.0-alpha.10, 🟡 3 del sexto
|
|
117
|
+
* informe de COGNITIV): el instante (ms de pared) en el que caduca, de la
|
|
118
|
+
* fila, o `null` si no caduca. Quien adopta la ventana no la renueva: si
|
|
119
|
+
* caduca a mitad, `lapsed` lo dice; esto dice que PUEDE caducar.
|
|
120
|
+
*/
|
|
121
|
+
untilMs?: number | null;
|
|
122
|
+
/**
|
|
123
|
+
* EN QUÉ ventana corrió (2.4.0-alpha.10, S2): la del OPERADOR adoptada, una
|
|
124
|
+
* PROPIA tomada para la pasada, o la que ya sostenía este manager
|
|
125
|
+
* (`enclosing`). La salida de los comandos lo dice siempre: «corrí dentro del
|
|
126
|
+
* cutover» y «tomé mi ventana porque no había ninguna» eran letra a letra
|
|
127
|
+
* iguales.
|
|
128
|
+
*/
|
|
129
|
+
window?: 'operator' | 'own' | 'enclosing';
|
|
110
130
|
/** ¿Se perdió la ventana en algún momento? (renovación fallida, fila ajena o vencida). */
|
|
111
131
|
lapsed: () => Promise<boolean>;
|
|
112
132
|
}
|
|
@@ -234,8 +254,50 @@ export declare class AuthorizationManager {
|
|
|
234
254
|
* `freezeStatus()`: eso es la fila, no la memoria.
|
|
235
255
|
*/
|
|
236
256
|
get frozen(): boolean;
|
|
257
|
+
/**
|
|
258
|
+
* La clave (en `relations.drivers`) del driver de RELACIONES activo:
|
|
259
|
+
* `relations.default ?? default` (2.4.0-alpha.10). La lee el guard de la
|
|
260
|
+
* dirección rancia de `runRelationsReconcile`; desde el cierre del auditor
|
|
261
|
+
* (R4) el llamante ya no puede contradecirla con un `activeKey` propio.
|
|
262
|
+
*/
|
|
263
|
+
relationsDriverKey(): string;
|
|
264
|
+
/** Las claves registradas en `relations.drivers` del config de ESTE manager (vacío si no hay relaciones). */
|
|
265
|
+
relationsDriverKeys(): string[];
|
|
266
|
+
/**
|
|
267
|
+
* La factory registrada bajo `key` en `relations.drivers` de ESTE manager, o
|
|
268
|
+
* `undefined` (cierre del auditor de alpha.10 · R4). `runRelationsReconcile`
|
|
269
|
+
* resuelve el origen y el destino por aquí —del MISMO config que decide el
|
|
270
|
+
* activo— y juzga la identidad por la factory resuelta, no por la etiqueta:
|
|
271
|
+
* dos claves con la misma factory son el mismo driver (y dos factories DISTINTAS
|
|
272
|
+
* sobre el mismo backend NO: límite declarado, cierre-2 · C1).
|
|
273
|
+
*
|
|
274
|
+
* **API de PLATAFORMA, junto a `driver()`** (cierre-2 · C6): con esta factory
|
|
275
|
+
* cualquier código que tenga el manager —vistas de `forRequest()` incluidas—
|
|
276
|
+
* construye un driver de relaciones NO activo y escribe en él sin `assertWrite`,
|
|
277
|
+
* `requireActor`, freeze ni `onRelationWrite`. No añade capacidad (el config ya
|
|
278
|
+
* da las factories), pero es otra cosa que buscar en una revisión.
|
|
279
|
+
*/
|
|
280
|
+
relationsDriverFactory(key: string): RelationsDriverFactory | undefined;
|
|
237
281
|
/** El freeze VIVO de la fila compartida, o `null`. Lo lee cualquiera; solo el token lo levanta. */
|
|
238
282
|
freezeStatus(): Promise<FreezeStatus | null>;
|
|
283
|
+
/**
|
|
284
|
+
* **La ÚLTIMA ventana de la fila, también la CADUCADA** (2.4.0-alpha.10, 🟠 2
|
|
285
|
+
* del sexto informe de COGNITIV). `freezeStatus()` responde solo lo VIVO —y
|
|
286
|
+
* así lo siguen usando la barrera y los comandos—, de modo que una ventana
|
|
287
|
+
* cuyo lease venció era indistinguible de «nunca hubo»: `authz:unfreeze`
|
|
288
|
+
* decía éxito y el último paso del cutover no delataba que las escrituras
|
|
289
|
+
* habían vuelto a entrar a mitad. Se distingue por la fila: `releaseFreeze`
|
|
290
|
+
* pone el motivo a `null` al levantar, así que motivo puesto + `untilMs` en
|
|
291
|
+
* el pasado es una ventana que CADUCÓ sin que nadie la cerrase.
|
|
292
|
+
*
|
|
293
|
+
* `null` = nunca hubo o ya se levantó; `expired: false` = viva (lo mismo que
|
|
294
|
+
* `freezeStatus()`); `expired: true` = caducada. `checkedAtMs` es el instante
|
|
295
|
+
* del reloj del config con el que se decidió.
|
|
296
|
+
*/
|
|
297
|
+
lastFreezeStatus(): Promise<(FreezeStatus & {
|
|
298
|
+
expired: boolean;
|
|
299
|
+
checkedAtMs: number;
|
|
300
|
+
}) | null>;
|
|
239
301
|
/**
|
|
240
302
|
* `freeze()` + `finally unfreeze(token)`. El `finally` es la parte que
|
|
241
303
|
* importa: una migración que revienta a la mitad no puede dejar la
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"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"}
|
package/build/src/manager.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { Exception } from '@adonisjs/core/exceptions';
|
|
3
3
|
import { v7 as uuidv7 } from 'uuid';
|
|
4
|
-
import {
|
|
4
|
+
import { activeRelationsDriverKey } from './define_config.js';
|
|
5
|
+
import { assertCatalogUuid, assertIdentity, assertScope, assertScopeType, assertSubject, assertValidSlug, chainKeysFrom, normalizeRoleQuery, scopeFromKey, scopeKey, } from './identity.js';
|
|
5
6
|
import { expiryChanged } from './expiry.js';
|
|
6
7
|
import { randomBytes } from 'node:crypto';
|
|
7
|
-
import { DEFAULT_FREEZE_LEASE_MS,
|
|
8
|
-
import { assertKnownScope, isAuthzError, resolveChain, rootOnlyResolver } from './shared/backend_guard.js';
|
|
8
|
+
import { DEFAULT_FREEZE_LEASE_MS, acquireFreezeOutcome, UNKNOWN_HOLDER, freezeIsLive, freezeKindOf, readFreezeRow, assertNotFrozenRow, releaseFreeze, renewFreeze, } from './freeze.js';
|
|
9
|
+
import { assertKnownScope, canonicalScopeTargets, isAuthzError, resolveChain, rootOnlyResolver } from './shared/backend_guard.js';
|
|
9
10
|
import { CatalogCache, GLOBAL_OWNER_KEY, invalidateAuthzCatalog, isRoleVisibleWith, readLocalRoles, withAuthzCatalogWrite } from './catalog/catalog_cache.js';
|
|
10
11
|
import { assertAssignableAt } from './catalog/catalog.js';
|
|
11
12
|
import { systemClock } from './clock.js';
|
|
12
|
-
import { ActorRequiredError, AuthorizationBackendError, AuthorizationBackendTimeoutError, AuthorizationFrozenError, AuthorizationConfigError, AuthorizationInternalError, CatalogConflictError, InvalidIdentityError, MassPurgeRefusedError, PruneInterruptedError, NoDescendantsResolverError, NotWithinError, PermissionNotDelegableError, RankExceededError, RoleImmutableError, RoleLevelAboveOwnerError, ScopeCycleError, ScopeResolverError, TooManyScopesError, UnknownPermissionError, UnknownRoleError, FreezeHeldError, UnsupportedOperationError, ViewExpiredError, WithinRequiredError, WithinRootForbiddenError, ScopeDriftUnguardedError, ScopeDepthLimitError, ScopeRequeueRefusedError, } from './errors.js';
|
|
13
|
+
import { ActorRequiredError, AuthorizationBackendError, AuthorizationBackendTimeoutError, AuthorizationFrozenError, AuthorizationConfigError, AuthorizationInternalError, CatalogConflictError, InvalidIdentityError, MassPurgeRefusedError, PruneInterruptedError, NoDescendantsResolverError, NotWithinError, PermissionNotDelegableError, RankExceededError, RoleImmutableError, RoleLevelAboveOwnerError, ScopeCycleError, ScopeResolverError, TooManyScopesError, UnknownPermissionError, UnknownRoleError, FreezeHeldError, UnsupportedOperationError, ViewExpiredError, WithinRequiredError, WithinRootForbiddenError, ScopeDriftUnguardedError, ScopeDepthLimitError, ScopeRequeueRefusedError, StaleSourceRefusedError, OperatorWindowExpiredError, } from './errors.js';
|
|
13
14
|
import { APP_SCOPE_TYPE, normalizeAuthorizationCapabilities } from './types.js';
|
|
14
15
|
import { memoizeAncestors } from './memoize_ancestors.js';
|
|
15
16
|
import { AUTHZ_TABLES_ORIGIN } from './reconcile.js';
|
|
@@ -20,12 +21,26 @@ const ROLE_DESCRIPTION_MAX = 500;
|
|
|
20
21
|
function describeAnswer(answer) {
|
|
21
22
|
if (!Array.isArray(answer))
|
|
22
23
|
return `${answer === null ? 'null' : typeof answer} (no es un array)`;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
for (let i = 0; i < answer.length; i++) {
|
|
25
|
+
if (!(i in answer))
|
|
26
|
+
return `un array de ${answer.length} con un HUECO en la posición ${i}`;
|
|
27
|
+
if (typeof answer[i] !== 'boolean')
|
|
28
|
+
return `un array de ${answer.length} con un elemento que no es boolean en la posición ${i} (${typeof answer[i]})`;
|
|
26
29
|
}
|
|
27
30
|
return `un array de ${answer.length}`;
|
|
28
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* ¿Cada POSICIÓN del array es un boolean? Por índice, nunca con `some`/`every`
|
|
34
|
+
* (que saltan los huecos de un array disperso: 🟡 A3 del auditor del lote A;
|
|
35
|
+
* gemelo de `checkMany` en `src/relations/manager.ts`).
|
|
36
|
+
*/
|
|
37
|
+
function everyPositionBoolean(answers) {
|
|
38
|
+
for (let i = 0; i < answers.length; i++) {
|
|
39
|
+
if (typeof answers[i] !== 'boolean')
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
29
44
|
/**
|
|
30
45
|
* Configs que ya recibieron el aviso de seguridad opt-in (B7/B1): una vez
|
|
31
46
|
* por config, no por instancia — `forRequest()` construye managers hijos con
|
|
@@ -406,12 +421,30 @@ export class AuthorizationManager {
|
|
|
406
421
|
const finalReason = reason ?? 'una operación de plataforma';
|
|
407
422
|
const holder = `${kind}:${process.pid}:${randomBytes(4).toString('hex')}`;
|
|
408
423
|
const nowMs = root.#wallMs();
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
424
|
+
// alpha.10 · S2 (+ cierre del auditor · R2): una ventana de OPERADOR
|
|
425
|
+
// caducada no se pisa en silencio. Una fila con el lease vencido se toma
|
|
426
|
+
// encima (así la flota vuelve sola tras un SIGKILL de una pasada), pero la
|
|
427
|
+
// del operador es EVIDENCIA: pisarla y soltarla después la deja a NULL y
|
|
428
|
+
// borra el único rastro de que las escrituras volvieron a entrar a mitad
|
|
429
|
+
// del cutover. La exclusión vive DENTRO del UPDATE de `acquireFreezeOutcome`
|
|
430
|
+
// (atómica): aquí solo se lee el veredicto y la fila para redactarlo — un
|
|
431
|
+
// «compruebo y luego tomo» con dos lecturas del reloj era un TOCTOU.
|
|
432
|
+
const outcome = await acquireFreezeOutcome({ reason: finalReason, holder, untilMs: leaseMs === null ? null : nowMs + leaseMs, nowMs }, { driver: this.#config.default });
|
|
433
|
+
if (outcome.status === 'expired-operator') {
|
|
434
|
+
const row = outcome.row;
|
|
435
|
+
const when = row.untilMs === null ? '?' : new Date(row.untilMs).toISOString();
|
|
436
|
+
throw new OperatorWindowExpiredError(`freeze (${reason ?? kind}): la ventana del OPERADOR (${row.holder ?? '?'}, ${row.reason}, fence ` +
|
|
437
|
+
`${row.fence}) CADUCÓ el ${when} y nadie la cerró: desde entonces las escrituras volvieron a entrar en ` +
|
|
438
|
+
`toda la flota. No se toma una ventana propia encima —borraría ese rastro al soltarla y la pasada se ` +
|
|
439
|
+
`certificaría como si hubiera corrido dentro del cutover—. Reconócela con authz:unfreeze (dice cuánto ` +
|
|
440
|
+
`tiempo estuvo abierta y limpia la fila) y reabre la ventana con authz:freeze antes de repetir.`);
|
|
441
|
+
}
|
|
442
|
+
if (outcome.status === 'held') {
|
|
443
|
+
const row = outcome.row;
|
|
412
444
|
throw new FreezeHeldError(`freeze: ya hay un freeze VIVO de otro dueño (${row.holder ?? '?'}, fence ${row.fence}, motivo: ${row.reason ?? '?'}). ` +
|
|
413
445
|
`Espera a que termine, o levántalo con authz:unfreeze si su proceso murió sin lease.`);
|
|
414
446
|
}
|
|
447
|
+
const token = outcome.token;
|
|
415
448
|
const held = { token, reason: finalReason, kind, leaseMs, timer: null, lapsed: false };
|
|
416
449
|
if (leaseMs !== null) {
|
|
417
450
|
const interval = Math.max(250, Math.floor(leaseMs / 3));
|
|
@@ -472,6 +505,39 @@ export class AuthorizationManager {
|
|
|
472
505
|
get frozen() {
|
|
473
506
|
return this.#root().#heldFreeze !== null;
|
|
474
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* La clave (en `relations.drivers`) del driver de RELACIONES activo:
|
|
510
|
+
* `relations.default ?? default` (2.4.0-alpha.10). La lee el guard de la
|
|
511
|
+
* dirección rancia de `runRelationsReconcile`; desde el cierre del auditor
|
|
512
|
+
* (R4) el llamante ya no puede contradecirla con un `activeKey` propio.
|
|
513
|
+
*/
|
|
514
|
+
relationsDriverKey() {
|
|
515
|
+
return activeRelationsDriverKey(this.#root().#config);
|
|
516
|
+
}
|
|
517
|
+
/** Las claves registradas en `relations.drivers` del config de ESTE manager (vacío si no hay relaciones). */
|
|
518
|
+
relationsDriverKeys() {
|
|
519
|
+
return Object.keys(this.#root().#config.relations?.drivers ?? {});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* La factory registrada bajo `key` en `relations.drivers` de ESTE manager, o
|
|
523
|
+
* `undefined` (cierre del auditor de alpha.10 · R4). `runRelationsReconcile`
|
|
524
|
+
* resuelve el origen y el destino por aquí —del MISMO config que decide el
|
|
525
|
+
* activo— y juzga la identidad por la factory resuelta, no por la etiqueta:
|
|
526
|
+
* dos claves con la misma factory son el mismo driver (y dos factories DISTINTAS
|
|
527
|
+
* sobre el mismo backend NO: límite declarado, cierre-2 · C1).
|
|
528
|
+
*
|
|
529
|
+
* **API de PLATAFORMA, junto a `driver()`** (cierre-2 · C6): con esta factory
|
|
530
|
+
* cualquier código que tenga el manager —vistas de `forRequest()` incluidas—
|
|
531
|
+
* construye un driver de relaciones NO activo y escribe en él sin `assertWrite`,
|
|
532
|
+
* `requireActor`, freeze ni `onRelationWrite`. No añade capacidad (el config ya
|
|
533
|
+
* da las factories), pero es otra cosa que buscar en una revisión.
|
|
534
|
+
*/
|
|
535
|
+
relationsDriverFactory(key) {
|
|
536
|
+
const drivers = this.#root().#config.relations?.drivers;
|
|
537
|
+
if (drivers === undefined || !Object.prototype.hasOwnProperty.call(drivers, key))
|
|
538
|
+
return undefined;
|
|
539
|
+
return drivers[key];
|
|
540
|
+
}
|
|
475
541
|
/** El freeze VIVO de la fila compartida, o `null`. Lo lee cualquiera; solo el token lo levanta. */
|
|
476
542
|
async freezeStatus() {
|
|
477
543
|
const row = await readFreezeRow({ driver: this.#config.default });
|
|
@@ -479,12 +545,42 @@ export class AuthorizationManager {
|
|
|
479
545
|
return null;
|
|
480
546
|
return {
|
|
481
547
|
reason: row.reason,
|
|
482
|
-
holder: row.holder ??
|
|
548
|
+
holder: row.holder ?? UNKNOWN_HOLDER,
|
|
483
549
|
kind: freezeKindOf(row.holder),
|
|
484
550
|
untilMs: row.untilMs,
|
|
485
551
|
fence: row.fence,
|
|
486
552
|
};
|
|
487
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* **La ÚLTIMA ventana de la fila, también la CADUCADA** (2.4.0-alpha.10, 🟠 2
|
|
556
|
+
* del sexto informe de COGNITIV). `freezeStatus()` responde solo lo VIVO —y
|
|
557
|
+
* así lo siguen usando la barrera y los comandos—, de modo que una ventana
|
|
558
|
+
* cuyo lease venció era indistinguible de «nunca hubo»: `authz:unfreeze`
|
|
559
|
+
* decía éxito y el último paso del cutover no delataba que las escrituras
|
|
560
|
+
* habían vuelto a entrar a mitad. Se distingue por la fila: `releaseFreeze`
|
|
561
|
+
* pone el motivo a `null` al levantar, así que motivo puesto + `untilMs` en
|
|
562
|
+
* el pasado es una ventana que CADUCÓ sin que nadie la cerrase.
|
|
563
|
+
*
|
|
564
|
+
* `null` = nunca hubo o ya se levantó; `expired: false` = viva (lo mismo que
|
|
565
|
+
* `freezeStatus()`); `expired: true` = caducada. `checkedAtMs` es el instante
|
|
566
|
+
* del reloj del config con el que se decidió.
|
|
567
|
+
*/
|
|
568
|
+
async lastFreezeStatus() {
|
|
569
|
+
const row = await readFreezeRow({ driver: this.#config.default });
|
|
570
|
+
if (row.reason === null)
|
|
571
|
+
return null;
|
|
572
|
+
const nowMs = this.#root().#wallMs();
|
|
573
|
+
return {
|
|
574
|
+
reason: row.reason,
|
|
575
|
+
holder: row.holder ?? UNKNOWN_HOLDER,
|
|
576
|
+
kind: freezeKindOf(row.holder),
|
|
577
|
+
untilMs: row.untilMs,
|
|
578
|
+
fence: row.fence,
|
|
579
|
+
expired: !freezeIsLive(row, nowMs),
|
|
580
|
+
// El instante (reloj del config) con el que se decidió `expired`.
|
|
581
|
+
checkedAtMs: nowMs,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
488
584
|
/**
|
|
489
585
|
* `freeze()` + `finally unfreeze(token)`. El `finally` es la parte que
|
|
490
586
|
* importa: una migración que revienta a la mitad no puede dejar la
|
|
@@ -502,7 +598,13 @@ export class AuthorizationManager {
|
|
|
502
598
|
operatorAsContext: options.operatorAsContext === true,
|
|
503
599
|
});
|
|
504
600
|
try {
|
|
505
|
-
return await fn({
|
|
601
|
+
return await fn({
|
|
602
|
+
fence: context.fence,
|
|
603
|
+
leaseMs: context.leaseMs,
|
|
604
|
+
lapsed: context.lapsed,
|
|
605
|
+
...(context.untilMs === undefined ? {} : { untilMs: context.untilMs }),
|
|
606
|
+
...(context.window === undefined ? {} : { window: context.window }),
|
|
607
|
+
});
|
|
506
608
|
}
|
|
507
609
|
finally {
|
|
508
610
|
await context.release();
|
|
@@ -528,6 +630,7 @@ export class AuthorizationManager {
|
|
|
528
630
|
return {
|
|
529
631
|
fence: outer.token.fence,
|
|
530
632
|
leaseMs: outer.leaseMs,
|
|
633
|
+
window: 'enclosing',
|
|
531
634
|
release: async () => { },
|
|
532
635
|
lapsed: () => root.#tokenLapsed(outer.token, outer.lapsed),
|
|
533
636
|
};
|
|
@@ -536,9 +639,14 @@ export class AuthorizationManager {
|
|
|
536
639
|
const status = await this.freezeStatus();
|
|
537
640
|
if (status !== null && status.kind === 'operator') {
|
|
538
641
|
const token = { fence: status.fence, holder: status.holder };
|
|
642
|
+
// alpha.10 · 3: la caducidad REAL de la ventana adoptada. Hasta alpha.9
|
|
643
|
+
// `leaseMs: null` fijo — el reporte decía «sin caducidad» de una ventana
|
|
644
|
+
// abierta con `--lease-ms` (la certificación sí leía la fila).
|
|
539
645
|
return {
|
|
540
646
|
fence: status.fence,
|
|
541
|
-
leaseMs: null,
|
|
647
|
+
leaseMs: status.untilMs === null ? null : Math.max(0, status.untilMs - root.#wallMs()),
|
|
648
|
+
untilMs: status.untilMs,
|
|
649
|
+
window: 'operator',
|
|
542
650
|
release: async () => { },
|
|
543
651
|
lapsed: () => root.#tokenLapsed(token, false),
|
|
544
652
|
};
|
|
@@ -549,6 +657,7 @@ export class AuthorizationManager {
|
|
|
549
657
|
return {
|
|
550
658
|
fence: token.fence,
|
|
551
659
|
leaseMs: held.leaseMs,
|
|
660
|
+
window: 'own',
|
|
552
661
|
release: async () => {
|
|
553
662
|
try {
|
|
554
663
|
await this.unfreeze(token);
|
|
@@ -731,14 +840,19 @@ export class AuthorizationManager {
|
|
|
731
840
|
//
|
|
732
841
|
// Y cuando NO hay cadena —la fila ya no existe, que es el orden
|
|
733
842
|
// soportado de `detached` (3F · S1)— no hay con qué canonizar: se
|
|
734
|
-
// purgan
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
//
|
|
740
|
-
|
|
741
|
-
|
|
843
|
+
// purgan las ortografías de las que el uuid del llamante puede ser
|
|
844
|
+
// alias (`scopeSpellings`, 3b-2h · 🟠 3: lo que PG funde,
|
|
845
|
+
// `pgFusedSpellings`) MENOS las que el resolutor reconozca como un
|
|
846
|
+
// scope VIVO distinto del pedido (cierre-3 del lote A · 🟡 C2-2: con
|
|
847
|
+
// ids de texto, la canónica de un alias puede ser la unit viva de otro
|
|
848
|
+
// tenant, y `purgeScope` en `database` le borraría los denies). Con la
|
|
849
|
+
// fila viva esto es exactamente una, la de la tabla. El punto de paso
|
|
850
|
+
// es ÚNICO y compartido con `revoke`/`removeDeny` de los dos drivers
|
|
851
|
+
// (`canonicalScopeTargets`); un resolutor que falla al contrastar es
|
|
852
|
+
// 503, nunca «purgar por si acaso». Antes se usaba la del llamante a
|
|
853
|
+
// secas: `purgeScope` demostraba cero sobre un objeto que no existe,
|
|
854
|
+
// devolvía OK, y el scope real seguía concediendo para siempre.
|
|
855
|
+
const targets = await canonicalScopeTargets(this.#freshResolver(), child, 'scopes.detached');
|
|
742
856
|
const outbox = this.#outbox();
|
|
743
857
|
if (outbox) {
|
|
744
858
|
// La identidad se resuelve AQUÍ, con la fila del consumidor todavía
|
|
@@ -857,12 +971,13 @@ export class AuthorizationManager {
|
|
|
857
971
|
throw new AuthorizationConfigError(`authz:scopes:relay: deadDetail solo admite 'full' (llegó ${JSON.stringify(options.deadDetail)}); sin él, bajo only, dead[] va sin error.`);
|
|
858
972
|
}
|
|
859
973
|
const inputs = await _a.#relayInputs(outbox, options.only, batchSize, options.deadDetail);
|
|
860
|
-
const { only, filter, dead, deadCount, deadEntries,
|
|
974
|
+
const { only, filter, dead, deadCount, deadEntries, unseenOf } = inputs;
|
|
861
975
|
if (dryRun) {
|
|
862
976
|
const batch = await outbox.pending(limit, undefined, filter);
|
|
863
977
|
_a.#assertOnlyHonoured(batch, only);
|
|
864
978
|
const extra = batch.length >= limit ? true : false;
|
|
865
979
|
const seen = new Set([...batch.map((b) => String(b.id)), ...deadEntries.map((d) => String(d.id))]);
|
|
980
|
+
const unseen = await unseenOf(seen);
|
|
866
981
|
// La edad es GLOBAL: sin filtro la cabeza es `batch[0]`; con `only` hay que mirar la cola entera.
|
|
867
982
|
const queue = filter === undefined ? this.#queueOf(batch[0]) : (await this.#queueStatus(outbox)).queue;
|
|
868
983
|
return {
|
|
@@ -878,7 +993,7 @@ export class AuthorizationManager {
|
|
|
878
993
|
dryRun: true,
|
|
879
994
|
wouldApply: batch.map((item) => ({ id: item.id, change: item.change, attempts: item.attempts })),
|
|
880
995
|
entries: deadEntries,
|
|
881
|
-
|
|
996
|
+
...unseen,
|
|
882
997
|
};
|
|
883
998
|
}
|
|
884
999
|
// El lease del escritor ÚNICO. Una outbox que no sabe darlo se comporta
|
|
@@ -919,8 +1034,14 @@ export class AuthorizationManager {
|
|
|
919
1034
|
// llegar a `batchSize`). Una sola lectura de más, y solo si no cabía.
|
|
920
1035
|
const census = dead.length < batchSize ? dead : await _a.#deadLetters(outbox, PARKED_CENSUS_MAX);
|
|
921
1036
|
const deadCount = census.length;
|
|
922
|
-
/**
|
|
923
|
-
|
|
1037
|
+
/**
|
|
1038
|
+
* Lo aparcado que se pidió (con `only`) o todo lo aparcado, para `entries`.
|
|
1039
|
+
* Cierre-2 · C2 (gemelo): con `only` sale del CENSO completo si la lectura se
|
|
1040
|
+
* cortó en `batchSize` — si no, un id pedido aparcado más allá del primer lote
|
|
1041
|
+
* era `missing` («no estaba ni pendiente ni aparcado») estando aparcado.
|
|
1042
|
+
*/
|
|
1043
|
+
const deadSource = only !== undefined && read.length >= batchSize ? census : read;
|
|
1044
|
+
const deadEntries = deadSource
|
|
924
1045
|
.filter((item) => only === undefined || only.has(String(item.id)))
|
|
925
1046
|
.map((item) => ({
|
|
926
1047
|
id: item.id,
|
|
@@ -929,8 +1050,29 @@ export class AuthorizationManager {
|
|
|
929
1050
|
attempts: item.attempts,
|
|
930
1051
|
...(item.error === undefined ? {} : { reason: item.error }),
|
|
931
1052
|
}));
|
|
932
|
-
|
|
933
|
-
|
|
1053
|
+
/**
|
|
1054
|
+
* **Qué fue de los ids pedidos que la pasada NO vio** (cierre-2 de alpha.10 ·
|
|
1055
|
+
* C2). Hasta el cierre-2 todos eran `missing`; pero un id fuera del lote porque
|
|
1056
|
+
* la pasada alcanzó `limit` sigue PENDIENTE. Se pregunta a la cola —filtrada por
|
|
1057
|
+
* esos ids, sin límite propio, UNA lectura y solo si hay alguno sin ver—: lo que
|
|
1058
|
+
* sigue pendiente es `notEvaluated`; el resto, `missing` (no está en la cola: lo
|
|
1059
|
+
* aparcado ya está en `seen`).
|
|
1060
|
+
*/
|
|
1061
|
+
const unseenOf = async (seen) => {
|
|
1062
|
+
if (only === undefined)
|
|
1063
|
+
return { missing: [], notEvaluated: [] };
|
|
1064
|
+
const unseen = [...only.values()].filter((id) => !seen.has(id)).map((id) => _a.#rawId(onlyIds, id));
|
|
1065
|
+
if (unseen.length === 0)
|
|
1066
|
+
return { missing: [], notEvaluated: [] };
|
|
1067
|
+
const stillPending = await outbox.pending(unseen.length, undefined, { ids: unseen });
|
|
1068
|
+
_a.#assertOnlyHonoured(stillPending, new Set(unseen.map(String)));
|
|
1069
|
+
const pendingIds = new Set(stillPending.map((item) => String(item.id)));
|
|
1070
|
+
return {
|
|
1071
|
+
missing: unseen.filter((id) => !pendingIds.has(String(id))),
|
|
1072
|
+
notEvaluated: unseen.filter((id) => pendingIds.has(String(id))),
|
|
1073
|
+
};
|
|
1074
|
+
};
|
|
1075
|
+
return { only, filter, dead, census, deadCount, deadEntries, unseenOf };
|
|
934
1076
|
}
|
|
935
1077
|
/** El reporte de una pasada que NO entró: otra tiene el lease. Nada aplicado, nada tocado. */
|
|
936
1078
|
async #busyReport(outbox, inputs) {
|
|
@@ -949,6 +1091,7 @@ export class AuthorizationManager {
|
|
|
949
1091
|
wouldApply: [],
|
|
950
1092
|
entries: inputs.deadEntries,
|
|
951
1093
|
missing: [],
|
|
1094
|
+
notEvaluated: [],
|
|
952
1095
|
};
|
|
953
1096
|
}
|
|
954
1097
|
/**
|
|
@@ -1020,7 +1163,7 @@ export class AuthorizationManager {
|
|
|
1020
1163
|
* (auditor 🟠 5: tomar el lease dos veces era `busy` en verde).
|
|
1021
1164
|
*/
|
|
1022
1165
|
async #drainScopeChanges(outbox, input) {
|
|
1023
|
-
const { limit, batchSize, only, filter, dead, census, deadCount, deadEntries,
|
|
1166
|
+
const { limit, batchSize, only, filter, dead, census, deadCount, deadEntries, unseenOf } = input;
|
|
1024
1167
|
const driver = await this.driver();
|
|
1025
1168
|
const applied = [];
|
|
1026
1169
|
const deferred = [];
|
|
@@ -1188,7 +1331,7 @@ export class AuthorizationManager {
|
|
|
1188
1331
|
dryRun: false,
|
|
1189
1332
|
wouldApply: [],
|
|
1190
1333
|
entries,
|
|
1191
|
-
|
|
1334
|
+
...(await unseenOf(new Set(entries.map((e) => String(e.id))))),
|
|
1192
1335
|
};
|
|
1193
1336
|
}
|
|
1194
1337
|
/**
|
|
@@ -1586,6 +1729,49 @@ export class AuthorizationManager {
|
|
|
1586
1729
|
throw new UnsupportedOperationError('reconcile', `authz:reconcile --to=${name}`, name, `El driver '${name}' no sabe reconstruirse desde 'authz_*' + el árbol del consumidor. ` +
|
|
1587
1730
|
`El driver 'database' es ese caso a propósito: sus tablas son el ORIGEN.`);
|
|
1588
1731
|
}
|
|
1732
|
+
// **La dirección RANCIA** (2.4.0-alpha.10; 🔴 4 del sexto informe de
|
|
1733
|
+
// COGNITIV y cuarta iteración del LINAJE «origen rancio del reconcile»:
|
|
1734
|
+
// 3b-5 cerró la pasada por defecto, el primer guard de alpha.10 cerró
|
|
1735
|
+
// `--from`, y quedaba abierto el origen elegido POR DESCARTE). Regla del
|
|
1736
|
+
// dueño: el tercer defecto de un linaje se arregla estructuralmente, así
|
|
1737
|
+
// que el guard NO mira la bandera, mira el ORIGEN RESUELTO — con la MISMA
|
|
1738
|
+
// decisión que `#factsOrigin` (`#isMaintenancePass`), no con una copia:
|
|
1739
|
+
// si `to` es el driver ACTIVO, el único origen legítimo es `to` mismo (la
|
|
1740
|
+
// pasada de MANTENIMIENTO); cualquier otro —`--from`, el driver elegido por
|
|
1741
|
+
// descarte o las tablas `authz_*`— dejó de ver los hechos en el cutover y la
|
|
1742
|
+
// pasada re-escribiría lo revocado después (con o sin --prune; el seguro de
|
|
1743
|
+
// borrado masivo no salta: el origen no está vacío). Se decide por `to`,
|
|
1744
|
+
// nunca por `from`: el cutover inicial y la vuelta atrás ANTES de cambiar
|
|
1745
|
+
// `default` tienen `to` ≠ activo y pasan. Antes de resolver el origen y de
|
|
1746
|
+
// congelar. `--dry-run` no lanza (es el verificador): lo marca y
|
|
1747
|
+
// `reconcileLines` lo hace deriva. `from === to` sigue siendo el 500
|
|
1748
|
+
// E_AUTHZ_CONFIG de 3b-3b.
|
|
1749
|
+
const stale = this.#sameDriver(name, this.#config.default) &&
|
|
1750
|
+
(options.from === undefined || !this.#sameDriver(options.from, name)) &&
|
|
1751
|
+
!this.#isMaintenancePass(name, target, options.from);
|
|
1752
|
+
const allowStale = options.allowStaleSource === true;
|
|
1753
|
+
if (stale && !allowStale && options.dryRun !== true) {
|
|
1754
|
+
const how = options.from !== undefined ? `--from=${options.from}` : 'sin --from';
|
|
1755
|
+
// Cierre del auditor (⚪ 8): con UN solo driver registrado no hay descarte que
|
|
1756
|
+
// hacer; la letra no puede hablar de un origen que no existe.
|
|
1757
|
+
const others = Object.keys(this.#config.drivers ?? {}).filter((key) => !this.#sameDriver(key, name));
|
|
1758
|
+
const originLabel = options.from !== undefined
|
|
1759
|
+
? `el origen '${options.from}' no lo es`
|
|
1760
|
+
: others.length === 0
|
|
1761
|
+
? `no hay ningún otro driver registrado ni pasada de mantenimiento para él (no guarda sus hechos en su ` +
|
|
1762
|
+
`backend: no declara hierarchyFacts), así que no hay nada de dónde reconciliar hacia '${name}'`
|
|
1763
|
+
: `sin --from el origen se elegiría POR DESCARTE (las tablas authz_* u otro driver registrado: ` +
|
|
1764
|
+
`${others.join(', ')}), y no es '${name}'`;
|
|
1765
|
+
throw new StaleSourceRefusedError(`authz:reconcile --to=${name} ${how}: '${name}' es el driver ACTIVO (config.default` +
|
|
1766
|
+
`${name === this.#config.default ? '' : `: la clave resuelve la MISMA factory que '${this.#config.default}'`}) y ${originLabel}. ` +
|
|
1767
|
+
`Esa dirección re-escribiría en el driver activo hechos que el origen ya no refleja: resucitaría las ` +
|
|
1768
|
+
`revocaciones hechas desde el cutover (y con --prune borraría lo concedido después). No se ha leído, ` +
|
|
1769
|
+
`congelado ni escrito nada. Hacia el driver activo el único origen es él mismo: la pasada de mantenimiento ` +
|
|
1770
|
+
`(authz:reconcile --to=${name} [--dry-run], sin --from) si su driver guarda sus hechos; y si lo que quieres ` +
|
|
1771
|
+
`es volver atrás, migra en la otra dirección ANTES de cambiar config.default. Si de verdad el origen sigue ` +
|
|
1772
|
+
`siendo la fuente de verdad (el motor aún no ha escrito desde '${name}'), la decisión humana es ` +
|
|
1773
|
+
`--allow-stale-source (allowStaleSource: true).`);
|
|
1774
|
+
}
|
|
1589
1775
|
// **De dónde salen los HECHOS** (3b-5): la decisión que faltaba, y la que
|
|
1590
1776
|
// el destino no puede tomar por su cuenta. Ver `#factsOrigin`.
|
|
1591
1777
|
const origin = await this.#factsOrigin(name, target, options.from);
|
|
@@ -1596,10 +1782,19 @@ export class AuthorizationManager {
|
|
|
1596
1782
|
// `authz_*` no construye ningún driver de más. Y el origen se resuelve
|
|
1597
1783
|
// UNA vez.
|
|
1598
1784
|
facts: origin.enumerate,
|
|
1599
|
-
factsOrigin: { name: origin.name, authzTables: origin.authzTables },
|
|
1785
|
+
factsOrigin: { name: origin.name, authzTables: origin.authzTables, ...(origin.self ? { self: true } : {}) },
|
|
1600
1786
|
// El techo de profundidad del consumidor, si lo declaró (alpha.5 · R0):
|
|
1601
1787
|
// el mismo mando que el gate de `scopes.*`.
|
|
1602
1788
|
...(this.#config.scopes?.maxDepth === undefined ? {} : { maxDepth: this.#config.scopes.maxDepth }),
|
|
1789
|
+
// **Los tipos del OTRO subsistema del mismo backend** (alpha.9 · B · S4),
|
|
1790
|
+
// si el consumidor declaró relaciones. Es SOLO para el reporte: parte lo
|
|
1791
|
+
// ajeno en «declarado» y «no lo reclama nadie». La decisión de BORRAR no
|
|
1792
|
+
// lo mira —la toma la lista blanca congelada del destino—, porque el
|
|
1793
|
+
// proceso que corre un cutover puede no tener esta config y aun así
|
|
1794
|
+
// tiene que respetar las tuplas de quien sí la tiene.
|
|
1795
|
+
...(this.#config.relations?.config === undefined
|
|
1796
|
+
? {}
|
|
1797
|
+
: { relationTypes: this.#config.relations.config.objectTypes.map((type) => type.type) }),
|
|
1603
1798
|
};
|
|
1604
1799
|
const pass = async () => {
|
|
1605
1800
|
const report = await target.reconcile(source, options);
|
|
@@ -1609,6 +1804,8 @@ export class AuthorizationManager {
|
|
|
1609
1804
|
// Quién fue el origen se DICE, siempre: es la diferencia entre una
|
|
1610
1805
|
// migración y una pasada de mantenimiento contra el driver activo.
|
|
1611
1806
|
report.factsFrom = origin.resolved();
|
|
1807
|
+
if (stale)
|
|
1808
|
+
report.staleSource = { to: name, from: origin.resolved(), allowed: allowStale };
|
|
1612
1809
|
return report;
|
|
1613
1810
|
};
|
|
1614
1811
|
// La pasada que escribe congela; el verificador NO (ver el docblock).
|
|
@@ -1632,6 +1829,8 @@ export class AuthorizationManager {
|
|
|
1632
1829
|
lapsed: await window.lapsed(),
|
|
1633
1830
|
leaseMs: window.leaseMs,
|
|
1634
1831
|
fence: window.fence,
|
|
1832
|
+
...(window.untilMs === undefined ? {} : { untilMs: window.untilMs }),
|
|
1833
|
+
...(window.window === undefined ? {} : { window: window.window }),
|
|
1635
1834
|
};
|
|
1636
1835
|
return report;
|
|
1637
1836
|
}
|
|
@@ -1659,7 +1858,9 @@ export class AuthorizationManager {
|
|
|
1659
1858
|
* MANTENIMIENTO: los hechos se leen del propio destino por el puerto
|
|
1660
1859
|
* (`enumerateFacts`), se rehace lo DERIVADO (marcador, catálogo, árbol)
|
|
1661
1860
|
* y se aplica el barrido de visibilidad del invariante 18 con el árbol
|
|
1662
|
-
* y el catálogo de HOY. No se inventa
|
|
1861
|
+
* y el catálogo de HOY. No se inventa ningún hecho; con `--prune` se
|
|
1862
|
+
* borran los que ya no respaldan nada (scopes que ya no resuelven,
|
|
1863
|
+
* caducados, roles/permisos retirados, y los que sobran). Un
|
|
1663
1864
|
* destino activo con `hierarchyFacts` que no sepa enumerar sus hechos
|
|
1664
1865
|
* es 500 `E_AUTHZ_UNSUPPORTED`: leerle `authz_*` sería justo el defecto.
|
|
1665
1866
|
* 2. **`--from=<nombre>` manda**, y por eso se resuelve YA: de la
|
|
@@ -1673,7 +1874,7 @@ export class AuthorizationManager {
|
|
|
1673
1874
|
* de 3b-3b (`#factsEnumerator`).
|
|
1674
1875
|
*/
|
|
1675
1876
|
async #factsOrigin(to, target, from) {
|
|
1676
|
-
if (
|
|
1877
|
+
if (this.#isMaintenancePass(to, target, from)) {
|
|
1677
1878
|
if (typeof target.enumerateFacts !== 'function') {
|
|
1678
1879
|
throw new UnsupportedOperationError('enumerateFacts', `authz:reconcile --to=${to}`, to, `El motor SIRVE desde '${to}' y ese driver declara que el árbol y los hechos viven en su backend ` +
|
|
1679
1880
|
`(hierarchyFacts), así que 'authz_assignments'/'authz_denies' NO son la fuente de verdad de sus ` +
|
|
@@ -1683,6 +1884,7 @@ export class AuthorizationManager {
|
|
|
1683
1884
|
return {
|
|
1684
1885
|
name: to,
|
|
1685
1886
|
authzTables: false,
|
|
1887
|
+
self: true,
|
|
1686
1888
|
enumerate: (page) => target.enumerateFacts(page),
|
|
1687
1889
|
resolved: () => to,
|
|
1688
1890
|
};
|
|
@@ -1708,10 +1910,44 @@ export class AuthorizationManager {
|
|
|
1708
1910
|
});
|
|
1709
1911
|
return { name: AUTHZ_TABLES_ORIGIN, authzTables: true, enumerate, resolved: () => resolvedName };
|
|
1710
1912
|
}
|
|
1913
|
+
/**
|
|
1914
|
+
* **¿Es la pasada de MANTENIMIENTO?** (3b-5; punto ÚNICO desde alpha.10): el
|
|
1915
|
+
* destino es el driver ACTIVO, sus hechos viven en su backend
|
|
1916
|
+
* (`hierarchyFacts`) y nadie nombró otro origen. Es la ÚNICA forma en la que
|
|
1917
|
+
* el origen resuelto de una pasada hacia el driver activo es el propio
|
|
1918
|
+
* destino, y la usan los dos que la necesitan: `#factsOrigin` (de dónde se
|
|
1919
|
+
* leen los hechos) y el guard de la dirección rancia de `reconcile` (si ese
|
|
1920
|
+
* origen es legítimo). Dos copias de esta condición eran dos respuestas.
|
|
1921
|
+
*/
|
|
1922
|
+
#isMaintenancePass(to, target, from) {
|
|
1923
|
+
return from === undefined && this.#sameDriver(to, this.#config.default) && target.capabilities?.hierarchyFacts === true;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* **¿Dos claves de `drivers` son el MISMO driver?** (cierre del auditor de
|
|
1927
|
+
* alpha.10 · R4, gemelo de roles): misma clave o la MISMA factory registrada
|
|
1928
|
+
* bajo dos claves. El guard de la dirección rancia, el mantenimiento, el
|
|
1929
|
+
* `from === to` y el descarte juzgan la identidad RESUELTA, no la etiqueta:
|
|
1930
|
+
* con `drivers: { openfga: f, fga: f }` y `default: 'openfga'`, `--to=fga
|
|
1931
|
+
* --from=database` es la misma dirección rancia que `--to=openfga`.
|
|
1932
|
+
*
|
|
1933
|
+
* **Límite DECLARADO (cierre-2 · C1)**: la identidad es la REFERENCIA de la
|
|
1934
|
+
* factory, no el backend. Dos factories distintas que construyen el mismo
|
|
1935
|
+
* store (`openfga: () => new OpenFga…(…)`, `fga: () => new OpenFga…(…)`) NO son
|
|
1936
|
+
* el mismo driver para este predicado y el alias pasa el guard. Identidad de
|
|
1937
|
+
* backend declarada por el driver ⇒ roadmap 2.5.
|
|
1938
|
+
*/
|
|
1939
|
+
#sameDriver(a, b) {
|
|
1940
|
+
if (a === b)
|
|
1941
|
+
return true;
|
|
1942
|
+
const drivers = this.#config.drivers ?? {};
|
|
1943
|
+
const own = (key) => (Object.prototype.hasOwnProperty.call(drivers, key) ? drivers[key] : undefined);
|
|
1944
|
+
const fa = own(a);
|
|
1945
|
+
return fa !== undefined && fa === own(b);
|
|
1946
|
+
}
|
|
1711
1947
|
/** El driver que `--from` nombra, con los dos errores de 3b-3b intactos. */
|
|
1712
1948
|
async #originDriver(from, to) {
|
|
1713
1949
|
const registered = Object.keys(this.#config.drivers ?? {});
|
|
1714
|
-
if (from
|
|
1950
|
+
if (this.#sameDriver(from, to)) {
|
|
1715
1951
|
throw new AuthorizationConfigError(`authz:reconcile --from=${from} --to=${to}: el origen y el destino son el mismo driver. ` +
|
|
1716
1952
|
`Si lo que quieres es VERIFICAR y reparar lo derivado del driver activo, no lo digas con --from: ` +
|
|
1717
1953
|
`la pasada ya lee sus hechos de él cuando es el driver por defecto.`);
|
|
@@ -1747,7 +1983,7 @@ export class AuthorizationManager {
|
|
|
1747
1983
|
const registered = Object.keys(this.#config.drivers ?? {});
|
|
1748
1984
|
const candidates = [];
|
|
1749
1985
|
for (const candidate of registered) {
|
|
1750
|
-
if (candidate
|
|
1986
|
+
if (this.#sameDriver(candidate, to))
|
|
1751
1987
|
continue;
|
|
1752
1988
|
const driver = await this.#config.drivers[candidate]();
|
|
1753
1989
|
if (typeof driver.enumerateFacts === 'function')
|
|
@@ -2179,7 +2415,7 @@ export class AuthorizationManager {
|
|
|
2179
2415
|
const answer = await driver.authorizeMany(subject, permission, scopes);
|
|
2180
2416
|
// Un `boolean[]` desalineado se leería por posición (F5, CR3): es un
|
|
2181
2417
|
// bug del driver, no una decisión. 500 nombrando al culpable.
|
|
2182
|
-
if (!Array.isArray(answer) || answer.length !== scopes.length || answer
|
|
2418
|
+
if (!Array.isArray(answer) || answer.length !== scopes.length || !everyPositionBoolean(answer)) {
|
|
2183
2419
|
throw new AuthorizationInternalError(`authorizeMany: el driver '${this.#config.default}' devolvió ${describeAnswer(answer)} para ${scopes.length} scopes; ` +
|
|
2184
2420
|
`el puerto exige un boolean[] con exactamente una posición por scope.`);
|
|
2185
2421
|
}
|