@jantstack/adonis-authz 2.5.0-alpha.3 → 2.5.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +414 -35
- package/build/commands/authz_catalog_diff.d.ts +0 -1
- package/build/commands/authz_catalog_diff.js +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts +0 -1
- package/build/commands/authz_catalog_prune_orphans.js +0 -1
- package/build/commands/authz_catalog_sync.d.ts +0 -1
- package/build/commands/authz_catalog_sync.js +0 -1
- package/build/commands/authz_freeze.d.ts +0 -1
- package/build/commands/authz_freeze.js +0 -1
- package/build/commands/authz_reconcile.d.ts +0 -1
- package/build/commands/authz_reconcile.js +0 -1
- package/build/commands/authz_relations_reconcile.d.ts +0 -1
- package/build/commands/authz_relations_reconcile.js +56 -8
- package/build/commands/authz_scopes_relay.d.ts +0 -1
- package/build/commands/authz_scopes_relay.js +49 -5
- package/build/commands/authz_scopes_requeue.d.ts +0 -1
- package/build/commands/authz_scopes_requeue.js +14 -2
- package/build/commands/authz_unfreeze.d.ts +0 -1
- package/build/commands/authz_unfreeze.js +0 -1
- package/build/commands/main.d.ts +0 -1
- package/build/commands/main.js +0 -1
- package/build/commands/openfga_provision.d.ts +10 -1
- package/build/commands/openfga_provision.js +43 -3
- package/build/configure.d.ts +0 -1
- package/build/configure.js +0 -1
- package/build/index.d.ts +7 -5
- package/build/index.js +10 -3
- package/build/providers/authz_provider.d.ts +0 -1
- package/build/providers/authz_provider.js +0 -1
- package/build/services/main.d.ts +0 -1
- package/build/services/main.js +0 -1
- package/build/services/relations.d.ts +0 -1
- package/build/services/relations.js +0 -1
- package/build/src/catalog/catalog.d.ts +0 -1
- package/build/src/catalog/catalog.js +0 -1
- package/build/src/catalog/catalog_cache.d.ts +13 -1
- package/build/src/catalog/catalog_cache.js +25 -1
- package/build/src/clock.d.ts +0 -1
- package/build/src/clock.js +0 -1
- package/build/src/define_config.d.ts +0 -1
- package/build/src/define_config.js +24 -1
- package/build/src/drivers/database_driver.d.ts +6 -1
- package/build/src/drivers/database_driver.js +22 -2
- package/build/src/drivers/database_relations_driver.d.ts +11 -5
- package/build/src/drivers/database_relations_driver.js +90 -18
- package/build/src/drivers/openfga_driver.d.ts +159 -26
- package/build/src/drivers/openfga_driver.js +882 -199
- package/build/src/drivers/openfga_facts.d.ts +91 -1
- package/build/src/drivers/openfga_facts.js +277 -3
- package/build/src/drivers/openfga_relations_driver.d.ts +41 -13
- package/build/src/drivers/openfga_relations_driver.js +422 -228
- package/build/src/errors.d.ts +121 -1
- package/build/src/errors.js +153 -1
- package/build/src/expiry.d.ts +51 -2
- package/build/src/expiry.js +96 -2
- package/build/src/freeze.d.ts +0 -1
- package/build/src/freeze.js +0 -1
- package/build/src/hierarchical_resolver.d.ts +0 -1
- package/build/src/hierarchical_resolver.js +0 -1
- package/build/src/http/app_access_middleware.d.ts +0 -1
- package/build/src/http/app_access_middleware.js +0 -1
- package/build/src/http/resource_access_middleware.d.ts +0 -1
- package/build/src/http/resource_access_middleware.js +0 -1
- package/build/src/identity.d.ts +2 -1
- package/build/src/identity.js +4 -1
- package/build/src/manager.d.ts +0 -1
- package/build/src/manager.js +156 -28
- package/build/src/memoize_ancestors.d.ts +0 -1
- package/build/src/memoize_ancestors.js +0 -1
- package/build/src/models/authz_assignment.d.ts +5 -6
- package/build/src/models/authz_assignment.js +0 -1
- package/build/src/models/authz_deny.d.ts +5 -6
- package/build/src/models/authz_deny.js +0 -1
- package/build/src/models/authz_permission.d.ts +5 -6
- package/build/src/models/authz_permission.js +0 -1
- package/build/src/models/authz_role.d.ts +5 -6
- package/build/src/models/authz_role.js +0 -1
- package/build/src/models/authz_role_permission.d.ts +5 -6
- package/build/src/models/authz_role_permission.js +0 -1
- package/build/src/openfga.d.ts +2 -3
- package/build/src/openfga.js +4 -2
- package/build/src/reconcile.d.ts +0 -1
- package/build/src/reconcile.js +0 -1
- package/build/src/relation_partition_trigger.d.ts +0 -1
- package/build/src/relation_partition_trigger.js +0 -1
- package/build/src/relations/check_many.d.ts +0 -1
- package/build/src/relations/check_many.js +0 -1
- package/build/src/relations/define_relations_config.d.ts +25 -3
- package/build/src/relations/define_relations_config.js +8 -3
- package/build/src/relations/deny_reach.d.ts +102 -0
- package/build/src/relations/deny_reach.js +230 -0
- package/build/src/relations/group_depth.d.ts +215 -0
- package/build/src/relations/group_depth.js +453 -0
- package/build/src/relations/manager.d.ts +14 -7
- package/build/src/relations/manager.js +60 -15
- package/build/src/relations/page.d.ts +19 -0
- package/build/src/relations/page.js +74 -0
- package/build/src/relations/reconcile.d.ts +58 -4
- package/build/src/relations/reconcile.js +266 -12
- package/build/src/relations/tuple_key.d.ts +0 -1
- package/build/src/relations/tuple_key.js +0 -1
- package/build/src/relations/tuple_order.d.ts +5 -4
- package/build/src/relations/tuple_order.js +5 -4
- package/build/src/relations_config_store.d.ts +57 -2
- package/build/src/relations_config_store.js +155 -5
- package/build/src/relations_reconcile.d.ts +0 -1
- package/build/src/relations_reconcile.js +0 -1
- package/build/src/scope_outbox.d.ts +0 -1
- package/build/src/scope_outbox.js +0 -1
- package/build/src/shared/backend_guard.d.ts +52 -1
- package/build/src/shared/backend_guard.js +164 -2
- package/build/src/shared/sql_expiry.d.ts +0 -1
- package/build/src/shared/sql_expiry.js +0 -1
- package/build/src/shared/transaction_guard.d.ts +0 -1
- package/build/src/shared/transaction_guard.js +0 -1
- package/build/src/sql_descendants.d.ts +0 -1
- package/build/src/sql_descendants.js +0 -1
- package/build/src/testing/contract.d.ts +7 -1
- package/build/src/testing/contract.js +77 -1
- package/build/src/testing/main.d.ts +0 -1
- package/build/src/testing/main.js +0 -1
- package/build/src/testing/migration_contract.d.ts +0 -1
- package/build/src/testing/migration_contract.js +0 -1
- package/build/src/testing/relations_contract.d.ts +7 -1
- package/build/src/testing/relations_contract.js +174 -7
- package/build/src/testing/relations_reconcile_contract.d.ts +0 -1
- package/build/src/testing/relations_reconcile_contract.js +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts +0 -1
- package/build/src/testing/scope_outbox_contract.js +0 -1
- package/build/src/testing/scope_tree.d.ts +0 -1
- package/build/src/testing/scope_tree.js +0 -1
- package/build/src/testing/test_clock.d.ts +0 -1
- package/build/src/testing/test_clock.js +0 -1
- package/build/src/traits/authz_scopes.d.ts +0 -1
- package/build/src/traits/authz_scopes.js +0 -1
- package/build/src/traits/has_uuid.d.ts +5 -6
- package/build/src/traits/has_uuid.js +0 -1
- package/build/src/types.d.ts +89 -14
- package/build/src/types.js +4 -1
- package/build/stubs/config/authorization.stub +22 -6
- package/build/stubs/main.d.ts +0 -1
- package/build/stubs/main.js +0 -1
- package/package.json +4 -3
- package/build/commands/authz_catalog_diff.d.ts.map +0 -1
- package/build/commands/authz_catalog_diff.js.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.d.ts.map +0 -1
- package/build/commands/authz_catalog_prune_orphans.js.map +0 -1
- package/build/commands/authz_catalog_sync.d.ts.map +0 -1
- package/build/commands/authz_catalog_sync.js.map +0 -1
- package/build/commands/authz_freeze.d.ts.map +0 -1
- package/build/commands/authz_freeze.js.map +0 -1
- package/build/commands/authz_reconcile.d.ts.map +0 -1
- package/build/commands/authz_reconcile.js.map +0 -1
- package/build/commands/authz_relations_reconcile.d.ts.map +0 -1
- package/build/commands/authz_relations_reconcile.js.map +0 -1
- package/build/commands/authz_scopes_relay.d.ts.map +0 -1
- package/build/commands/authz_scopes_relay.js.map +0 -1
- package/build/commands/authz_scopes_requeue.d.ts.map +0 -1
- package/build/commands/authz_scopes_requeue.js.map +0 -1
- package/build/commands/authz_unfreeze.d.ts.map +0 -1
- package/build/commands/authz_unfreeze.js.map +0 -1
- package/build/commands/main.d.ts.map +0 -1
- package/build/commands/main.js.map +0 -1
- package/build/commands/openfga_provision.d.ts.map +0 -1
- package/build/commands/openfga_provision.js.map +0 -1
- package/build/configure.d.ts.map +0 -1
- package/build/configure.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/providers/authz_provider.d.ts.map +0 -1
- package/build/providers/authz_provider.js.map +0 -1
- package/build/services/main.d.ts.map +0 -1
- package/build/services/main.js.map +0 -1
- package/build/services/relations.d.ts.map +0 -1
- package/build/services/relations.js.map +0 -1
- package/build/src/catalog/catalog.d.ts.map +0 -1
- package/build/src/catalog/catalog.js.map +0 -1
- package/build/src/catalog/catalog_cache.d.ts.map +0 -1
- package/build/src/catalog/catalog_cache.js.map +0 -1
- package/build/src/clock.d.ts.map +0 -1
- package/build/src/clock.js.map +0 -1
- package/build/src/define_config.d.ts.map +0 -1
- package/build/src/define_config.js.map +0 -1
- package/build/src/drivers/database_driver.d.ts.map +0 -1
- package/build/src/drivers/database_driver.js.map +0 -1
- package/build/src/drivers/database_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/database_relations_driver.js.map +0 -1
- package/build/src/drivers/openfga_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_driver.js.map +0 -1
- package/build/src/drivers/openfga_facts.d.ts.map +0 -1
- package/build/src/drivers/openfga_facts.js.map +0 -1
- package/build/src/drivers/openfga_relations_driver.d.ts.map +0 -1
- package/build/src/drivers/openfga_relations_driver.js.map +0 -1
- package/build/src/errors.d.ts.map +0 -1
- package/build/src/errors.js.map +0 -1
- package/build/src/expiry.d.ts.map +0 -1
- package/build/src/expiry.js.map +0 -1
- package/build/src/freeze.d.ts.map +0 -1
- package/build/src/freeze.js.map +0 -1
- package/build/src/hierarchical_resolver.d.ts.map +0 -1
- package/build/src/hierarchical_resolver.js.map +0 -1
- package/build/src/http/app_access_middleware.d.ts.map +0 -1
- package/build/src/http/app_access_middleware.js.map +0 -1
- package/build/src/http/resource_access_middleware.d.ts.map +0 -1
- package/build/src/http/resource_access_middleware.js.map +0 -1
- package/build/src/identity.d.ts.map +0 -1
- package/build/src/identity.js.map +0 -1
- package/build/src/manager.d.ts.map +0 -1
- package/build/src/manager.js.map +0 -1
- package/build/src/memoize_ancestors.d.ts.map +0 -1
- package/build/src/memoize_ancestors.js.map +0 -1
- package/build/src/models/authz_assignment.d.ts.map +0 -1
- package/build/src/models/authz_assignment.js.map +0 -1
- package/build/src/models/authz_deny.d.ts.map +0 -1
- package/build/src/models/authz_deny.js.map +0 -1
- package/build/src/models/authz_permission.d.ts.map +0 -1
- package/build/src/models/authz_permission.js.map +0 -1
- package/build/src/models/authz_role.d.ts.map +0 -1
- package/build/src/models/authz_role.js.map +0 -1
- package/build/src/models/authz_role_permission.d.ts.map +0 -1
- package/build/src/models/authz_role_permission.js.map +0 -1
- package/build/src/openfga.d.ts.map +0 -1
- package/build/src/openfga.js.map +0 -1
- package/build/src/reconcile.d.ts.map +0 -1
- package/build/src/reconcile.js.map +0 -1
- package/build/src/relation_partition_trigger.d.ts.map +0 -1
- package/build/src/relation_partition_trigger.js.map +0 -1
- package/build/src/relations/check_many.d.ts.map +0 -1
- package/build/src/relations/check_many.js.map +0 -1
- package/build/src/relations/define_relations_config.d.ts.map +0 -1
- package/build/src/relations/define_relations_config.js.map +0 -1
- package/build/src/relations/manager.d.ts.map +0 -1
- package/build/src/relations/manager.js.map +0 -1
- package/build/src/relations/reconcile.d.ts.map +0 -1
- package/build/src/relations/reconcile.js.map +0 -1
- package/build/src/relations/tuple_key.d.ts.map +0 -1
- package/build/src/relations/tuple_key.js.map +0 -1
- package/build/src/relations/tuple_order.d.ts.map +0 -1
- package/build/src/relations/tuple_order.js.map +0 -1
- package/build/src/relations_config_store.d.ts.map +0 -1
- package/build/src/relations_config_store.js.map +0 -1
- package/build/src/relations_reconcile.d.ts.map +0 -1
- package/build/src/relations_reconcile.js.map +0 -1
- package/build/src/scope_outbox.d.ts.map +0 -1
- package/build/src/scope_outbox.js.map +0 -1
- package/build/src/shared/backend_guard.d.ts.map +0 -1
- package/build/src/shared/backend_guard.js.map +0 -1
- package/build/src/shared/sql_expiry.d.ts.map +0 -1
- package/build/src/shared/sql_expiry.js.map +0 -1
- package/build/src/shared/transaction_guard.d.ts.map +0 -1
- package/build/src/shared/transaction_guard.js.map +0 -1
- package/build/src/sql_descendants.d.ts.map +0 -1
- package/build/src/sql_descendants.js.map +0 -1
- package/build/src/testing/contract.d.ts.map +0 -1
- package/build/src/testing/contract.js.map +0 -1
- package/build/src/testing/main.d.ts.map +0 -1
- package/build/src/testing/main.js.map +0 -1
- package/build/src/testing/migration_contract.d.ts.map +0 -1
- package/build/src/testing/migration_contract.js.map +0 -1
- package/build/src/testing/relations_contract.d.ts.map +0 -1
- package/build/src/testing/relations_contract.js.map +0 -1
- package/build/src/testing/relations_reconcile_contract.d.ts.map +0 -1
- package/build/src/testing/relations_reconcile_contract.js.map +0 -1
- package/build/src/testing/scope_outbox_contract.d.ts.map +0 -1
- package/build/src/testing/scope_outbox_contract.js.map +0 -1
- package/build/src/testing/scope_tree.d.ts.map +0 -1
- package/build/src/testing/scope_tree.js.map +0 -1
- package/build/src/testing/test_clock.d.ts.map +0 -1
- package/build/src/testing/test_clock.js.map +0 -1
- package/build/src/traits/authz_scopes.d.ts.map +0 -1
- package/build/src/traits/authz_scopes.js.map +0 -1
- package/build/src/traits/has_uuid.d.ts.map +0 -1
- package/build/src/traits/has_uuid.js.map +0 -1
- package/build/src/types.d.ts.map +0 -1
- package/build/src/types.js.map +0 -1
- package/build/stubs/main.d.ts.map +0 -1
- package/build/stubs/main.js.map +0 -1
|
@@ -13,4 +13,3 @@ export { runRelationsDriverContract, registerRelationsDriverContract, makeRelati
|
|
|
13
13
|
export { createTestClock } from './test_clock.js';
|
|
14
14
|
export { runRelationsReconcileContract, registerRelationsReconcileContract, } from './relations_reconcile_contract.js';
|
|
15
15
|
export { runScopeOutboxContract, registerScopeOutboxContract, makeScopeOutbox, SCOPE_OUTBOX_CAPABILITY_KEYS, } from './scope_outbox_contract.js';
|
|
16
|
-
//# sourceMappingURL=main.js.map
|
|
@@ -290,4 +290,3 @@ export declare function runMigrationDirection(harness: MigrationContractHarness,
|
|
|
290
290
|
*/
|
|
291
291
|
export declare function runMigrationContract(harness: MigrationContractHarness): void;
|
|
292
292
|
export declare function registerMigrationContract(harness: MigrationContractHarness, api: ContractTestApi): void;
|
|
293
|
-
//# sourceMappingURL=migration_contract.d.ts.map
|
|
@@ -22,6 +22,13 @@ export declare const RELATIONS_CONTRACT_OBJECT_TYPES: readonly ["document", "wor
|
|
|
22
22
|
* editor ⊆ viewer` (includes de un nivel) y `membersOf` habilitado en `database`.
|
|
23
23
|
*/
|
|
24
24
|
export declare function contractRelationsConfig(): RelationsConfig;
|
|
25
|
+
/**
|
|
26
|
+
* **La config del contrato CON techo de anidación de grupos** (lote «antes de RC» · R1, cierre: el runner publicado no
|
|
27
|
+
* tenía ningún caso del techo y un driver de terceros no tenía juez): la MISMA config que `contractRelationsConfig` con
|
|
28
|
+
* `maxGroupDepth` (por defecto 3) y el opt-in de `database` (`database.enforceGroupDepth`). Un driver que declare
|
|
29
|
+
* `groupDepthLimit()` tiene que responder con un techo ≤ ese número (la config solo BAJA el del driver) y aplicarlo.
|
|
30
|
+
*/
|
|
31
|
+
export declare function contractGroupDepthRelationsConfig(maxGroupDepth?: number): RelationsConfig;
|
|
25
32
|
/**
|
|
26
33
|
* **La config del contrato CON deny** (plan 2.5 · lote D · D1): los MISMOS dos tipos con el mismo retículo y, además,
|
|
27
34
|
* un tercero sin opt-in —para que «deny sobre un tipo sin deny ⇒ 422» y «D8: un tipo sin opt-in también honra»
|
|
@@ -102,4 +109,3 @@ export declare function registerRelationsDriverContract(harness: RelationsDriver
|
|
|
102
109
|
registered: number;
|
|
103
110
|
covered: Set<keyof RelationsDriverCapabilities>;
|
|
104
111
|
};
|
|
105
|
-
//# sourceMappingURL=relations_contract.d.ts.map
|
|
@@ -25,6 +25,7 @@ import { RenewalUnsafeError } from '../errors.js';
|
|
|
25
25
|
import { RelationsManager } from '../relations/manager.js';
|
|
26
26
|
import { defineRelationsConfig, assertRelationDeclared, assertObjectTypeDeclared, assertUsersetSubject, assertRelationDenyable, assertDenySubject, assertDenyNoExpiry, cutPathAllows, decideFrom, deniedRelationOf, denyPhaseOf, storedRelationKind, relationsDown, RELATION_DENY_PREFIX, } from '../relations/define_relations_config.js';
|
|
27
27
|
import { assertCheckManyObjects, distinctObjects, expandByPosition, RELATIONS_CHECK_MANY_MAX } from '../relations/check_many.js';
|
|
28
|
+
import { assertEnumeratePage, assertNoListPage } from '../relations/page.js';
|
|
28
29
|
import { systemClock } from '../clock.js';
|
|
29
30
|
import { resolveGrantExpiry, sameInstant } from '../expiry.js';
|
|
30
31
|
import { isRelUserset } from '../types.js';
|
|
@@ -59,6 +60,23 @@ export function contractRelationsConfig() {
|
|
|
59
60
|
database: { membersOf: true },
|
|
60
61
|
});
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* **La config del contrato CON techo de anidación de grupos** (lote «antes de RC» · R1, cierre: el runner publicado no
|
|
65
|
+
* tenía ningún caso del techo y un driver de terceros no tenía juez): la MISMA config que `contractRelationsConfig` con
|
|
66
|
+
* `maxGroupDepth` (por defecto 3) y el opt-in de `database` (`database.enforceGroupDepth`). Un driver que declare
|
|
67
|
+
* `groupDepthLimit()` tiene que responder con un techo ≤ ese número (la config solo BAJA el del driver) y aplicarlo.
|
|
68
|
+
*/
|
|
69
|
+
export function contractGroupDepthRelationsConfig(maxGroupDepth = 3) {
|
|
70
|
+
return defineRelationsConfig({
|
|
71
|
+
objectTypes: RELATIONS_CONTRACT_OBJECT_TYPES.map((type) => ({
|
|
72
|
+
type,
|
|
73
|
+
relations: [{ name: 'owner' }, { name: 'editor', includes: ['owner'] }, { name: 'viewer', includes: ['editor'] }],
|
|
74
|
+
})),
|
|
75
|
+
holderTypes: [...RELATIONS_HOLDER_TYPES],
|
|
76
|
+
database: { membersOf: true, enforceGroupDepth: true },
|
|
77
|
+
maxGroupDepth,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
62
80
|
/**
|
|
63
81
|
* **La config del contrato CON deny** (plan 2.5 · lote D · D1): los MISMOS dos tipos con el mismo retículo y, además,
|
|
64
82
|
* un tercero sin opt-in —para que «deny sobre un tipo sin deny ⇒ 422» y «D8: un tipo sin opt-in también honra»
|
|
@@ -308,10 +326,12 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
308
326
|
assertSubjectDeclared(subject);
|
|
309
327
|
return resolves(subject, relation, object, scopeKeyOf(partition));
|
|
310
328
|
},
|
|
311
|
-
async listObjects(subject, relation, objectType, partition
|
|
329
|
+
async listObjects(subject, relation, objectType, partition) {
|
|
312
330
|
assertRelationDeclared(config, { type: objectType }, relation);
|
|
313
331
|
assertSubjectDeclared(subject);
|
|
314
332
|
const partitionKey = scopeKeyOf(partition);
|
|
333
|
+
// 7.4: el doble tampoco pagina (un quinto argumento ≠ undefined ⇒ 500), después de F-05 y la partición.
|
|
334
|
+
assertNoListPage('listObjects', arguments[4], 'double');
|
|
315
335
|
const ids = new Set();
|
|
316
336
|
for (const t of tuples) {
|
|
317
337
|
if (t.partition === partitionKey && t.objectType === objectType && active(t))
|
|
@@ -332,9 +352,10 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
332
352
|
}
|
|
333
353
|
return { objects: matches };
|
|
334
354
|
},
|
|
335
|
-
async listSubjects(relation, object, partition
|
|
355
|
+
async listSubjects(relation, object, partition) {
|
|
336
356
|
assertRelationDeclared(config, object, relation);
|
|
337
357
|
const partitionKey = scopeKeyOf(partition);
|
|
358
|
+
assertNoListPage('listSubjects', arguments[3], 'double');
|
|
338
359
|
// DIRECTOS del relation EXACTO (invariante 7): ni transitivo (eso es
|
|
339
360
|
// `membersOf`) ni derivado. `usersetSubjects` decide si los usersets
|
|
340
361
|
// (`group:g#member`) salen o solo los holders.
|
|
@@ -422,9 +443,11 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
422
443
|
assertRelationDeclared(config, object, relation);
|
|
423
444
|
return denyPhaseOf(config, object.type) ?? null;
|
|
424
445
|
};
|
|
425
|
-
driver.listDenies = async (relation, object, partition)
|
|
446
|
+
driver.listDenies = async function (relation, object, partition) {
|
|
426
447
|
assertRelationDeclared(config, object, relation);
|
|
427
|
-
const
|
|
448
|
+
const key = scopeKeyOf(partition);
|
|
449
|
+
assertNoListPage('listDenies', arguments[3], 'double');
|
|
450
|
+
const direct = directSubjects(key, object.type, object.id, deniedRelationOf(relation));
|
|
428
451
|
return { subjects: dedupeSubjects(direct.map((t) => t.subject)) };
|
|
429
452
|
};
|
|
430
453
|
driver.decideMany = async (subject, relation, objects, partition) => {
|
|
@@ -444,16 +467,19 @@ function buildRelationsDouble(options, tuples, now) {
|
|
|
444
467
|
};
|
|
445
468
|
}
|
|
446
469
|
if (capabilities.membersOfNative) {
|
|
447
|
-
driver.membersOf = async (object, relation, partition)
|
|
470
|
+
driver.membersOf = async function (object, relation, partition) {
|
|
448
471
|
assertRelationDeclared(config, object, relation);
|
|
449
472
|
// Lote D: `membersOf` RESTA (corte de camino por candidato, como `database`).
|
|
450
473
|
const key = scopeKeyOf(partition);
|
|
474
|
+
assertNoListPage('membersOf', arguments[3], 'double');
|
|
451
475
|
return { subjects: collectMembers(object, relation, key).filter((s) => resolves(s, relation, object, key)) };
|
|
452
476
|
};
|
|
453
477
|
}
|
|
454
478
|
if (capabilities.enumerateRelations) {
|
|
455
|
-
driver.enumerateRelations = async (partition) => {
|
|
479
|
+
driver.enumerateRelations = async (partition, page) => {
|
|
456
480
|
const partitionKey = scopeKeyOf(partition);
|
|
481
|
+
// 7.4: la misma forma que los drivers reales; el doble no emite cursor (todo en una página) ⇒ after es 500.
|
|
482
|
+
assertEnumeratePage('enumerateRelations', page, 'none', 'double');
|
|
457
483
|
// SIN filtrar la caducada (R-15): llega con su `expiresAt` para que el
|
|
458
484
|
// destino de reconcile la cuente en `skipped`, no para que desaparezca.
|
|
459
485
|
const out = tuples
|
|
@@ -703,6 +729,46 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
703
729
|
await state.manager.relate({ object: g2, relation: 'member' }, 'viewer', doc, p);
|
|
704
730
|
assert.isTrue(await state.manager.check(u, 'viewer', doc, p));
|
|
705
731
|
});
|
|
732
|
+
/* ── R1 (lote «antes de RC»): el techo de anidación de grupos, si el driver lo declara ── */
|
|
733
|
+
test('R1 · groupDepthLimit (si el driver lo declara): con maxGroupDepth 3 + database.enforceGroupDepth el techo es ≤ 3 y se APLICA — la arista que deja una cadena más larga, por ARRIBA o por ABAJO, o que cierra un ciclo ⇒ 422 E_AUTHZ_GROUP_DEPTH_LIMIT sin escribir (check sigue F) y sin evento por el manager; la membresía de un holder y una relación de dominio no son anidación', async ({ assert, }) => {
|
|
734
|
+
const config = contractGroupDepthRelationsConfig(3);
|
|
735
|
+
const driver = await harness.makeDriver(config);
|
|
736
|
+
// `groupDepthLimit` es OPCIONAL en el puerto: un driver sin él no declara techo (el plan del reconcile no tiene
|
|
737
|
+
// nada que negar) y no hay gate que juzgar. Lo mismo un driver que responde `null` (sin techo con esta config).
|
|
738
|
+
const limit = typeof driver.groupDepthLimit === 'function' ? await driver.groupDepthLimit() : null;
|
|
739
|
+
if (limit === null)
|
|
740
|
+
return;
|
|
741
|
+
assert.isTrue(Number.isInteger(limit) && limit >= 1 && limit <= 3, `el techo con maxGroupDepth 3 tiene que estar en 1..3 (llegó ${String(limit)})`);
|
|
742
|
+
const events = [];
|
|
743
|
+
const manager = new RelationsManager(driver, config, { driverName: harness.name, onRelationWrite: (e) => void events.push(e) });
|
|
744
|
+
const p = partition();
|
|
745
|
+
const g = Array.from({ length: limit + 2 }, () => ({ type: 'group', id: docId() }));
|
|
746
|
+
const member = (o) => ({ object: o, relation: 'member' });
|
|
747
|
+
for (let i = 0; i < limit; i++)
|
|
748
|
+
await manager.relate(member(g[i]), 'member', g[i + 1], p);
|
|
749
|
+
const u = holder();
|
|
750
|
+
await manager.relate(u, 'member', g[0], p);
|
|
751
|
+
assert.isTrue(await manager.check(u, 'member', g[limit], p), `una cadena de ${limit} saltos (el techo) entra y resuelve`);
|
|
752
|
+
const before = events.length;
|
|
753
|
+
const refused = async (label, run) => {
|
|
754
|
+
const error = await run().then(() => null, (e) => e);
|
|
755
|
+
assert.equal(error?.status, 422, `${label}: ${error?.message ?? 'entró'}`);
|
|
756
|
+
assert.equal(error?.code, 'E_AUTHZ_GROUP_DEPTH_LIMIT', label);
|
|
757
|
+
return error;
|
|
758
|
+
};
|
|
759
|
+
await refused('por ARRIBA (la cima de la cadena dentro de otro grupo)', () => manager.relate(member(g[limit]), 'member', g[limit + 1], p));
|
|
760
|
+
assert.isFalse(await manager.check(u, 'member', g[limit + 1], p), 'la arista rechazada no se escribió');
|
|
761
|
+
const x = { type: 'group', id: docId() };
|
|
762
|
+
await refused('por ABAJO (otro grupo dentro de la base de la cadena)', () => manager.relate(member(x), 'member', g[0], p));
|
|
763
|
+
const v = holder();
|
|
764
|
+
await manager.relate(v, 'member', x, p);
|
|
765
|
+
assert.isFalse(await manager.check(v, 'member', g[0], p), 'x ⊂ g0 no se escribió');
|
|
766
|
+
await refused('el CICLO', () => manager.relate(member(g[limit]), 'member', g[0], p));
|
|
767
|
+
assert.equal(events.length, before + 1, '422 ⇒ esa escritura no ocurrió y no hay evento (invariante 13): solo el del holder v');
|
|
768
|
+
// No es anidación: la membresía de un holder y una relación de dominio sobre la cima.
|
|
769
|
+
await manager.relate(holder(), 'member', g[limit], p);
|
|
770
|
+
await manager.relate(member(g[limit]), 'viewer', { type: 'document', id: docId() }, p);
|
|
771
|
+
});
|
|
706
772
|
/* ── R-05: la partición aísla ── */
|
|
707
773
|
test('R-05 · partición: relate en A no concede en B', async ({ assert }) => {
|
|
708
774
|
const u = holder();
|
|
@@ -750,6 +816,93 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
750
816
|
const page = await state.manager.listObjects(u, 'viewer', 'document', p);
|
|
751
817
|
assert.sameMembers(page.objects.map((o) => o.id).sort(), [d1.id, d2.id].sort());
|
|
752
818
|
});
|
|
819
|
+
/* ── 7.4 (lote «antes de RC», opción 4 del juez): los listados NO paginan ── */
|
|
820
|
+
/**
|
|
821
|
+
* **P13 · «sin página ⇒ lista completa y sin cursor»** (injerto 1 del juez; auditor R1-2, tester P13). N = 150:
|
|
822
|
+
* por encima del default de 100 de `enumerate*` de `database` y del `page_size` 100 de `Read` en OpenFGA, así que
|
|
823
|
+
* un driver que pagina por dentro y devuelve la PRIMERA página (o un cursor que nadie lee) es rojo aquí. El tipo ya
|
|
824
|
+
* no tiene `cursor` (protege a quien compila); este caso protege en EJECUCIÓN frente a un driver de terceros y a un
|
|
825
|
+
* default de `limit` que alguien meta en 2.6. `listObjects` puede venir `truncated` SOLO si el driver declara
|
|
826
|
+
* `listObjectsTruncation` (y entonces lo que trae ⊆ lo escrito); los demás, el conjunto exacto.
|
|
827
|
+
*/
|
|
828
|
+
test('7.4 · P13 · sin page los listados son EXHAUSTIVOS y sin cursor: N=150 (> 100 de enumerate y del page_size de Read) en listSubjects y listObjects —y listDenies con relationDenies, membersOf con membersOfNative—; listObjects truncated solo con listObjectsTruncation', async ({ assert, }) => {
|
|
829
|
+
const N = 150;
|
|
830
|
+
const p = partition();
|
|
831
|
+
const users = Array.from({ length: N }, () => holder());
|
|
832
|
+
const ids = (subjects) => subjects.map((s) => s.uuid).sort();
|
|
833
|
+
const expected = users.map((u) => u.uuid).sort();
|
|
834
|
+
const doc = { type: 'document', id: docId() };
|
|
835
|
+
for (const u of users)
|
|
836
|
+
await state.manager.relate(u, 'viewer', doc, p);
|
|
837
|
+
const subjects = await state.manager.listSubjects('viewer', doc, p);
|
|
838
|
+
assert.notProperty(subjects, 'cursor', 'listSubjects sin page no devuelve cursor');
|
|
839
|
+
assert.notEqual(subjects.truncated, true, 'listSubjects no corta (Read se agota o lanza)');
|
|
840
|
+
assert.deepEqual(ids(subjects.subjects), expected, `listSubjects devuelve los ${N}, no una página`);
|
|
841
|
+
const reader = holder();
|
|
842
|
+
const docs = Array.from({ length: N }, () => docId());
|
|
843
|
+
for (const id of docs)
|
|
844
|
+
await state.manager.relate(reader, 'viewer', { type: 'document', id }, p);
|
|
845
|
+
const objects = await state.manager.listObjects(reader, 'viewer', 'document', p);
|
|
846
|
+
assert.notProperty(objects, 'cursor', 'listObjects sin page no devuelve cursor');
|
|
847
|
+
const got = objects.objects.map((o) => o.id).sort();
|
|
848
|
+
if (objects.truncated === true) {
|
|
849
|
+
assert.isTrue(harness.capabilities.listObjectsTruncation, 'truncated sin declarar listObjectsTruncation');
|
|
850
|
+
assert.includeMembers(docs, got, 'lo que trae una lista truncada es de lo escrito');
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
assert.deepEqual(got, [...docs].sort(), `listObjects devuelve los ${N}, no una página`);
|
|
854
|
+
}
|
|
855
|
+
if (harness.capabilities.membersOfNative) {
|
|
856
|
+
const group = { type: 'group', id: docId() };
|
|
857
|
+
for (const u of users)
|
|
858
|
+
await state.manager.relate(u, 'member', group, p);
|
|
859
|
+
const members = await state.manager.membersOf(group, 'member', p);
|
|
860
|
+
assert.notProperty(members, 'cursor', 'membersOf sin page no devuelve cursor');
|
|
861
|
+
assert.deepEqual(ids(members.subjects), expected, `membersOf devuelve los ${N}`);
|
|
862
|
+
}
|
|
863
|
+
if (harness.capabilities.relationDenies) {
|
|
864
|
+
const { manager } = await denyManager();
|
|
865
|
+
const denied = { type: 'document', id: docId() };
|
|
866
|
+
for (const u of users)
|
|
867
|
+
await manager.deny(u, 'viewer', denied, p);
|
|
868
|
+
const denies = await manager.listDenies('viewer', denied, p);
|
|
869
|
+
assert.notProperty(denies, 'cursor', 'listDenies sin page no devuelve cursor');
|
|
870
|
+
assert.deepEqual(ids(denies.subjects), expected, `listDenies devuelve los ${N}`);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
/**
|
|
874
|
+
* **RC74-1 · página en los cuatro listados ⇒ 500, cero llamadas** (capa MANAGER, sobre el driver del harness). En
|
|
875
|
+
* 2.6 este caso pasa a ser la cara `whenFalse` de cada capacidad de paginación. `undefined` EXPLÍCITO entra
|
|
876
|
+
* (RC74-3). No se exige al driver en directo: obligaría a un tercero a un 500 por un parámetro que ya no está en
|
|
877
|
+
* el tipo (lo cubren la guardia y los specs de los drivers del paquete).
|
|
878
|
+
*/
|
|
879
|
+
test('7.4 · RC74-1 · listObjects/listSubjects/listDenies/membersOf con page ({ limit: 1 }, null, {}) ⇒ 500 E_AUTHZ_UNSUPPORTED nombrando la operación, con CERO llamadas al driver; undefined explícito entra (RC74-3)', async ({ assert, }) => {
|
|
880
|
+
const u = holder();
|
|
881
|
+
const doc = { type: 'document', id: docId() };
|
|
882
|
+
const group = { type: 'group', id: docId() };
|
|
883
|
+
const p = partition();
|
|
884
|
+
const m = state.manager;
|
|
885
|
+
const calls = [
|
|
886
|
+
['listObjects', (page) => m.listObjects(u, 'viewer', 'document', p, page)],
|
|
887
|
+
['listSubjects', (page) => m.listSubjects('viewer', doc, p, page)],
|
|
888
|
+
['listDenies', (page) => m.listDenies('viewer', doc, p, page)],
|
|
889
|
+
['membersOf', (page) => m.membersOf(group, 'member', p, page)],
|
|
890
|
+
];
|
|
891
|
+
for (const [operation, call] of calls) {
|
|
892
|
+
for (const page of [{ limit: 1 }, null, {}]) {
|
|
893
|
+
const before = state.calls();
|
|
894
|
+
const caught = await call(page).then(() => null, (e) => e);
|
|
895
|
+
assert.isNotNull(caught, `${operation}(…, ${JSON.stringify(page)}) entró: una página que no hace nada es una lista entera que parece una página`);
|
|
896
|
+
assert.equal(caught.status, 500, `${operation}: ${caught.message}`);
|
|
897
|
+
assert.equal(caught.code, 'E_AUTHZ_UNSUPPORTED', `${operation}: ${caught.message}`);
|
|
898
|
+
assert.include(caught.message, operation);
|
|
899
|
+
assert.equal(state.calls(), before, `${operation} con page: CERO llamadas al driver`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// RC74-3: el argumento `undefined` explícito es «sin página» (quien reenvía un opcional vacío no se rompe).
|
|
903
|
+
assert.isArray((await m.listObjects(u, 'viewer', 'document', p, undefined)).objects);
|
|
904
|
+
assert.isArray((await m.listSubjects('viewer', doc, p, undefined)).subjects);
|
|
905
|
+
});
|
|
753
906
|
/* ── R-12: identidad mal formada ⇒ 422 ── */
|
|
754
907
|
test('R-12 · partición mal formada ⇒ 422 con code', async ({ assert }) => {
|
|
755
908
|
const u = holder();
|
|
@@ -1783,6 +1936,21 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
1783
1936
|
for (const r of denies)
|
|
1784
1937
|
await m.deny(u, r, object, p);
|
|
1785
1938
|
}
|
|
1939
|
+
/* ── Estrategia 2.5.0 · firma 4 · el par `experimental` (regla D15; mismo nombre que en roles) ── */
|
|
1940
|
+
// Dice lo que el consumidor tiene que saber («fuera de la garantía de la versión») y no apaga casos: el runner corre
|
|
1941
|
+
// ENTERO contra un driver experimental. Se observa la declaración del driver y la copia normalizada del manager.
|
|
1942
|
+
caseFor('experimental', {
|
|
1943
|
+
whenTrue: () => test('experimental:true · el driver se declara EXPERIMENTAL (capabilities.experimental === true) y `relations.capabilities()` lo publica tal cual; el runner corre igual contra él', async ({ assert }) => {
|
|
1944
|
+
assert.strictEqual(state.base.capabilities?.experimental, true, 'el harness declara experimental: true y el driver no: declara lo observable');
|
|
1945
|
+
const published = state.manager.capabilities();
|
|
1946
|
+
assert.strictEqual(published.experimental, true);
|
|
1947
|
+
assert.isTrue(Object.isFrozen(published));
|
|
1948
|
+
}),
|
|
1949
|
+
whenFalse: () => test('experimental:false · el driver NO se declara experimental (ausente o false) y `relations.capabilities().experimental` es false, nunca undefined (D15)', async ({ assert }) => {
|
|
1950
|
+
assert.notStrictEqual(state.base.capabilities?.experimental, true, 'el harness declara experimental: false y el driver declara true: declara lo observable');
|
|
1951
|
+
assert.strictEqual(state.manager.capabilities().experimental, false);
|
|
1952
|
+
}),
|
|
1953
|
+
});
|
|
1786
1954
|
/* ── Cierre 2 de D2 · 🟠 N1 · el par `atomicRenewal` (renovar una membresía que TRANSPORTA un deny) ── */
|
|
1787
1955
|
/**
|
|
1788
1956
|
* Siembra: U ∈ g con caducidad (+1 h), un allow DIRECTO de U sobre X y —si el driver deniega— el deny de `g#member`
|
|
@@ -2361,4 +2529,3 @@ export function registerRelationsDriverContract(harness, api) {
|
|
|
2361
2529
|
});
|
|
2362
2530
|
return { registered, covered };
|
|
2363
2531
|
}
|
|
2364
|
-
//# sourceMappingURL=relations_contract.js.map
|
|
@@ -12,4 +12,3 @@ export interface TestClock {
|
|
|
12
12
|
}
|
|
13
13
|
/** Un reloj de test parado en `start` (default: la hora de pared al crearlo), controlable con `set`/`advance`. */
|
|
14
14
|
export declare function createTestClock(start?: Date | number | string): TestClock;
|
|
15
|
-
//# sourceMappingURL=test_clock.d.ts.map
|
|
@@ -42,4 +42,3 @@ export type WithAuthzScopes<T extends Constructor> = T & AuthzScopesStatics;
|
|
|
42
42
|
*/
|
|
43
43
|
export declare function withAuthzScopes<T extends Constructor | AuthzScopesOptions>(arg: T): T extends Constructor ? WithAuthzScopes<T> : <U extends Constructor>(Base: U) => WithAuthzScopes<U>;
|
|
44
44
|
export {};
|
|
45
|
-
//# sourceMappingURL=authz_scopes.d.ts.map
|
|
@@ -81,6 +81,11 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
|
|
|
81
81
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelPaginatorContract<InstanceType<Model_1>>, "paginate">): void;
|
|
82
82
|
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event>): void;
|
|
83
83
|
};
|
|
84
|
+
before: {
|
|
85
|
+
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_1 extends "find" | "fetch">(this: Model_1, event: Event_1, handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, Event_1>): void;
|
|
86
|
+
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<[import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>], "paginate">): void;
|
|
87
|
+
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
|
|
88
|
+
};
|
|
84
89
|
readonly booted: boolean;
|
|
85
90
|
$columnsDefinitions: Map<string, import("@adonisjs/lucid/types/model").ModelColumnOptions>;
|
|
86
91
|
$relationsDefinitions: Map<string, import("@adonisjs/lucid/types/relations").RelationshipsContract>;
|
|
@@ -117,11 +122,6 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
|
|
|
117
122
|
};
|
|
118
123
|
$defineProperty: <Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Prop extends keyof Model_1>(this: Model_1, propertyName: Prop, defaultValue: Model_1[Prop], strategy: "inherit" | "define" | ((value: Model_1[Prop]) => Model_1[Prop])) => void;
|
|
119
124
|
boot: () => void;
|
|
120
|
-
before: {
|
|
121
|
-
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_1 extends "find" | "fetch">(this: Model_1, event: Event_1, handler: import("@adonisjs/lucid/types/model").HooksHandler<import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, Event_1>): void;
|
|
122
|
-
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel>(this: Model_1, event: "paginate", handler: import("@adonisjs/lucid/types/model").HooksHandler<[import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>, import("@adonisjs/lucid/types/model").ModelQueryBuilderContract<Model_1>], "paginate">): void;
|
|
123
|
-
<Model_1 extends import("@adonisjs/lucid/types/model").LucidModel, Event_2 extends import("@adonisjs/lucid/types/model").EventsList>(this: Model_1, event: Event_2, handler: import("@adonisjs/lucid/types/model").HooksHandler<InstanceType<Model_1>, Event_2>): void;
|
|
124
|
-
};
|
|
125
125
|
create: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
126
126
|
createQuietly: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>, options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>>;
|
|
127
127
|
createMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, values: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
@@ -148,4 +148,3 @@ export declare const hasUuid: <Model extends NormalizeConstructor<typeof BaseMod
|
|
|
148
148
|
fetchOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
149
149
|
updateOrCreateMany: <T extends import("@adonisjs/lucid/types/model").LucidModel>(this: T, predicate: keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>> | (keyof import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>)[], payload: Partial<import("@adonisjs/lucid/types/model").ModelAttributes<InstanceType<T>>>[], options?: import("@adonisjs/lucid/types/model").ModelAssignOptions) => Promise<InstanceType<T>[]>;
|
|
150
150
|
} & Model;
|
|
151
|
-
//# sourceMappingURL=has_uuid.d.ts.map
|
package/build/src/types.d.ts
CHANGED
|
@@ -334,6 +334,15 @@ export interface AuthorizationDriverCapabilities {
|
|
|
334
334
|
* `transactional: true`.
|
|
335
335
|
*/
|
|
336
336
|
transactionalWrites: boolean;
|
|
337
|
+
/**
|
|
338
|
+
* **El driver es EXPERIMENTAL: fuera de la garantía de la versión** (estrategia 2.5.0, firma 4 del dueño 2026-10-21).
|
|
339
|
+
* **Opcional y normalizada a `false`** (regla D15). `openfga` = `true` en 2.5.x: no apto para producción, no se
|
|
340
|
+
* construye sin `experimental: 'openfga@2.5'` (500 `E_AUTHZ_CONFIG` sin él) y sus límites declarados —con cifra,
|
|
341
|
+
* receta, detector y versión de cierre (2.6)— están en README «Limits». `database` = `false` (no la declara). El juez
|
|
342
|
+
* corre ENTERO contra un driver experimental (no hay skip): la capacidad dice lo que el consumidor tiene que saber, no
|
|
343
|
+
* apaga casos. Par de caras en `runAuthorizationDriverContract`.
|
|
344
|
+
*/
|
|
345
|
+
experimental?: boolean;
|
|
337
346
|
}
|
|
338
347
|
/**
|
|
339
348
|
* Las claves de `AuthorizationDriverCapabilities`, LITERALES (2.4.0-alpha.4):
|
|
@@ -730,6 +739,14 @@ export interface ReconcileFact {
|
|
|
730
739
|
* que es justo lo que la migración no puede hacer.
|
|
731
740
|
*/
|
|
732
741
|
expiresAt?: Date | null;
|
|
742
|
+
/**
|
|
743
|
+
* `deny` (cierre-2 de L11 · C1): el origen guarda ese deny con una condición que el paquete no escribe ni sabe leer
|
|
744
|
+
* (escritor AJENO; el motivo, p. ej. `foreign: condición 'inverted'`). NO es un deny limpio: el destino lo migra como
|
|
745
|
+
* deny —fail-closed: ante la duda, niega— pero lo DECLARA (`invalid-condition`, deriva); y en la pasada de
|
|
746
|
+
* MANTENIMIENTO (origen = destino) no se escribe ni se borra, solo se reporta. Un destino que ignore la marca lo trata
|
|
747
|
+
* como deny permanente, que es la lectura conservadora para un deny.
|
|
748
|
+
*/
|
|
749
|
+
invalidCondition?: string;
|
|
733
750
|
/** Cómo lo nombra el origen (para `details`): un motivo sin la fila no se arregla. */
|
|
734
751
|
detail: string;
|
|
735
752
|
}
|
|
@@ -1151,6 +1168,18 @@ export interface ScopeRelayFailure {
|
|
|
1151
1168
|
change: ScopeTreeChange;
|
|
1152
1169
|
error: string;
|
|
1153
1170
|
transient: boolean;
|
|
1171
|
+
/**
|
|
1172
|
+
* Cierre de R3 (⚪ R3-A7; punto 7): el `code` del error (`E_AUTHZ_WRITE_CONFLICT`, `E_AUTHZ_RESOLUTION_LIMIT`,
|
|
1173
|
+
* `E_AUTHZ_BACKEND_UNAVAILABLE`…), para que el comando diga QUÉ rama transitoria corrió: una carrera o un grafo
|
|
1174
|
+
* demasiado profundo no son «el backend no respondió». Ausente si el error no trae `code` (driver de terceros).
|
|
1175
|
+
*/
|
|
1176
|
+
code?: string;
|
|
1177
|
+
/**
|
|
1178
|
+
* Cierre-2 de L11 (⚪ C2): `true` ⇒ el fallo es del DESPLIEGUE (el modelo fijado no es el que genera la config, o el
|
|
1179
|
+
* generador no puede generar con él; `isDeploymentFailure`), no del backend ni de la entrada. Es `transient` también
|
|
1180
|
+
* (no consume intento), pero el backend SÍ respondió: el comando lo dice en su propia línea. Ausente = no lo es.
|
|
1181
|
+
*/
|
|
1182
|
+
deployment?: true;
|
|
1154
1183
|
}
|
|
1155
1184
|
/** Qué fue de una entrada en la pasada (2.4.0-alpha.5 · R1, `ScopeRelayReport.entries`). */
|
|
1156
1185
|
export type ScopeRelayEntryStatus = 'applied' | 'deferred' | 'failed' | 'dead';
|
|
@@ -1179,6 +1208,13 @@ export interface ScopeRelayEntry {
|
|
|
1179
1208
|
status: ScopeRelayEntryStatus;
|
|
1180
1209
|
reason?: string;
|
|
1181
1210
|
attempts?: number;
|
|
1211
|
+
/**
|
|
1212
|
+
* Solo en `applied` de un `attached`/`moved` relevado contra un driver con `hierarchyFacts` (asentar · cierre 1): el
|
|
1213
|
+
* padre que se APLICÓ de verdad —el FRESCO del consumidor, que puede no ser `change.parent` (`reason:
|
|
1214
|
+
* 'consumer-parent'`)— o `null` si no se escribió arista (`reason: 'superseded'`: el nodo ya no existe y un `detached`
|
|
1215
|
+
* suyo lo respalda). Ausente en `database`, que aplica el encolado tal cual.
|
|
1216
|
+
*/
|
|
1217
|
+
appliedParent?: ScopeRef | null;
|
|
1182
1218
|
/**
|
|
1183
1219
|
* Solo en `deferred` (2.4.0-alpha.8): el ORIGEN —transitivo, la causa
|
|
1184
1220
|
* raíz— del bloqueo que la aplazó. `parked` = una entrada APARCADA (la
|
|
@@ -1963,7 +1999,12 @@ export interface RelationDenyOptions extends RelationTransactionOptions {
|
|
|
1963
1999
|
}
|
|
1964
2000
|
/** Una decisión de `decideMany` (D9): `allow` (concede aquí), `deny` (no heredes más allá de este objeto), `none` (nada aquí). */
|
|
1965
2001
|
export type RelationDecision = 'allow' | 'deny' | 'none';
|
|
1966
|
-
/**
|
|
2002
|
+
/**
|
|
2003
|
+
* Una página de `enumerateRelations`/`enumerateRelationsMany` (cursor opaco que AVANZA, no filtra herencia). **Solo
|
|
2004
|
+
* `enumerate*`** desde el lote «antes de RC» · 7.4: los `list*` no paginan en 2.5 (ver `RelationObjectsPage`).
|
|
2005
|
+
* `limit` entero ≥ 1 (si no, 422 `E_AUTHZ_INVALID_PAGE`; por encima de `RELATIONS_ENUMERATE_MAX_LIMIT` se recorta);
|
|
2006
|
+
* `after` = el `cursor` que devolvió la página anterior, sin tocar.
|
|
2007
|
+
*/
|
|
1967
2008
|
export interface RelationPage {
|
|
1968
2009
|
limit?: number;
|
|
1969
2010
|
after?: string;
|
|
@@ -2054,24 +2095,31 @@ export interface RelationsDriverCapabilities {
|
|
|
2054
2095
|
* `runRelationsDriverContract`.
|
|
2055
2096
|
*/
|
|
2056
2097
|
atomicRenewal?: boolean;
|
|
2098
|
+
/**
|
|
2099
|
+
* **El driver es EXPERIMENTAL: fuera de la garantía de la versión** (estrategia 2.5.0, firma 4). Mismo nombre y misma
|
|
2100
|
+
* regla que en `AuthorizationDriverCapabilities` (D15: opcional, normalizada a `false`). `openfga` = `true` en 2.5.x
|
|
2101
|
+
* (500 `E_AUTHZ_CONFIG` al construir sin `experimental: 'openfga@2.5'`; límites en README «Limits», cierre en 2.6);
|
|
2102
|
+
* `database` = `false`. Par de caras en `runRelationsDriverContract`.
|
|
2103
|
+
*/
|
|
2104
|
+
experimental?: boolean;
|
|
2057
2105
|
}
|
|
2058
2106
|
/** Las claves de `RelationsDriverCapabilities`, literales: lo que `relations.capabilities()` normaliza (2.4.0-alpha.4). */
|
|
2059
2107
|
export declare const RELATIONS_DRIVER_CAPABILITY_KEYS: ReadonlyArray<keyof RelationsDriverCapabilities>;
|
|
2060
2108
|
/** La copia congelada y normalizada de `relations.capabilities()`; misma regla que `normalizeAuthorizationCapabilities`. */
|
|
2061
2109
|
export declare function normalizeRelationsCapabilities(declared: Partial<RelationsDriverCapabilities> | undefined): Readonly<RelationsDriverCapabilities>;
|
|
2062
2110
|
/**
|
|
2063
|
-
*
|
|
2064
|
-
*
|
|
2065
|
-
* consumidor que lo ve sabe que hay MÁS y no toma la
|
|
2111
|
+
* La respuesta de `listObjects`. **Sin `cursor`** (lote «antes de RC» · 7.4, opción 4 del juez): los `list*` de
|
|
2112
|
+
* relaciones devuelven el conjunto COMPLETO o señalan `truncated`; nunca un cursor. `truncated` dice si el backend
|
|
2113
|
+
* cortó (tope o deadline; solo con `listObjectsTruncation`): un consumidor que lo ve sabe que hay MÁS y no toma la
|
|
2114
|
+
* lista por completa. La paginación de los listados llegará en 2.6 como métodos APARTE (`*Page`) con capacidad propia.
|
|
2066
2115
|
*/
|
|
2067
2116
|
export interface RelationObjectsPage {
|
|
2068
2117
|
objects: RelObject[];
|
|
2069
|
-
cursor?: string;
|
|
2070
2118
|
truncated?: boolean;
|
|
2071
2119
|
}
|
|
2120
|
+
/** La respuesta de `listSubjects`/`listDenies`/`membersOf`: completa o `truncated`, **sin `cursor`** (7.4). */
|
|
2072
2121
|
export interface RelationSubjectsPage {
|
|
2073
2122
|
subjects: RelSubject[];
|
|
2074
|
-
cursor?: string;
|
|
2075
2123
|
truncated?: boolean;
|
|
2076
2124
|
}
|
|
2077
2125
|
/** Una tupla de relación tal como la enumera `enumerateRelations` (origen de reconcile). */
|
|
@@ -2096,6 +2144,19 @@ export interface RelationTuple {
|
|
|
2096
2144
|
* o se contaría como relación no declarada, y perder un deny no es una pérdida que se pueda declarar.
|
|
2097
2145
|
*/
|
|
2098
2146
|
kind?: 'allow' | 'deny';
|
|
2147
|
+
/**
|
|
2148
|
+
* **La condición de la tupla escrita no es la `not_expired` del paquete, o no se puede leer** (lote «antes de RC» ·
|
|
2149
|
+
* L11): el porqué, legible. Solo lo pone un driver que lee condiciones (`openfga`, `fgaTupleExpiry`): `foreign` (otra
|
|
2150
|
+
* condición o un contexto que no es exactamente `{ valid_until }`) o `unparseable`. Con esto puesto `expiresAt` es la
|
|
2151
|
+
* ÉPOCA (`new Date(0)`) y no es una caducidad real. **La época NO es conservadora para todo** (cierre-2 de L11 · ⚪ C3):
|
|
2152
|
+
* un lector que ignore este campo la lee VENCIDA, que es fail-CLOSED para un ALLOW (deja de conceder) pero fail-OPEN
|
|
2153
|
+
* para un DENY (`kind: 'deny'`) y para una membresía de `group` que TRANSPORTA un deny (deja de negar). Ningún valor de
|
|
2154
|
+
* `expiresAt` es seguro para los dos tipos de tupla (con `null` era al revés): quien lea `enumerateRelations` TIENE que
|
|
2155
|
+
* mirar `invalidCondition` antes que la fecha. Dentro del paquete se hace: `reconcileRelations` la clasifica en el PLAN, antes de mirar la fecha, como `invalid-condition` (origen:
|
|
2156
|
+
* no se migra; destino: presente e intocable, nunca se poda) y es deriva. Opcional (un driver de terceros anterior
|
|
2157
|
+
* sigue compilando; ausente = la condición se entendió).
|
|
2158
|
+
*/
|
|
2159
|
+
invalidCondition?: string;
|
|
2099
2160
|
}
|
|
2100
2161
|
export interface RelationTuplePage {
|
|
2101
2162
|
tuples: RelationTuple[];
|
|
@@ -2160,10 +2221,14 @@ export interface RelationsDriver {
|
|
|
2160
2221
|
* llamante (eso es `decideMany`, lote D).
|
|
2161
2222
|
*/
|
|
2162
2223
|
checkMany?(subject: RelSubject, relation: string, objects: RelObject[], partition: ScopeRef): Promise<boolean[]>;
|
|
2163
|
-
/**
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2224
|
+
/**
|
|
2225
|
+
* Los objetos de tipo `objectType` sobre los que `subject` tiene `relation`. Directos + derivados, sin herencia
|
|
2226
|
+
* abierta. **Sin página** (7.4): el conjunto completo o `truncated`; un quinto argumento distinto de `undefined` ⇒
|
|
2227
|
+
* 500 `E_AUTHZ_UNSUPPORTED` sin tocar el backend (lo mismo en `listSubjects`/`listDenies`/`membersOf`).
|
|
2228
|
+
*/
|
|
2229
|
+
listObjects(subject: RelSubject, relation: string, objectType: string, partition: ScopeRef): Promise<RelationObjectsPage>;
|
|
2230
|
+
/** Los sujetos DIRECTOS de `relation` sobre `object` (holders y usersets). Nunca la membresía transitiva (eso es `membersOf`). Sin página (7.4). */
|
|
2231
|
+
listSubjects(relation: string, object: RelObject, partition: ScopeRef): Promise<RelationSubjectsPage>;
|
|
2167
2232
|
/** Borra todas las tuplas cuyo OBJETO es `object` (F-05 sobre el TIPO, alpha.3) y demuestra cero, o lanza 500 `E_AUTHZ_PURGE_INCOMPLETE` (invariante 11). Si falla tras borrar parte, marcado con `markPartialWrite`. */
|
|
2168
2233
|
purgeObject(object: RelObject, partition: ScopeRef, options?: RelationTransactionOptions): Promise<void>;
|
|
2169
2234
|
/** Borra todas las tuplas cuyo SUJETO es `subject` (F-05 sobre el userset, alpha.3) y demuestra cero, o lanza 500. Si falla tras borrar parte, marcado con `markPartialWrite`. */
|
|
@@ -2174,7 +2239,7 @@ export interface RelationsDriver {
|
|
|
2174
2239
|
* `listSubjects(member, group)`, que devuelve solo los hechos DIRECTOS. Solo
|
|
2175
2240
|
* lo trae el driver con `membersOfNative: true`.
|
|
2176
2241
|
*/
|
|
2177
|
-
membersOf?(object: RelObject, relation: string, partition: ScopeRef
|
|
2242
|
+
membersOf?(object: RelObject, relation: string, partition: ScopeRef): Promise<RelationSubjectsPage>;
|
|
2178
2243
|
/** ORIGEN de `authz:reconcile` de relaciones: las tuplas paginadas, sin filtrar (la caducada LLEGA con su `expiresAt`). Solo con `enumerateRelations: true`. */
|
|
2179
2244
|
enumerateRelations?(partition: ScopeRef, page?: RelationPage): Promise<RelationTuplePage>;
|
|
2180
2245
|
/**
|
|
@@ -2200,8 +2265,8 @@ export interface RelationsDriver {
|
|
|
2200
2265
|
deny?(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationDenyOptions): Promise<void>;
|
|
2201
2266
|
/** Retira el deny. No-op seguro si no existe. Sobre CUALQUIER tipo declarado (D8: retirar el opt-in es reversible). */
|
|
2202
2267
|
removeDeny?(subject: RelSubject, relation: string, object: RelObject, partition: ScopeRef, options?: RelationDenyOptions): Promise<void>;
|
|
2203
|
-
/** Los sujetos con deny DIRECTO de `relation` sobre `object` (hechos, como `listSubjects`; no resta nada). */
|
|
2204
|
-
listDenies?(relation: string, object: RelObject, partition: ScopeRef
|
|
2268
|
+
/** Los sujetos con deny DIRECTO de `relation` sobre `object` (hechos, como `listSubjects`; no resta nada). Sin página (7.4). */
|
|
2269
|
+
listDenies?(relation: string, object: RelObject, partition: ScopeRef): Promise<RelationSubjectsPage>;
|
|
2205
2270
|
/**
|
|
2206
2271
|
* **La primitiva de CADENA** (D9): una decisión por objeto, POR POSICIÓN — `allow` si `check` concede; si no, `deny`
|
|
2207
2272
|
* si el sujeto tiene un deny vivo (directo o por grupo) sobre alguna relación de `up*(down*(R))` en el objeto; si
|
|
@@ -2222,6 +2287,17 @@ export interface RelationsDriver {
|
|
|
2222
2287
|
denyPhase?(object: {
|
|
2223
2288
|
type: string;
|
|
2224
2289
|
}, relation: string): Promise<'read' | 'write' | null>;
|
|
2290
|
+
/**
|
|
2291
|
+
* **El techo de anidación de grupos que ESTE driver aplica** (lote «antes de RC» · R1, decisión del dueño 2026-10-17
|
|
2292
|
+
* (5): «cada driver declara su techo»): saltos `group⊂group` del camino más largo que admite al escribir una arista
|
|
2293
|
+
* `group#member`, o `null` si no aplica ninguno. `openfga`: el CALIBRADO contra el servidor (el mínimo de un miembro y
|
|
2294
|
+
* de un no-miembro con membresía; solo lectura, tuplas contextuales, memoriza solo el éxito) o el `maxGroupDepth` de
|
|
2295
|
+
* la config si es menor; `database`: `maxGroupDepth` con `database.enforceGroupDepth`, si no `null`. Lo llama
|
|
2296
|
+
* `reconcileRelations` sobre el DESTINO en la fase de PLAN (también en `--dry-run`: es de solo lectura) para NEGAR la
|
|
2297
|
+
* partición cuya cadena final lo excede, nunca saltar la arista. Opcional: un driver de terceros sin él no tiene techo
|
|
2298
|
+
* para el plan. Lectura de configuración: no recibe tupla ni partición.
|
|
2299
|
+
*/
|
|
2300
|
+
groupDepthLimit?(): Promise<number | null>;
|
|
2225
2301
|
/**
|
|
2226
2302
|
* Vista de este driver con OTRO reloj de pared (R-15, paridad con
|
|
2227
2303
|
* `AuthorizationDriver.withClock`, 2.5 · J1): mismo backend, solo cambia el
|
|
@@ -2242,4 +2318,3 @@ export interface RelationsDriver {
|
|
|
2242
2318
|
* factory (como el de roles), así que el comando nunca toca el SDK (pureza).
|
|
2243
2319
|
*/
|
|
2244
2320
|
export type RelationsDriverFactory = () => RelationsDriver | Promise<RelationsDriver>;
|
|
2245
|
-
//# sourceMappingURL=types.d.ts.map
|
package/build/src/types.js
CHANGED
|
@@ -57,6 +57,8 @@ export const AUTHORIZATION_DRIVER_CAPABILITY_KEYS = Object.freeze([
|
|
|
57
57
|
'canonicalScopeReads',
|
|
58
58
|
'enumerateFacts',
|
|
59
59
|
'transactionalWrites',
|
|
60
|
+
// Estrategia 2.5.0 · firma 4 (D15): opcional, normalizada a `false`.
|
|
61
|
+
'experimental',
|
|
60
62
|
]);
|
|
61
63
|
/**
|
|
62
64
|
* **La copia CONGELADA y NORMALIZADA que publica `authorization.capabilities()`**
|
|
@@ -100,6 +102,8 @@ export const RELATIONS_DRIVER_CAPABILITY_KEYS = Object.freeze([
|
|
|
100
102
|
'transactionalWrites',
|
|
101
103
|
'relationDenies',
|
|
102
104
|
'atomicRenewal',
|
|
105
|
+
// Estrategia 2.5.0 · firma 4 (D15): opcional, normalizada a `false`.
|
|
106
|
+
'experimental',
|
|
103
107
|
]);
|
|
104
108
|
/** La copia congelada y normalizada de `relations.capabilities()`; misma regla que `normalizeAuthorizationCapabilities`. */
|
|
105
109
|
export function normalizeRelationsCapabilities(declared) {
|
|
@@ -108,4 +112,3 @@ export function normalizeRelationsCapabilities(declared) {
|
|
|
108
112
|
out[key] = declared?.[key] === true;
|
|
109
113
|
return Object.freeze(out);
|
|
110
114
|
}
|
|
111
|
-
//# sourceMappingURL=types.js.map
|