@jantstack/adonis-authz 2.5.0-alpha.4 → 2.5.0-rc.2
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 +151 -56
- package/build/commands/authz_catalog_prune_orphans.d.ts +17 -2
- package/build/commands/authz_catalog_prune_orphans.js +28 -5
- package/build/commands/authz_scopes_relay.js +5 -1
- package/build/commands/authz_scopes_requeue.js +2 -1
- package/build/commands/authz_upgrade_1x.d.ts +216 -0
- package/build/commands/authz_upgrade_1x.js +903 -0
- package/build/commands/main.d.ts +2 -1
- package/build/commands/main.js +5 -3
- package/build/index.d.ts +2 -2
- package/build/src/catalog/catalog_cache.js +2 -1
- package/build/src/drivers/database_driver.d.ts +54 -2
- package/build/src/drivers/database_driver.js +138 -23
- package/build/src/drivers/database_relations_driver.js +25 -15
- package/build/src/errors.d.ts +39 -0
- package/build/src/errors.js +8 -0
- package/build/src/identity.d.ts +7 -1
- package/build/src/identity.js +29 -0
- package/build/src/manager.d.ts +24 -0
- package/build/src/manager.js +225 -57
- package/build/src/scope_outbox.d.ts +36 -2
- package/build/src/scope_outbox.js +78 -17
- package/build/src/shared/sql_expiry.d.ts +7 -3
- package/build/src/shared/sql_expiry.js +41 -7
- package/build/src/testing/contract.d.ts +37 -4
- package/build/src/testing/contract.js +280 -22
- package/build/src/testing/relations_contract.d.ts +11 -1
- package/build/src/testing/relations_contract.js +107 -18
- package/build/src/types.d.ts +49 -0
- package/build/src/types.js +2 -0
- package/package.json +1 -1
|
@@ -34,6 +34,11 @@ export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, s
|
|
|
34
34
|
level: 'log' | 'warning';
|
|
35
35
|
message: string;
|
|
36
36
|
}>;
|
|
37
|
+
/**
|
|
38
|
+
* La letra de los disparadores del seguro de purga masiva (3b-0b · AA2 + rotación 2 · R2-02), en el orden en que
|
|
39
|
+
* el manager los reporta. Pura, para que el caso la fije.
|
|
40
|
+
*/
|
|
41
|
+
export declare function massPurgeLetter(reasons: ReadonlyArray<string>): string;
|
|
37
42
|
/**
|
|
38
43
|
* Lista —y con `--force` borra— los roles LOCALES cuyo owner el árbol del
|
|
39
44
|
* consumidor YA NO conoce (3b-0 · Z2).
|
|
@@ -56,6 +61,7 @@ export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, s
|
|
|
56
61
|
* node ace authz:catalog:prune-orphans # --dry-run: solo lista
|
|
57
62
|
* node ace authz:catalog:prune-orphans --force # purga de verdad
|
|
58
63
|
* node ace authz:catalog:prune-orphans --force --allow-mass-purge
|
|
64
|
+
* node ace authz:catalog:prune-orphans --max-local-roles=50000 # catálogo local de más de 10 000 roles
|
|
59
65
|
*
|
|
60
66
|
* Es una operación de PLATAFORMA, no una escritura que dispare un tenant: no
|
|
61
67
|
* lleva actor ni mide rangos. Hasta 3G esta limpieza la arrastraba
|
|
@@ -63,8 +69,10 @@ export declare function orphanLines(orphans: OrphanRow[], purged: Set<string>, s
|
|
|
63
69
|
* resuelve—, y de ahí salieron tres de las cuatro regresiones de la Fase 3.
|
|
64
70
|
*
|
|
65
71
|
* `--allow-mass-purge` (3b-0b · AA2) desbloquea la cota que rechaza una
|
|
66
|
-
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
67
|
-
* más del 50 % de los roles locales
|
|
72
|
+
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
73
|
+
* más del 50 % de los roles locales, o —rotación 2 · R2-02— huérfanos de más
|
|
74
|
+
* de un owner con alguno que todavía concede): es una decisión humana, no un
|
|
75
|
+
* default. `--max-local-roles` (R2-04) sube la cota de la lectura.
|
|
68
76
|
*/
|
|
69
77
|
export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
70
78
|
static commandName: string;
|
|
@@ -72,6 +80,13 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
|
72
80
|
static options: CommandOptions;
|
|
73
81
|
force: boolean | undefined;
|
|
74
82
|
allowMassPurge: boolean | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Rotación 2 · R2-04: la cota de la lectura del catálogo local (default 10 000). Hasta este cierre el mensaje
|
|
85
|
+
* de `E_AUTHZ_TOO_MANY_LOCAL_ROLES` pedía «subir la cota» y no había mando: un tenant que pasara de 10 000 roles
|
|
86
|
+
* locales dejaba este comando muerto para todos. Un valor que no es un entero ≥ 1 lo rechaza el manager (500
|
|
87
|
+
* `E_AUTHZ_CONFIG`) antes de leer nada.
|
|
88
|
+
*/
|
|
89
|
+
maxLocalRoles: number | undefined;
|
|
75
90
|
run(): Promise<void>;
|
|
76
91
|
}
|
|
77
92
|
export {};
|
|
@@ -54,6 +54,18 @@ export function orphanLines(orphans, purged, skipped) {
|
|
|
54
54
|
}
|
|
55
55
|
return lines;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* La letra de los disparadores del seguro de purga masiva (3b-0b · AA2 + rotación 2 · R2-02), en el orden en que
|
|
59
|
+
* el manager los reporta. Pura, para que el caso la fije.
|
|
60
|
+
*/
|
|
61
|
+
export function massPurgeLetter(reasons) {
|
|
62
|
+
const words = {
|
|
63
|
+
'all-owners': 'todos los owners salen huérfanos',
|
|
64
|
+
'proportion': 'más del 50 % de los roles locales',
|
|
65
|
+
'granting-across-owners': 'huérfanos de más de un owner y alguno TODAVÍA concede o no se sabe',
|
|
66
|
+
};
|
|
67
|
+
return reasons.map((reason) => words[reason] ?? reason).join('; ') || 'sin motivo reportado';
|
|
68
|
+
}
|
|
57
69
|
/**
|
|
58
70
|
* Lista —y con `--force` borra— los roles LOCALES cuyo owner el árbol del
|
|
59
71
|
* consumidor YA NO conoce (3b-0 · Z2).
|
|
@@ -76,6 +88,7 @@ export function orphanLines(orphans, purged, skipped) {
|
|
|
76
88
|
* node ace authz:catalog:prune-orphans # --dry-run: solo lista
|
|
77
89
|
* node ace authz:catalog:prune-orphans --force # purga de verdad
|
|
78
90
|
* node ace authz:catalog:prune-orphans --force --allow-mass-purge
|
|
91
|
+
* node ace authz:catalog:prune-orphans --max-local-roles=50000 # catálogo local de más de 10 000 roles
|
|
79
92
|
*
|
|
80
93
|
* Es una operación de PLATAFORMA, no una escritura que dispare un tenant: no
|
|
81
94
|
* lleva actor ni mide rangos. Hasta 3G esta limpieza la arrastraba
|
|
@@ -83,8 +96,10 @@ export function orphanLines(orphans, purged, skipped) {
|
|
|
83
96
|
* resuelve—, y de ahí salieron tres de las cuatro regresiones de la Fase 3.
|
|
84
97
|
*
|
|
85
98
|
* `--allow-mass-purge` (3b-0b · AA2) desbloquea la cota que rechaza una
|
|
86
|
-
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
87
|
-
* más del 50 % de los roles locales
|
|
99
|
+
* pasada con la firma de un resolutor ciego (todos los owners huérfanos,
|
|
100
|
+
* más del 50 % de los roles locales, o —rotación 2 · R2-02— huérfanos de más
|
|
101
|
+
* de un owner con alguno que todavía concede): es una decisión humana, no un
|
|
102
|
+
* default. `--max-local-roles` (R2-04) sube la cota de la lectura.
|
|
88
103
|
*/
|
|
89
104
|
export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
90
105
|
static commandName = 'authz:catalog:prune-orphans';
|
|
@@ -94,9 +109,10 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
|
94
109
|
};
|
|
95
110
|
async run() {
|
|
96
111
|
const { default: authorization } = await import('../services/main.js');
|
|
97
|
-
const { orphans, purged, skipped, massPurge, dryRun } = await authorization.pruneOrphanRoles({
|
|
112
|
+
const { orphans, purged, skipped, massPurge, massPurgeReasons, dryRun } = await authorization.pruneOrphanRoles({
|
|
98
113
|
force: this.force === true,
|
|
99
114
|
allowMassPurge: this.allowMassPurge === true,
|
|
115
|
+
...(this.maxLocalRoles !== undefined ? { maxLocalRoles: this.maxLocalRoles } : {}),
|
|
100
116
|
});
|
|
101
117
|
// Los que todavía conceden —y los que NADIE puede demostrar que no
|
|
102
118
|
// concedan— van aparte, con aviso: purgarlos es revocar permisos vivos,
|
|
@@ -110,7 +126,8 @@ export default class AuthzCatalogPruneOrphans extends BaseCommand {
|
|
|
110
126
|
}
|
|
111
127
|
if (dryRun) {
|
|
112
128
|
if (massPurge) {
|
|
113
|
-
this.logger.warning(`${orphans.length} de los roles locales quedarían huérfanos: eso es la firma de un 'scopes.resolveChain' ciego
|
|
129
|
+
this.logger.warning(`${orphans.length} de los roles locales quedarían huérfanos: eso es la firma de un 'scopes.resolveChain' ciego ` +
|
|
130
|
+
`(${massPurgeLetter(massPurgeReasons ?? [])}). ` +
|
|
114
131
|
`Con --force esto se rechaza (E_AUTHZ_MASS_PURGE_REFUSED); comprueba el resolutor antes de añadir --allow-mass-purge.`);
|
|
115
132
|
}
|
|
116
133
|
this.logger.warning(`${orphans.length} rol(es) local(es) con el owner fuera del árbol. No se ha borrado nada: repite con --force.`);
|
|
@@ -128,7 +145,13 @@ __decorate([
|
|
|
128
145
|
], AuthzCatalogPruneOrphans.prototype, "force", void 0);
|
|
129
146
|
__decorate([
|
|
130
147
|
flags.boolean({
|
|
131
|
-
description: 'Allow a pass that would purge every owner
|
|
148
|
+
description: 'Allow a pass that would purge every owner, more than half the local roles, or orphans of several owners that still grant (or cannot be counted) — check your resolveChain first',
|
|
132
149
|
}),
|
|
133
150
|
__metadata("design:type", Object)
|
|
134
151
|
], AuthzCatalogPruneOrphans.prototype, "allowMassPurge", void 0);
|
|
152
|
+
__decorate([
|
|
153
|
+
flags.number({
|
|
154
|
+
description: 'Cap on the local roles this pass reads (default 10000); above it, 500 E_AUTHZ_TOO_MANY_LOCAL_ROLES, never a partial list',
|
|
155
|
+
}),
|
|
156
|
+
__metadata("design:type", Object)
|
|
157
|
+
], AuthzCatalogPruneOrphans.prototype, "maxLocalRoles", void 0);
|
|
@@ -207,7 +207,11 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
207
207
|
'Revísala en la cola (el consumidor puede volver a notificar el cambio) o reconcilia.');
|
|
208
208
|
}
|
|
209
209
|
if (report.busy) {
|
|
210
|
-
|
|
210
|
+
// Rotación 2 · R2-01b: la rama sigue saliendo 0 —respuesta del dueño en el cierre 2: la línea y `--max-age` son el
|
|
211
|
+
// detector; el caso fija exit 0 exacto (T-05)—, y la línea dice lo que puede ser.
|
|
212
|
+
this.logger.info('Otra pasada del relay tiene el lease de la cola: esta no ha aplicado nada (el relay es escritor único). ' +
|
|
213
|
+
'En MySQL, durante 2.5.x, el lease puede tenerlo OTRO despliegue del mismo servidor (el nombre de transición no ' +
|
|
214
|
+
'lleva la base; se retira en 2.6): si se repite en cada pasada, la cola no avanza — vigílala con --max-age.');
|
|
211
215
|
if (report.dead.length > 0)
|
|
212
216
|
this.exitCode = 1;
|
|
213
217
|
return;
|
|
@@ -69,7 +69,8 @@ export function requeueLines(input) {
|
|
|
69
69
|
level: 'error',
|
|
70
70
|
message: 'Otra pasada del relay tiene el lease de la cola: con --apply no se ha reencolado NADA (todo o nada: reencolar y no ' +
|
|
71
71
|
'aplicar dejaría la entrada con un solo intento, que la siguiente pasada gastaría). Reintenta (--wait-lease-ms espera ' +
|
|
72
|
-
'a que la suelten), o reencola sin --apply y deja que la próxima pasada la aplique.'
|
|
72
|
+
'a que la suelten), o reencola sin --apply y deja que la próxima pasada la aplique. En MySQL, durante 2.5.x, el ' +
|
|
73
|
+
'lease puede tenerlo OTRO despliegue del mismo servidor (el nombre de transición no lleva la base; se retira en 2.6).',
|
|
73
74
|
});
|
|
74
75
|
return { lines, clean: false };
|
|
75
76
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
|
+
import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
/**
|
|
4
|
+
* **`authz:upgrade-1x`** (rotación 3 · cierre 3, ESTRUCTURAL; firmado por el dueño el 2026-10-26).
|
|
5
|
+
*
|
|
6
|
+
* La subida 1.x → 2.x de MySQL deja de ser un bloque SQL que el operador copia y pasa a ser CÓDIGO que inspecciona el
|
|
7
|
+
* estado antes de tocar nada. Es el tercer defecto del linaje «receta del operador que no aguanta su re-ejecución»
|
|
8
|
+
* (R3-01 → RR-1 → A1/A2 del re-ataque 2, los tres confirmados en `mysql84`), y la regla de `CLAUDE.md` es que el
|
|
9
|
+
* tercero no se parchea: se quita el camino manual.
|
|
10
|
+
*
|
|
11
|
+
* Reparto (mismo patrón que `unfreezePlan`/`reconcileLines`):
|
|
12
|
+
* - `readUpgrade1xState` LEE (information_schema, el marcador y los datos) por UNA sesión;
|
|
13
|
+
* - `upgrade1xPlan(estado, banderas)` es la función PURA que DECIDE: estado detectado, negativa o pasos;
|
|
14
|
+
* - `run()` imprime el plan y, solo con `--force`, ejecuta los pasos por esa misma sesión.
|
|
15
|
+
*
|
|
16
|
+
* Qué hace la subida (lo que era el bloque MySQL del README): los `MODIFY` de collation/tipo, `expires_at` de
|
|
17
|
+
* `timestamp` a `datetime(3)` en la zona de sesión que tenía 1.x (`@@global.time_zone`), la CONVERSIÓN de cada
|
|
18
|
+
* caducidad de 1.x desde la zona de sus procesos (`--zone`) a UTC, `authz_catalog_version` con sus dos filas, y las
|
|
19
|
+
* columnas `owner_scope_key`/`assignable_at`. El marcador `authz_upgrade_1x` (fila `expires_at`) se ARMA antes del
|
|
20
|
+
* `MODIFY` y SOLO si la columna sigue siendo `timestamp` (cierra A1/B2: una base ya en `datetime` sin marcador no se
|
|
21
|
+
* convierte nunca por defecto); la conversión y el «hecho» van en UNA transacción (DML de InnoDB: todo o nada) y la
|
|
22
|
+
* conversión va ANTES de crear `authz_catalog_version`, así que ningún proceso 2.x ha podido escribir una fila que se
|
|
23
|
+
* convierta (sin esa tabla toda escritura de 2.x es 503).
|
|
24
|
+
*
|
|
25
|
+
* Cierre 4 de la rotación 3 (firmado 2026-10-26, «sí, firmo la a) con el 2 y el 3»), tres límites que el código solo no
|
|
26
|
+
* podía ver:
|
|
27
|
+
* - C1 · un proceso 1.x VIVO tras la conversión sigue escribiendo la pared de su zona en el `datetime(3)` y nada lo
|
|
28
|
+
* detecta después (el marcador ya dice `done`). Precondición OBLIGATORIA y CONFIRMADA: todo `--force` que convierte
|
|
29
|
+
* (`1x-sin-subir`, `a-medias` antes de `convert`, `--fix-previous-upgrade`) exige `--confirm-legacy-stopped`; sin
|
|
30
|
+
* ella, negativa con cero sentencias de escritura. El esquema NO se cambia para romper las escrituras de 1.x (opción
|
|
31
|
+
* b descartada por el dueño).
|
|
32
|
+
* - C2 · «la conversión y el done en UNA transacción» solo es verdad en InnoDB: el comando lee
|
|
33
|
+
* `information_schema.tables.engine` de `authz_assignments` y del marcador y, si alguno no es InnoDB, se niega
|
|
34
|
+
* (`motor-no-transaccional`) antes de tocar nada; las tablas que crea llevan `ENGINE=InnoDB` explícito.
|
|
35
|
+
* - P1 · una base NACIDA en 2.x no se distingue de una subida ENTERA con el bloque viejo: `--declare-born-in-2` es la
|
|
36
|
+
* DECLARACIÓN del operador (el comando no puede demostrar que no hubo 1.x) y escribe el marcador `done`/`born` sin
|
|
37
|
+
* tocar datos.
|
|
38
|
+
*
|
|
39
|
+
* Cierre 5 de la rotación 3 (firmado 2026-10-27): born se NIEGA donde el comando SÍ ve 1.x (`upgrade1xLegacyTraces`:
|
|
40
|
+
* DDL de 1.x pendiente o fechas cero, además de `timestamp` y del marcador), y `receta-vieja` deja de ofrecerlo ahí (B1,
|
|
41
|
+
* B1-bis); las banderas de la conversión se rechazan con born en vez de tragarse (H4).
|
|
42
|
+
*/
|
|
43
|
+
/** El estado detectado: la PRIMERA línea de la salida empieza por `estado: <clave>` (CLAUDE.md punto 7). */
|
|
44
|
+
export type Upgrade1xStateKey = 'no-mysql' | 'sin-tablas' | '1x-sin-subir' | 'a-medias' | 'receta-vieja' | 'hecho' | 'incoherente' | 'motor-no-transaccional';
|
|
45
|
+
/** Los DDL de la subida que se detectan pendientes uno a uno (cada uno es idempotente por inspección). */
|
|
46
|
+
export declare const UPGRADE_1X_PRE_CONVERT_DDL: readonly ["collation-roles", "collation-permissions", "assignments", "collation-denies"];
|
|
47
|
+
export declare const UPGRADE_1X_POST_CONVERT_DDL: readonly ["catalog-version", "roles-owner", "permissions-assignable"];
|
|
48
|
+
export type Upgrade1xDdl = (typeof UPGRADE_1X_PRE_CONVERT_DDL)[number] | (typeof UPGRADE_1X_POST_CONVERT_DDL)[number];
|
|
49
|
+
/** Un paso ejecutable, en el orden en que el plan los pone. */
|
|
50
|
+
export type Upgrade1xStep = 'zero-dates' | 'marker-table' | 'marker-arm' | 'convert' | 'fix-previous' | 'born' | Upgrade1xDdl;
|
|
51
|
+
/** Los pasos que CONVIERTEN caducidades: con `--force` exigen `--confirm-legacy-stopped` (C1). */
|
|
52
|
+
export declare const UPGRADE_1X_CONVERTING_STEPS: readonly Upgrade1xStep[];
|
|
53
|
+
export interface Upgrade1xMarker {
|
|
54
|
+
step: string;
|
|
55
|
+
/** `armed` (antes del MODIFY de `expires_at`, con la columna aún `timestamp`) o `done`. */
|
|
56
|
+
state: string;
|
|
57
|
+
/**
|
|
58
|
+
* `upgrade` (subida completa), `fix-previous` (corrección de quien subió con el bloque viejo) o `born` (el operador
|
|
59
|
+
* DECLARÓ la base nacida en 2.x: `--declare-born-in-2`; zona `+00:00`, ningún dato tocado).
|
|
60
|
+
*/
|
|
61
|
+
mode: string;
|
|
62
|
+
zone: string | null;
|
|
63
|
+
/** `--upgraded-at` de la corrección, ISO; `null` en `upgrade`. */
|
|
64
|
+
upgradedAt: string | null;
|
|
65
|
+
}
|
|
66
|
+
/** Lo que `readUpgrade1xState` lee. Las entradas de la tabla de verdad de `upgrade1xPlan`. */
|
|
67
|
+
export interface Upgrade1xState {
|
|
68
|
+
/** Nombre de la conexión de Lucid sobre la que se lee. */
|
|
69
|
+
connection: string;
|
|
70
|
+
/** `dialect.name` de esa conexión. Fuera de MySQL nada más se lee. */
|
|
71
|
+
dialect: string;
|
|
72
|
+
/** Faltan tablas de 1.x (`authz_roles`, `authz_permissions`, `authz_assignments`, `authz_denies`). */
|
|
73
|
+
missingTables: string[];
|
|
74
|
+
/**
|
|
75
|
+
* Tablas que la conversión escribe en UNA transacción (`authz_assignments` y, si existe, `authz_upgrade_1x`) con un
|
|
76
|
+
* motor que NO es InnoDB, como `tabla (MOTOR)` (C2: en MyISAM el ROLLBACK no deshace nada). Ausente = ninguna.
|
|
77
|
+
*/
|
|
78
|
+
nonTransactional?: string[];
|
|
79
|
+
/** `COLUMN_TYPE` de `authz_assignments.expires_at` (`timestamp`, `datetime(3)`, …), o `null` si no existe. */
|
|
80
|
+
expiresType: string | null;
|
|
81
|
+
/** Filas de `authz_upgrade_1x`; `null` si la tabla no existe; `'malformed'` si existe con otra forma. */
|
|
82
|
+
marker: Upgrade1xMarker[] | null | 'malformed';
|
|
83
|
+
/** DDL pendientes, en orden canónico. */
|
|
84
|
+
pendingDdl: Upgrade1xDdl[];
|
|
85
|
+
/** Combinaciones de esquema que ningún punto de corte de este comando produce (índices a medias, …). */
|
|
86
|
+
schemaOdd: string[];
|
|
87
|
+
/** ¿Existe `authz_catalog_version`? (con ella un proceso 2.x puede haber escrito). */
|
|
88
|
+
catalogVersionTable: boolean;
|
|
89
|
+
/** Filas con `expires_at` no nulo. */
|
|
90
|
+
expiring: number;
|
|
91
|
+
/** Caducidades presentes que MySQL no puede convertir (fecha cero, mes/día cero): `CONVERT_TZ` da `NULL` (A2). */
|
|
92
|
+
unreadable: number;
|
|
93
|
+
/** Zonas sondeadas (`--zone`, la del marcador) → ¿`CONVERT_TZ` las convierte? (tablas de zonas cargadas). */
|
|
94
|
+
zones: Record<string, boolean>;
|
|
95
|
+
/** Milisegundo del UUIDv7 más antiguo de `authz_assignments` (`null` si no hay ninguno v7). */
|
|
96
|
+
oldestV7Ms: number | null;
|
|
97
|
+
/** Filas cuyo `uuid` no tiene forma de UUIDv7. */
|
|
98
|
+
nonV7: number;
|
|
99
|
+
/** Con `--fix-previous-upgrade`: filas con caducidad insertadas antes de `--upgraded-at` (de 1.x). */
|
|
100
|
+
fixBefore: number | null;
|
|
101
|
+
/** …y de ellas, las que la conversión ADELANTA (las únicas que la corrección mueve). */
|
|
102
|
+
fixCandidates: number | null;
|
|
103
|
+
/** Reloj de pared (`systemClock`), para «`--upgraded-at` no es futuro». */
|
|
104
|
+
nowMs: number;
|
|
105
|
+
}
|
|
106
|
+
export interface Upgrade1xFlags {
|
|
107
|
+
zone?: string;
|
|
108
|
+
force?: boolean;
|
|
109
|
+
dryRun?: boolean;
|
|
110
|
+
fixPreviousUpgrade?: boolean;
|
|
111
|
+
upgradedAt?: string;
|
|
112
|
+
zeroDates?: string;
|
|
113
|
+
/** C1: el operador CONFIRMA que todos los procesos 1.x están parados (obligatoria con `--force` donde se convierte). */
|
|
114
|
+
confirmLegacyStopped?: boolean;
|
|
115
|
+
/** P1: el operador DECLARA que la base nació en 2.x (nunca tuvo datos de 1.x). */
|
|
116
|
+
declareBornIn2?: boolean;
|
|
117
|
+
}
|
|
118
|
+
export interface Upgrade1xLine {
|
|
119
|
+
level: 'info' | 'warning' | 'error' | 'success';
|
|
120
|
+
message: string;
|
|
121
|
+
}
|
|
122
|
+
export interface Upgrade1xPlan {
|
|
123
|
+
state: Upgrade1xStateKey;
|
|
124
|
+
/** `noop` (nada que hacer, exit 0), `refuse` (exit 1, nada escrito) o `run` (hay pasos). */
|
|
125
|
+
action: 'noop' | 'refuse' | 'run';
|
|
126
|
+
/** `run` con `--force`: se ejecutan los pasos. `run` sin él es la simulación (exit 0, cero escrituras). */
|
|
127
|
+
write: boolean;
|
|
128
|
+
exitCode: 0 | 1;
|
|
129
|
+
/** La zona con la que se convierte (bandera o marcador). */
|
|
130
|
+
zone: string | null;
|
|
131
|
+
upgradedAtMs: number | null;
|
|
132
|
+
steps: Upgrade1xStep[];
|
|
133
|
+
/** La primera es SIEMPRE `estado: <clave> · …`. */
|
|
134
|
+
lines: Upgrade1xLine[];
|
|
135
|
+
}
|
|
136
|
+
/** La zona que guarda el marcador `born`: 2.x siempre escribió UTC (columna `zone` NOT NULL). */
|
|
137
|
+
export declare const UPGRADE_1X_BORN_ZONE = "+00:00";
|
|
138
|
+
/** La letra de la negativa de born con rastro de 1.x (rotación 3 · cierre 5, B1/B1-bis). */
|
|
139
|
+
export declare const UPGRADE_1X_BORN_TRACE_TEXT = "hay rastro de 1.x, born no aplica";
|
|
140
|
+
/**
|
|
141
|
+
* Los RASTROS de 1.x que el comando DETECTA en una base con `expires_at` ya `datetime(3)` y sin marcador (rotación 3 ·
|
|
142
|
+
* cierre 5, B1/B1-bis). Ninguna migración publicada de 2.x deja un paso de esquema pendiente (crean `authz_catalog_version`
|
|
143
|
+
* con sus dos filas, `owner_scope_key` con el unique de tres columnas, `assignable_at` y las columnas `utf8mb4_bin`), y el
|
|
144
|
+
* codec de 2.x no escribe fechas cero. Sin ninguno de los dos, una base nacida en 2.x y una subida entera con el bloque
|
|
145
|
+
* viejo tienen la misma forma: ahí `--declare-born-in-2` sigue siendo una DECLARACIÓN del operador.
|
|
146
|
+
*/
|
|
147
|
+
export declare function upgrade1xLegacyTraces(state: Pick<Upgrade1xState, 'pendingDdl' | 'unreadable'>): string[];
|
|
148
|
+
/**
|
|
149
|
+
* La precondición de C1, en una frase: la usan la negativa, la línea del plan y la simulación. Un proceso 1.x vivo tras
|
|
150
|
+
* la conversión escribe la PARED de su zona en el `datetime(3)` que 2.x lee como UTC, y ya nada lo detecta.
|
|
151
|
+
*/
|
|
152
|
+
export declare const UPGRADE_1X_LEGACY_STOPPED_TEXT: string;
|
|
153
|
+
/** El instante en el que `--zero-dates=expire` deja una caducidad ilegible: PASADO, nunca `NULL` (A2). */
|
|
154
|
+
export declare const UPGRADE_1X_EXPIRED_INSTANT = 86400;
|
|
155
|
+
/** `--upgraded-at` exige ISO con zona explícita (`Z` o `±hh:mm`): sin ella JavaScript la leería en la hora local. */
|
|
156
|
+
export declare function parseUpgradedAt(value: string | undefined): number | null;
|
|
157
|
+
/** El prefijo de UUIDv7 de un milisegundo (`xxxxxxxx-xxxx`): las filas insertadas antes ordenan por debajo, por bytes. */
|
|
158
|
+
export declare function v7Prefix(ms: number): string;
|
|
159
|
+
export declare function describeUpgrade1xStep(step: Upgrade1xStep): string;
|
|
160
|
+
/**
|
|
161
|
+
* Decide, sin I/O, qué hacer con el estado detectado. Tabla de verdad (la fija `command_flags_upgrade_1x.spec.ts`):
|
|
162
|
+
*
|
|
163
|
+
* | `expires_at` | marcador | otras entradas | estado | acción |
|
|
164
|
+
* |---|---|---|---|---|
|
|
165
|
+
* | (no MySQL) | — | — | `no-mysql` | noop, exit 0, cero sentencias |
|
|
166
|
+
* | falta una tabla de 1.x | — | — | `sin-tablas` | negativa |
|
|
167
|
+
* | `timestamp` | ninguno | sin `authz_catalog_version` | `1x-sin-subir` | subida completa; exige `--zone` |
|
|
168
|
+
* | `timestamp` | `armed` | sin `authz_catalog_version` | `a-medias` | reanuda con la zona del marcador |
|
|
169
|
+
* | `timestamp` | `done` | — | `incoherente` (B2: volcado de 1.x restaurado) | negativa |
|
|
170
|
+
* | `timestamp` | ninguno/`armed` | con `authz_catalog_version` | `incoherente` (2.x pudo escribir en la columna de 1.x) | negativa |
|
|
171
|
+
* | `datetime(3)` | ninguno | alguna caducidad o DDL pendiente | `receta-vieja` | negativa; con `--fix-previous-upgrade` corrige |
|
|
172
|
+
* | `datetime(3)` | ninguno | ninguna caducidad y nada pendiente | `hecho` | noop |
|
|
173
|
+
* | `datetime(3)` | `armed` | sin `authz_catalog_version` | `a-medias` | convierte y termina |
|
|
174
|
+
* | `datetime(3)` | `done` | DDL pendiente | `a-medias` | termina el DDL |
|
|
175
|
+
* | `datetime(3)` | `done` | nada pendiente | `hecho` | noop (otra `--zone` ⇒ negativa) |
|
|
176
|
+
* | otro tipo, marcador con otra forma, índices a medias | | | `incoherente` | negativa |
|
|
177
|
+
* | (cualquiera) | (cualquiera) | `authz_assignments` o el marcador no son InnoDB | `motor-no-transaccional` (C2) | negativa |
|
|
178
|
+
* | `datetime(3)` | ninguno | `--declare-born-in-2`, sin rastro de 1.x | `receta-vieja` / `hecho` | escribe el marcador `done`/`born`, cero datos (P1) |
|
|
179
|
+
* | `datetime(3)` | ninguno | `--declare-born-in-2` + DDL pendiente o fechas cero | `receta-vieja` | negativa: «hay rastro de 1.x, born no aplica» (B1) |
|
|
180
|
+
* | (cualquiera) | (cualquiera) | `--declare-born-in-2` + `--zero-dates`/`--confirm-legacy-stopped`/`--zone`/fix | | negativa (H4) |
|
|
181
|
+
*
|
|
182
|
+
* Y en todo estado que convierte: zona desconocida para MySQL ⇒ negativa ANTES de tocar nada; caducidades ilegibles ⇒
|
|
183
|
+
* negativa salvo `--zero-dates=expire`; con `--force`, sin `--confirm-legacy-stopped` ⇒ negativa (C1). Escribir exige
|
|
184
|
+
* `--force`; sin él es la simulación (que menciona la precondición).
|
|
185
|
+
*/
|
|
186
|
+
export declare function upgrade1xPlan(state: Upgrade1xState, flags: Upgrade1xFlags): Upgrade1xPlan;
|
|
187
|
+
type Query = (sql: string, bindings?: unknown[]) => Promise<any[]>;
|
|
188
|
+
export declare function readUpgrade1xState(q: Query, connection: string, flags: Upgrade1xFlags, nowMs: number): Promise<Upgrade1xState>;
|
|
189
|
+
export declare function runUpgrade1xStep(q: Query, step: Upgrade1xStep, plan: Upgrade1xPlan): Promise<string>;
|
|
190
|
+
/**
|
|
191
|
+
* node ace authz:upgrade-1x --zone=Europe/Madrid # simulación: estado detectado + plan, cero escrituras
|
|
192
|
+
* node ace authz:upgrade-1x --zone=Europe/Madrid --confirm-legacy-stopped --force # sube (re-ejecutable tras un corte)
|
|
193
|
+
* node ace authz:upgrade-1x --fix-previous-upgrade --zone=Europe/Madrid --upgraded-at=2026-09-01T10:00:00Z --confirm-legacy-stopped --force
|
|
194
|
+
* node ace authz:upgrade-1x --declare-born-in-2 --force # base nacida en 2.x: marcador, cero datos
|
|
195
|
+
*/
|
|
196
|
+
export default class AuthzUpgrade1x extends BaseCommand {
|
|
197
|
+
static commandName: string;
|
|
198
|
+
static description: string;
|
|
199
|
+
static options: CommandOptions;
|
|
200
|
+
/**
|
|
201
|
+
* Gancho de TEST (cortes): se llama tras cada paso confirmado; si lanza, el comando se comporta como un proceso
|
|
202
|
+
* cortado ahí (exit 1, lo hecho queda hecho). Nadie lo pone en producción.
|
|
203
|
+
*/
|
|
204
|
+
static afterStep: ((step: Upgrade1xStep) => void | Promise<void>) | undefined;
|
|
205
|
+
zone: string | undefined;
|
|
206
|
+
force: boolean | undefined;
|
|
207
|
+
dryRun: boolean | undefined;
|
|
208
|
+
fixPreviousUpgrade: boolean | undefined;
|
|
209
|
+
upgradedAt: string | undefined;
|
|
210
|
+
zeroDates: string | undefined;
|
|
211
|
+
confirmLegacyStopped: boolean | undefined;
|
|
212
|
+
declareBornIn2: boolean | undefined;
|
|
213
|
+
connection: string | undefined;
|
|
214
|
+
run(): Promise<void>;
|
|
215
|
+
}
|
|
216
|
+
export {};
|