@stigmer/server 3.27.1 → 3.28.0
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 +2 -0
- package/dist/authorization/authorizer.d.ts +7 -5
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +7 -5
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts +3 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +52 -2
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +9 -10
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +29 -18
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/bindings.d.ts +41 -0
- package/dist/authorization/model/bindings.d.ts.map +1 -0
- package/dist/authorization/model/bindings.js +89 -0
- package/dist/authorization/model/bindings.js.map +1 -0
- package/dist/authorization/model/data/authorization-model.json +3114 -0
- package/dist/authorization/model/default-of.d.ts +2 -2
- package/dist/authorization/model/execution-viewer.d.ts +3 -0
- package/dist/authorization/model/execution-viewer.d.ts.map +1 -0
- package/dist/authorization/model/execution-viewer.js +50 -0
- package/dist/authorization/model/execution-viewer.js.map +1 -0
- package/dist/authorization/model/index.d.ts +33 -21
- package/dist/authorization/model/index.d.ts.map +1 -1
- package/dist/authorization/model/index.js +74 -52
- package/dist/authorization/model/index.js.map +1 -1
- package/dist/authorization/model/openfga-json.d.ts +18 -0
- package/dist/authorization/model/openfga-json.d.ts.map +1 -0
- package/dist/authorization/model/openfga-json.js +266 -0
- package/dist/authorization/model/openfga-json.js.map +1 -0
- package/dist/authorization/model/rewrite.d.ts +39 -67
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +1 -117
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/strict-json.d.ts +20 -0
- package/dist/authorization/strict-json.d.ts.map +1 -0
- package/dist/authorization/strict-json.js +45 -0
- package/dist/authorization/strict-json.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +9 -7
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/list-indexes.d.ts.map +1 -1
- package/dist/boot/list-indexes.js +6 -0
- package/dist/boot/list-indexes.js.map +1 -1
- package/dist/domain/iampolicy/list-index.d.ts +2 -0
- package/dist/domain/iampolicy/list-index.d.ts.map +1 -0
- package/dist/domain/iampolicy/list-index.js +32 -0
- package/dist/domain/iampolicy/list-index.js.map +1 -0
- package/dist/domain/iampolicy/resource-store.d.ts.map +1 -1
- package/dist/domain/iampolicy/resource-store.js +24 -11
- package/dist/domain/iampolicy/resource-store.js.map +1 -1
- package/dist/domain/workflowexecution/list-index.d.ts +1 -1
- package/dist/extensions/list-read-scope.d.ts +20 -3
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +22 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/authorization/README.md +48 -39
- package/src/authorization/__tests__/authorizer.test.ts +5 -14
- package/src/authorization/__tests__/derived-tuples.test.ts +192 -17
- package/src/authorization/__tests__/evaluator.test.ts +53 -160
- package/src/authorization/__tests__/facts.test.ts +5 -11
- package/src/authorization/__tests__/list-read-scope.measure.test.ts +424 -62
- package/src/authorization/__tests__/list-read-scope.test.ts +15 -14
- package/src/authorization/__tests__/organization-directory.test.ts +2 -2
- package/src/authorization/__tests__/store-test-kit.ts +246 -0
- package/src/authorization/__tests__/store-tests.test.ts +91 -223
- package/src/authorization/__tests__/support.ts +30 -2
- package/src/authorization/__tests__/throwaway-model.ts +75 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +141 -107
- package/src/authorization/authorizer.ts +7 -5
- package/src/authorization/derived-tuples.ts +105 -16
- package/src/authorization/evaluator.ts +8 -10
- package/src/authorization/list-read-scope.ts +37 -22
- package/src/authorization/model/__tests__/openfga-json.test.ts +353 -0
- package/src/authorization/model/__tests__/registry.test.ts +102 -276
- package/src/authorization/model/bindings.ts +131 -0
- package/src/authorization/model/data/authorization-model.json +3114 -0
- package/src/authorization/model/default-of.ts +2 -2
- package/src/authorization/model/execution-viewer.ts +56 -0
- package/src/authorization/model/index.ts +81 -72
- package/src/authorization/model/openfga-json.ts +310 -0
- package/src/authorization/model/rewrite.ts +39 -188
- package/src/authorization/strict-json.ts +52 -0
- package/src/boot/__tests__/list-indexes.test.ts +4 -0
- package/src/boot/compose.ts +9 -7
- package/src/boot/list-indexes.ts +6 -0
- package/src/domain/iampolicy/__tests__/resource-store.test.ts +65 -5
- package/src/domain/iampolicy/list-index.ts +33 -0
- package/src/domain/iampolicy/resource-store.ts +24 -11
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +3 -6
- package/src/extensions/list-read-scope.ts +32 -4
- package/src/index.ts +20 -44
- package/dist/authorization/model/agent.d.ts +0 -2
- package/dist/authorization/model/agent.d.ts.map +0 -1
- package/dist/authorization/model/agent.js +0 -43
- package/dist/authorization/model/agent.js.map +0 -1
- package/dist/authorization/model/agent_channel.d.ts +0 -2
- package/dist/authorization/model/agent_channel.d.ts.map +0 -1
- package/dist/authorization/model/agent_channel.js +0 -39
- package/dist/authorization/model/agent_channel.js.map +0 -1
- package/dist/authorization/model/agent_execution.d.ts +0 -2
- package/dist/authorization/model/agent_execution.d.ts.map +0 -1
- package/dist/authorization/model/agent_execution.js +0 -25
- package/dist/authorization/model/agent_execution.js.map +0 -1
- package/dist/authorization/model/agent_instance.d.ts +0 -2
- package/dist/authorization/model/agent_instance.d.ts.map +0 -1
- package/dist/authorization/model/agent_instance.js +0 -48
- package/dist/authorization/model/agent_instance.js.map +0 -1
- package/dist/authorization/model/agent_share.d.ts +0 -2
- package/dist/authorization/model/agent_share.d.ts.map +0 -1
- package/dist/authorization/model/agent_share.js +0 -29
- package/dist/authorization/model/agent_share.js.map +0 -1
- package/dist/authorization/model/api_key.d.ts +0 -2
- package/dist/authorization/model/api_key.d.ts.map +0 -1
- package/dist/authorization/model/api_key.js +0 -27
- package/dist/authorization/model/api_key.js.map +0 -1
- package/dist/authorization/model/artifact.d.ts +0 -2
- package/dist/authorization/model/artifact.d.ts.map +0 -1
- package/dist/authorization/model/artifact.js +0 -27
- package/dist/authorization/model/artifact.js.map +0 -1
- package/dist/authorization/model/channel_app.d.ts +0 -2
- package/dist/authorization/model/channel_app.d.ts.map +0 -1
- package/dist/authorization/model/channel_app.js +0 -28
- package/dist/authorization/model/channel_app.js.map +0 -1
- package/dist/authorization/model/environment.d.ts +0 -2
- package/dist/authorization/model/environment.d.ts.map +0 -1
- package/dist/authorization/model/environment.js +0 -33
- package/dist/authorization/model/environment.js.map +0 -1
- package/dist/authorization/model/execution_context.d.ts +0 -2
- package/dist/authorization/model/execution_context.d.ts.map +0 -1
- package/dist/authorization/model/execution_context.js +0 -19
- package/dist/authorization/model/execution_context.js.map +0 -1
- package/dist/authorization/model/iam_policy.d.ts +0 -2
- package/dist/authorization/model/iam_policy.d.ts.map +0 -1
- package/dist/authorization/model/iam_policy.js +0 -31
- package/dist/authorization/model/iam_policy.js.map +0 -1
- package/dist/authorization/model/identity_account.d.ts +0 -2
- package/dist/authorization/model/identity_account.d.ts.map +0 -1
- package/dist/authorization/model/identity_account.js +0 -25
- package/dist/authorization/model/identity_account.js.map +0 -1
- package/dist/authorization/model/mcp_server.d.ts +0 -2
- package/dist/authorization/model/mcp_server.d.ts.map +0 -1
- package/dist/authorization/model/mcp_server.js +0 -38
- package/dist/authorization/model/mcp_server.js.map +0 -1
- package/dist/authorization/model/memory.d.ts +0 -2
- package/dist/authorization/model/memory.d.ts.map +0 -1
- package/dist/authorization/model/memory.js +0 -26
- package/dist/authorization/model/memory.js.map +0 -1
- package/dist/authorization/model/oauth_app.d.ts +0 -2
- package/dist/authorization/model/oauth_app.d.ts.map +0 -1
- package/dist/authorization/model/oauth_app.js +0 -31
- package/dist/authorization/model/oauth_app.js.map +0 -1
- package/dist/authorization/model/organization.d.ts +0 -2
- package/dist/authorization/model/organization.d.ts.map +0 -1
- package/dist/authorization/model/organization.js +0 -58
- package/dist/authorization/model/organization.js.map +0 -1
- package/dist/authorization/model/platform_client.d.ts +0 -2
- package/dist/authorization/model/platform_client.d.ts.map +0 -1
- package/dist/authorization/model/platform_client.js +0 -31
- package/dist/authorization/model/platform_client.js.map +0 -1
- package/dist/authorization/model/plugin.d.ts +0 -2
- package/dist/authorization/model/plugin.d.ts.map +0 -1
- package/dist/authorization/model/plugin.js +0 -45
- package/dist/authorization/model/plugin.js.map +0 -1
- package/dist/authorization/model/schedule.d.ts +0 -2
- package/dist/authorization/model/schedule.d.ts.map +0 -1
- package/dist/authorization/model/schedule.js +0 -34
- package/dist/authorization/model/schedule.js.map +0 -1
- package/dist/authorization/model/session.d.ts +0 -2
- package/dist/authorization/model/session.d.ts.map +0 -1
- package/dist/authorization/model/session.js +0 -38
- package/dist/authorization/model/session.js.map +0 -1
- package/dist/authorization/model/skill.d.ts +0 -2
- package/dist/authorization/model/skill.d.ts.map +0 -1
- package/dist/authorization/model/skill.js +0 -39
- package/dist/authorization/model/skill.js.map +0 -1
- package/dist/authorization/model/workflow.d.ts +0 -2
- package/dist/authorization/model/workflow.d.ts.map +0 -1
- package/dist/authorization/model/workflow.js +0 -39
- package/dist/authorization/model/workflow.js.map +0 -1
- package/dist/authorization/model/workflow_execution.d.ts +0 -2
- package/dist/authorization/model/workflow_execution.d.ts.map +0 -1
- package/dist/authorization/model/workflow_execution.js +0 -33
- package/dist/authorization/model/workflow_execution.js.map +0 -1
- package/dist/authorization/model/workflow_instance.d.ts +0 -2
- package/dist/authorization/model/workflow_instance.d.ts.map +0 -1
- package/dist/authorization/model/workflow_instance.js +0 -96
- package/dist/authorization/model/workflow_instance.js.map +0 -1
- package/dist/authorization/store-test-kit.d.ts +0 -62
- package/dist/authorization/store-test-kit.d.ts.map +0 -1
- package/dist/authorization/store-test-kit.js +0 -265
- package/dist/authorization/store-test-kit.js.map +0 -1
- package/src/authorization/__tests__/fixtures/fga/agent-channel-owner.fga.yaml +0 -112
- package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +0 -177
- package/src/authorization/__tests__/fixtures/fga/agent-share-owner.fga.yaml +0 -109
- package/src/authorization/__tests__/fixtures/fga/artifact-org-and-owner.fga.yaml +0 -128
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +0 -318
- package/src/authorization/__tests__/fixtures/fga/channel-app-owner.fga.yaml +0 -125
- package/src/authorization/__tests__/fixtures/fga/channel-participation.fga.yaml +0 -201
- package/src/authorization/__tests__/fixtures/fga/channel-session-visibility.fga.yaml +0 -187
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +0 -214
- package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +0 -129
- package/src/authorization/__tests__/fixtures/fga/memory-subject-only.fga.yaml +0 -72
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +0 -308
- package/src/authorization/__tests__/fixtures/fga/org-shared-environment.fga.yaml +0 -144
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +0 -199
- package/src/authorization/__tests__/fixtures/fga/provider-standing-viewer.fga.yaml +0 -40
- package/src/authorization/__tests__/fixtures/fga/reserved-label-writer.fga.yaml +0 -41
- package/src/authorization/__tests__/fixtures/fga/schedule-owner.fga.yaml +0 -113
- package/src/authorization/__tests__/fixtures/fga/schedule-session-visibility.fga.yaml +0 -214
- package/src/authorization/__tests__/fixtures/fga/session-personal-resource.fga.yaml +0 -117
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +0 -190
- package/src/authorization/model/agent.ts +0 -64
- package/src/authorization/model/agent_channel.ts +0 -57
- package/src/authorization/model/agent_execution.ts +0 -33
- package/src/authorization/model/agent_instance.ts +0 -66
- package/src/authorization/model/agent_share.ts +0 -40
- package/src/authorization/model/api_key.ts +0 -28
- package/src/authorization/model/artifact.ts +0 -38
- package/src/authorization/model/channel_app.ts +0 -39
- package/src/authorization/model/environment.ts +0 -48
- package/src/authorization/model/execution_context.ts +0 -20
- package/src/authorization/model/iam_policy.ts +0 -42
- package/src/authorization/model/identity_account.ts +0 -26
- package/src/authorization/model/mcp_server.ts +0 -59
- package/src/authorization/model/memory.ts +0 -27
- package/src/authorization/model/oauth_app.ts +0 -43
- package/src/authorization/model/organization.ts +0 -59
- package/src/authorization/model/platform_client.ts +0 -43
- package/src/authorization/model/plugin.ts +0 -66
- package/src/authorization/model/schedule.ts +0 -49
- package/src/authorization/model/session.ts +0 -50
- package/src/authorization/model/skill.ts +0 -60
- package/src/authorization/model/workflow.ts +0 -60
- package/src/authorization/model/workflow_execution.ts +0 -46
- package/src/authorization/model/workflow_instance.ts +0 -125
- package/src/authorization/store-test-kit.ts +0 -395
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* source, an absent object answering nothing, a store fault propagating
|
|
15
15
|
* as the fault it is — the ratified store-fault mapping, which is what
|
|
16
16
|
* lets the driver above fold it to `unavailable` and never to a denial.
|
|
17
|
+
* Its grants-to-teams arm evaluates the built-in model's Enterprise team
|
|
18
|
+
* type: a grant to a team reaches its members and nobody
|
|
19
|
+
* who left the organization, and a person in no team — every open-source
|
|
20
|
+
* caller — still costs the source exactly one read.
|
|
17
21
|
*/
|
|
18
22
|
import { create } from "@bufbuild/protobuf";
|
|
19
23
|
import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
@@ -40,20 +44,12 @@ import { deriveTuples, newDerivedTupleSource } from "../derived-tuples.js";
|
|
|
40
44
|
import { checkRelation } from "../evaluator.js";
|
|
41
45
|
import { rowFactsOf } from "../facts.js";
|
|
42
46
|
import { builtInModel } from "../model/index.js";
|
|
43
|
-
import type { KindDeclaration } from "../model/rewrite.js";
|
|
44
47
|
import type { Person } from "../tuples.js";
|
|
45
48
|
import { formatTuple, parseObjectRef } from "../tuples.js";
|
|
46
49
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
47
50
|
import type { OpenedStore } from "./drivers.js";
|
|
48
|
-
import { fixtureRow } from "./support.js";
|
|
49
|
-
|
|
50
|
-
function declared(type: string): KindDeclaration {
|
|
51
|
-
const declaration = builtInModel.byType(type);
|
|
52
|
-
if (declaration === undefined) {
|
|
53
|
-
throw new Error(`${type} is declared in this slice`);
|
|
54
|
-
}
|
|
55
|
-
return declaration;
|
|
56
|
-
}
|
|
51
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
52
|
+
import type { StoredKindDeclaration } from "./support.js";
|
|
57
53
|
|
|
58
54
|
function derivedFor(
|
|
59
55
|
type: string,
|
|
@@ -64,7 +60,7 @@ function derivedFor(
|
|
|
64
60
|
spec?: Readonly<Record<string, unknown>>;
|
|
65
61
|
},
|
|
66
62
|
): string[] {
|
|
67
|
-
const declaration =
|
|
63
|
+
const declaration = storedDeclaration(type);
|
|
68
64
|
const row = fixtureRow(declaration, {
|
|
69
65
|
id: `${type}-1`,
|
|
70
66
|
org: facts.org ?? "acme",
|
|
@@ -244,6 +240,7 @@ const SEEDED_KINDS: ReadonlyArray<ApiResourceKind> = [
|
|
|
244
240
|
ApiResourceKind.workflow_instance,
|
|
245
241
|
ApiResourceKind.workflow_execution,
|
|
246
242
|
ApiResourceKind.identity_account,
|
|
243
|
+
ApiResourceKind.team,
|
|
247
244
|
];
|
|
248
245
|
|
|
249
246
|
afterAll(dropPostgresFixture);
|
|
@@ -295,7 +292,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
295
292
|
opened = await fixture.open();
|
|
296
293
|
counted = countingPolicies(newResourceIamPolicyStore(opened.store));
|
|
297
294
|
accounts = newResourceIdentityAccountStore(opened.store);
|
|
298
|
-
const organization =
|
|
295
|
+
const organization = storedDeclaration("organization");
|
|
299
296
|
await opened.store.saveResource(
|
|
300
297
|
ApiResourceKind.organization,
|
|
301
298
|
"acme",
|
|
@@ -307,7 +304,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
307
304
|
createdBy: ROOT.accountId,
|
|
308
305
|
}),
|
|
309
306
|
);
|
|
310
|
-
const agent =
|
|
307
|
+
const agent = storedDeclaration("agent");
|
|
311
308
|
await opened.store.saveResource(
|
|
312
309
|
ApiResourceKind.agent,
|
|
313
310
|
"agt_team",
|
|
@@ -433,17 +430,28 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
433
430
|
).toEqual([]);
|
|
434
431
|
});
|
|
435
432
|
|
|
436
|
-
it("an absent object,
|
|
433
|
+
it("an absent object, a declared kind this edition stores no row of, a rowless kind, or a kind the model does not declare answers no tuples", async () => {
|
|
437
434
|
const source = sourceFor(ROOT);
|
|
438
435
|
expect(
|
|
439
436
|
await source.tuplesOf(parseObjectRef("agent:agt_missing"), "owner"),
|
|
440
437
|
).toEqual([]);
|
|
438
|
+
// Declared, served by the wider editions: the store has no such row.
|
|
441
439
|
expect(
|
|
442
440
|
await source.tuplesOf(
|
|
443
441
|
parseObjectRef("identity_provider:idp"),
|
|
444
442
|
"owner",
|
|
445
443
|
),
|
|
446
444
|
).toEqual([]);
|
|
445
|
+
// Rowless: no row is loaded for it at all.
|
|
446
|
+
expect(
|
|
447
|
+
await source.tuplesOf(parseObjectRef("platform:stigmer"), "operator"),
|
|
448
|
+
).toEqual([]);
|
|
449
|
+
expect(
|
|
450
|
+
await source.tuplesOf(
|
|
451
|
+
parseObjectRef("api_resource_version:v1"),
|
|
452
|
+
"owner",
|
|
453
|
+
),
|
|
454
|
+
).toEqual([]);
|
|
447
455
|
expect(
|
|
448
456
|
await source.tuplesOf(parseObjectRef("nonsense:x"), "owner"),
|
|
449
457
|
).toEqual([]);
|
|
@@ -462,7 +470,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
462
470
|
|
|
463
471
|
it("a source seeded with a candidate's facts derives that object's tuples WITHOUT loading its row; the parent hop still loads", async () => {
|
|
464
472
|
let rowReads = 0;
|
|
465
|
-
const agent =
|
|
473
|
+
const agent = storedDeclaration("agent");
|
|
466
474
|
const seeded = newDerivedTupleSource(
|
|
467
475
|
{
|
|
468
476
|
store: {
|
|
@@ -517,7 +525,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
517
525
|
// carry, so the rule loads the row — one read, stated in the list
|
|
518
526
|
// scope's cost pin as well.
|
|
519
527
|
let rowReads = 0;
|
|
520
|
-
const instance =
|
|
528
|
+
const instance = storedDeclaration("agent_instance");
|
|
521
529
|
const row = fixtureRow(instance, {
|
|
522
530
|
id: "ai_seeded",
|
|
523
531
|
org: "acme",
|
|
@@ -567,6 +575,173 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
567
575
|
});
|
|
568
576
|
});
|
|
569
577
|
|
|
578
|
+
/**
|
|
579
|
+
* The grants made to a person's teams, through the real source and the
|
|
580
|
+
* evaluator over the built-in model's Enterprise team type. Seeded:
|
|
581
|
+
* `acme` with Dave a member and Root its owner; `tm_core` and
|
|
582
|
+
* `tm_other` in `acme`; Dave and Erin granted `member` on `tm_core`,
|
|
583
|
+
* Erin holding no organization role (she left); `agt_core` shared with
|
|
584
|
+
* `tm_core#member` and `agt_other` with `tm_other#member`, both private
|
|
585
|
+
* and Carol's.
|
|
586
|
+
*/
|
|
587
|
+
describe.skipIf(fixture.skip)("the grants made to a person's teams", () => {
|
|
588
|
+
let opened: OpenedStore;
|
|
589
|
+
let counted: ReturnType<typeof countingPolicies>;
|
|
590
|
+
const ERIN: Person = {
|
|
591
|
+
accountId: "ida_erin",
|
|
592
|
+
aliases: new Set(["ida_erin"]),
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
async function seed(
|
|
596
|
+
declaration: StoredKindDeclaration,
|
|
597
|
+
facts: Parameters<typeof fixtureRow>[1],
|
|
598
|
+
): Promise<void> {
|
|
599
|
+
await opened.store.saveResource(
|
|
600
|
+
declaration.kind,
|
|
601
|
+
facts.id,
|
|
602
|
+
declaration.schema,
|
|
603
|
+
fixtureRow(declaration, facts),
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
beforeEach(async () => {
|
|
608
|
+
opened = await fixture.open();
|
|
609
|
+
counted = countingPolicies(newResourceIamPolicyStore(opened.store));
|
|
610
|
+
await seed(storedDeclaration("organization"), {
|
|
611
|
+
id: "acme",
|
|
612
|
+
org: "",
|
|
613
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
614
|
+
createdBy: ROOT.accountId,
|
|
615
|
+
});
|
|
616
|
+
for (const id of ["tm_core", "tm_other"]) {
|
|
617
|
+
await seed(storedDeclaration("team"), {
|
|
618
|
+
id,
|
|
619
|
+
org: "acme",
|
|
620
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
621
|
+
createdBy: ROOT.accountId,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
for (const [id, team] of [
|
|
625
|
+
["agt_core", "tm_core"],
|
|
626
|
+
["agt_other", "tm_other"],
|
|
627
|
+
] as const) {
|
|
628
|
+
await seed(storedDeclaration("agent"), {
|
|
629
|
+
id,
|
|
630
|
+
org: "acme",
|
|
631
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
632
|
+
createdBy: "ida_carol",
|
|
633
|
+
});
|
|
634
|
+
await grant(
|
|
635
|
+
counted.policies,
|
|
636
|
+
triple({ kind: "team", id: team, relation: "member" }, "viewer", {
|
|
637
|
+
kind: "agent",
|
|
638
|
+
id,
|
|
639
|
+
}),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
await grant(counted.policies, orgRole(ROOT.accountId, "owner", "acme"));
|
|
643
|
+
await grant(
|
|
644
|
+
counted.policies,
|
|
645
|
+
orgRole(DAVE.accountId, "member", "acme"),
|
|
646
|
+
);
|
|
647
|
+
for (const member of [DAVE, ERIN]) {
|
|
648
|
+
await grant(
|
|
649
|
+
counted.policies,
|
|
650
|
+
triple(
|
|
651
|
+
{ kind: "identity_account", id: member.accountId },
|
|
652
|
+
"member",
|
|
653
|
+
{ kind: "team", id: "tm_core" },
|
|
654
|
+
),
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
afterEach(async () => {
|
|
660
|
+
await opened.close();
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
function sourceFor(person: Person) {
|
|
664
|
+
return newDerivedTupleSource(
|
|
665
|
+
{
|
|
666
|
+
store: opened.store,
|
|
667
|
+
policies: counted.policies,
|
|
668
|
+
accounts: newResourceIdentityAccountStore(opened.store),
|
|
669
|
+
model: builtInModel,
|
|
670
|
+
},
|
|
671
|
+
person,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async function allowed(
|
|
676
|
+
person: Person,
|
|
677
|
+
object: string,
|
|
678
|
+
relation: string,
|
|
679
|
+
): Promise<boolean> {
|
|
680
|
+
return checkRelation(
|
|
681
|
+
{ model: builtInModel, source: sourceFor(person) },
|
|
682
|
+
parseObjectRef(object),
|
|
683
|
+
relation,
|
|
684
|
+
person,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
it("a grant to a team reaches its member: the source serves the team's row on the object, and the member views it", async () => {
|
|
689
|
+
expect(
|
|
690
|
+
(
|
|
691
|
+
await sourceFor(DAVE).tuplesOf(
|
|
692
|
+
parseObjectRef("agent:agt_core"),
|
|
693
|
+
"viewer",
|
|
694
|
+
)
|
|
695
|
+
).map(formatTuple),
|
|
696
|
+
).toEqual(["agent:agt_core#viewer@team:tm_core#member"]);
|
|
697
|
+
expect(await allowed(DAVE, "agent:agt_core", "can_view")).toBe(true);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("a member who left the organization reads the team's rows and is admitted by none of them — the model's bound, not a cleanup", async () => {
|
|
701
|
+
expect(
|
|
702
|
+
(
|
|
703
|
+
await sourceFor(ERIN).tuplesOf(
|
|
704
|
+
parseObjectRef("agent:agt_core"),
|
|
705
|
+
"viewer",
|
|
706
|
+
)
|
|
707
|
+
).map(formatTuple),
|
|
708
|
+
).toEqual(["agent:agt_core#viewer@team:tm_core#member"]);
|
|
709
|
+
expect(await allowed(ERIN, "agent:agt_core", "can_view")).toBe(false);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it("a grant to a team the person is not in never enters their source", async () => {
|
|
713
|
+
expect(
|
|
714
|
+
await sourceFor(DAVE).tuplesOf(
|
|
715
|
+
parseObjectRef("agent:agt_other"),
|
|
716
|
+
"viewer",
|
|
717
|
+
),
|
|
718
|
+
).toEqual([]);
|
|
719
|
+
expect(await allowed(DAVE, "agent:agt_other", "can_view")).toBe(false);
|
|
720
|
+
expect(await allowed(ROOT, "agent:agt_core", "can_view")).toBe(true);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
it("reads once for a person in no team and once more per team held, however many objects are asked", async () => {
|
|
724
|
+
const root = sourceFor(ROOT);
|
|
725
|
+
await root.tuplesOf(parseObjectRef("agent:agt_core"), "viewer");
|
|
726
|
+
await root.tuplesOf(parseObjectRef("agent:agt_other"), "viewer");
|
|
727
|
+
expect(counted.reads()).toBe(1);
|
|
728
|
+
const dave = sourceFor(DAVE);
|
|
729
|
+
await dave.tuplesOf(parseObjectRef("agent:agt_core"), "viewer");
|
|
730
|
+
await dave.tuplesOf(parseObjectRef("agent:agt_other"), "viewer");
|
|
731
|
+
await dave.tuplesOf(parseObjectRef("organization:acme"), "member");
|
|
732
|
+
expect(counted.reads()).toBe(1 + 2);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it("personTuples stays the person's own rows — the organization directory's candidates do not grow with team grants", async () => {
|
|
736
|
+
expect(
|
|
737
|
+
(await sourceFor(DAVE).personTuples()).map(formatTuple).sort(),
|
|
738
|
+
).toEqual([
|
|
739
|
+
"organization:acme#member@identity_account:ida_dave",
|
|
740
|
+
"team:tm_core#member@identity_account:ida_dave",
|
|
741
|
+
]);
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
570
745
|
/**
|
|
571
746
|
* The two derived rules through the real source and the evaluator:
|
|
572
747
|
* `default_of` from the blueprint's pointer, `execution_viewer` from
|
|
@@ -589,7 +764,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
589
764
|
id: string,
|
|
590
765
|
facts: Omit<Parameters<typeof fixtureRow>[1], "id">,
|
|
591
766
|
): Promise<void> {
|
|
592
|
-
const declaration =
|
|
767
|
+
const declaration = storedDeclaration(type);
|
|
593
768
|
await opened.store.saveResource(
|
|
594
769
|
declaration.kind,
|
|
595
770
|
id,
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pins the evaluator's semantics the store tests cannot express (the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
3
|
+
* model's `.fga.yaml` suites prove its answers; this file proves the
|
|
4
|
+
* machinery under them): how a person matches a tuple, that a direct
|
|
5
|
+
* tuple outside the line's type restriction is ignored (model drift fails
|
|
6
|
+
* closed), how far a resolution may walk, that an intersection holds only
|
|
7
|
+
* while every member does (the team bound) and stops at its first false
|
|
8
|
+
* member, and that a declaration cycle or an undeclared target is a
|
|
9
|
+
* thrown fault and never a silent deny. What the model file may hold is
|
|
10
|
+
* the reader's to refuse (model/__tests__/openfga-json.test.ts).
|
|
11
11
|
*
|
|
12
12
|
* The fixture source is the in-memory one the store-test kit uses, so the
|
|
13
13
|
* two proofs share one tuple vocabulary. Where a case needs a shape the
|
|
14
|
-
* real model does not have (a cycle, a chain past the depth bound
|
|
15
|
-
* declares a throwaway model
|
|
16
|
-
* over the built-in model so the fixtures are real
|
|
14
|
+
* real model does not have (a cycle, a chain past the depth bound, a type
|
|
15
|
+
* the model omits), it declares a throwaway model (throwaway-model.ts);
|
|
16
|
+
* everything else runs over the built-in model so the fixtures are real
|
|
17
|
+
* relations.
|
|
17
18
|
*/
|
|
18
19
|
import { describe, expect, it } from "vitest";
|
|
19
20
|
|
|
@@ -26,23 +27,21 @@ import {
|
|
|
26
27
|
checkRelation,
|
|
27
28
|
} from "../evaluator.js";
|
|
28
29
|
import { builtInModel, newModel } from "../model/index.js";
|
|
30
|
+
import type { Person, Tuple, TupleSource } from "../tuples.js";
|
|
31
|
+
import {
|
|
32
|
+
newInMemoryTupleSource,
|
|
33
|
+
parseObjectRef,
|
|
34
|
+
parseSubject,
|
|
35
|
+
} from "../tuples.js";
|
|
29
36
|
import {
|
|
30
37
|
computed,
|
|
31
|
-
declareKind,
|
|
32
38
|
direct,
|
|
33
39
|
from,
|
|
34
|
-
intersection,
|
|
35
40
|
objectOf,
|
|
41
|
+
throwawayDeclaration,
|
|
36
42
|
union,
|
|
37
43
|
usersetOf,
|
|
38
|
-
} from "
|
|
39
|
-
import type { Rewrite } from "../model/rewrite.js";
|
|
40
|
-
import type { Person, Tuple, TupleSource } from "../tuples.js";
|
|
41
|
-
import {
|
|
42
|
-
newInMemoryTupleSource,
|
|
43
|
-
parseObjectRef,
|
|
44
|
-
parseSubject,
|
|
45
|
-
} from "../tuples.js";
|
|
44
|
+
} from "./throwaway-model.js";
|
|
46
45
|
|
|
47
46
|
/** `object#relation@subject` in the FGA tuple notation, the fixtures' own spelling. */
|
|
48
47
|
function tuple(text: string): Tuple {
|
|
@@ -157,13 +156,13 @@ describe("relations and kinds the model does not declare", () => {
|
|
|
157
156
|
).toBe(false);
|
|
158
157
|
});
|
|
159
158
|
|
|
160
|
-
it("an undeclared kind as the TARGET is a thrown fault — the
|
|
159
|
+
it("an undeclared kind as the TARGET is a thrown fault — the model declares every kind, so reaching here is a registry gap", async () => {
|
|
161
160
|
await expect(
|
|
162
|
-
allowed(source, "
|
|
161
|
+
allowed(source, "api_resource_version:v1", "can_view", person("ida_carol")),
|
|
163
162
|
).rejects.toMatchObject({ reason: "undeclared-target-kind" });
|
|
164
163
|
});
|
|
165
164
|
|
|
166
|
-
it("
|
|
165
|
+
it("a declared kind reached through a hop with no tuple on it yields nothing — `agent.platform_viewer` walks `identity_provider#platform_user`", async () => {
|
|
167
166
|
const platform = newInMemoryTupleSource([
|
|
168
167
|
tuple("agent:p#organization@organization:acme"),
|
|
169
168
|
tuple("agent:p#platform_viewer@identity_provider:idp#platform_user"),
|
|
@@ -172,6 +171,30 @@ describe("relations and kinds the model does not declare", () => {
|
|
|
172
171
|
await allowed(platform, "agent:p", "can_view", person("ida_zed")),
|
|
173
172
|
).toBe(false);
|
|
174
173
|
});
|
|
174
|
+
|
|
175
|
+
it("a type the model omits, reached through a hop, is an object with no relations — nothing, never a fault", async () => {
|
|
176
|
+
const model = newModel([
|
|
177
|
+
throwawayDeclaration({
|
|
178
|
+
kind: ApiResourceKind.agent,
|
|
179
|
+
schema: AgentSchema,
|
|
180
|
+
relations: [
|
|
181
|
+
["viewer", direct(usersetOf("api_resource_version", "member"))],
|
|
182
|
+
["can_view", computed("viewer")],
|
|
183
|
+
],
|
|
184
|
+
}),
|
|
185
|
+
]);
|
|
186
|
+
const hop = newInMemoryTupleSource([
|
|
187
|
+
tuple("agent:p#viewer@api_resource_version:v1#member"),
|
|
188
|
+
]);
|
|
189
|
+
expect(
|
|
190
|
+
await checkRelation(
|
|
191
|
+
{ model, source: hop },
|
|
192
|
+
parseObjectRef("agent:p"),
|
|
193
|
+
"can_view",
|
|
194
|
+
person("ida_zed"),
|
|
195
|
+
),
|
|
196
|
+
).toBe(false);
|
|
197
|
+
});
|
|
175
198
|
});
|
|
176
199
|
|
|
177
200
|
describe("the walk itself", () => {
|
|
@@ -203,10 +226,9 @@ describe("the walk itself", () => {
|
|
|
203
226
|
|
|
204
227
|
it("bounds the resolution at OpenFGA's own limit and reports the overrun as a fault", async () => {
|
|
205
228
|
// A chain of `next from link` deeper than the bound, on a throwaway model.
|
|
206
|
-
const deep =
|
|
229
|
+
const deep = throwawayDeclaration({
|
|
207
230
|
kind: ApiResourceKind.agent,
|
|
208
231
|
schema: AgentSchema,
|
|
209
|
-
source: "test/deep.fga",
|
|
210
232
|
relations: [
|
|
211
233
|
["link", direct(objectOf("agent"))],
|
|
212
234
|
[
|
|
@@ -230,23 +252,8 @@ describe("the walk itself", () => {
|
|
|
230
252
|
});
|
|
231
253
|
|
|
232
254
|
it("an intersection holds only while every member does — a team member reaches a team grant only while still an organization viewer", async () => {
|
|
233
|
-
// The Enterprise team type
|
|
234
|
-
//
|
|
235
|
-
const team = declareKind({
|
|
236
|
-
kind: ApiResourceKind.team,
|
|
237
|
-
schema: AgentSchema,
|
|
238
|
-
source: "test/team.fga",
|
|
239
|
-
relations: [
|
|
240
|
-
["organization", direct(objectOf("organization"))],
|
|
241
|
-
[
|
|
242
|
-
"member",
|
|
243
|
-
intersection(
|
|
244
|
-
direct(objectOf("identity_account")),
|
|
245
|
-
from("viewer", "organization"),
|
|
246
|
-
),
|
|
247
|
-
],
|
|
248
|
-
],
|
|
249
|
-
});
|
|
255
|
+
// The model's Enterprise team type: `member` is a person granted the
|
|
256
|
+
// role AND still one of the organization's viewers.
|
|
250
257
|
const source = newInMemoryTupleSource([
|
|
251
258
|
tuple("organization:acme#viewer@identity_account:ida_vic"),
|
|
252
259
|
tuple("team:sre#organization@organization:acme"),
|
|
@@ -255,10 +262,9 @@ describe("the walk itself", () => {
|
|
|
255
262
|
tuple("agent:a#organization@organization:acme"),
|
|
256
263
|
tuple("agent:a#viewer@team:sre#member"),
|
|
257
264
|
]);
|
|
258
|
-
const model = newModel([...builtInModel.declarations, team]);
|
|
259
265
|
const check = (who: Person): Promise<boolean> =>
|
|
260
266
|
checkRelation(
|
|
261
|
-
{ model, source },
|
|
267
|
+
{ model: builtInModel, source },
|
|
262
268
|
parseObjectRef("agent:a"),
|
|
263
269
|
"can_view",
|
|
264
270
|
who,
|
|
@@ -270,21 +276,6 @@ describe("the walk itself", () => {
|
|
|
270
276
|
});
|
|
271
277
|
|
|
272
278
|
it("an intersection stops at its first false member — the direct arm is read before the hop that bounds it", async () => {
|
|
273
|
-
const team = declareKind({
|
|
274
|
-
kind: ApiResourceKind.team,
|
|
275
|
-
schema: AgentSchema,
|
|
276
|
-
source: "test/team.fga",
|
|
277
|
-
relations: [
|
|
278
|
-
["organization", direct(objectOf("organization"))],
|
|
279
|
-
[
|
|
280
|
-
"member",
|
|
281
|
-
intersection(
|
|
282
|
-
direct(objectOf("identity_account")),
|
|
283
|
-
from("viewer", "organization"),
|
|
284
|
-
),
|
|
285
|
-
],
|
|
286
|
-
],
|
|
287
|
-
});
|
|
288
279
|
const asked: string[] = [];
|
|
289
280
|
const inner = newInMemoryTupleSource([
|
|
290
281
|
tuple("team:sre#organization@organization:acme"),
|
|
@@ -297,7 +288,7 @@ describe("the walk itself", () => {
|
|
|
297
288
|
};
|
|
298
289
|
expect(
|
|
299
290
|
await checkRelation(
|
|
300
|
-
{ model:
|
|
291
|
+
{ model: builtInModel, source: counting },
|
|
301
292
|
parseObjectRef("team:sre"),
|
|
302
293
|
"member",
|
|
303
294
|
person("ida_nobody"),
|
|
@@ -307,10 +298,9 @@ describe("the walk itself", () => {
|
|
|
307
298
|
});
|
|
308
299
|
|
|
309
300
|
it("a declaration that re-enters itself on one object is a cycle fault, never a silent false", async () => {
|
|
310
|
-
const cyclic =
|
|
301
|
+
const cyclic = throwawayDeclaration({
|
|
311
302
|
kind: ApiResourceKind.agent,
|
|
312
303
|
schema: AgentSchema,
|
|
313
|
-
source: "test/cyclic.fga",
|
|
314
304
|
relations: [
|
|
315
305
|
["a", computed("b")],
|
|
316
306
|
["b", computed("a")],
|
|
@@ -326,100 +316,3 @@ describe("the walk itself", () => {
|
|
|
326
316
|
).rejects.toBeInstanceOf(AuthorizationEvaluationError);
|
|
327
317
|
});
|
|
328
318
|
});
|
|
329
|
-
|
|
330
|
-
describe("declareKind validates a transcript at load", () => {
|
|
331
|
-
it("refuses a computed or tupleset reference to a relation the kind does not declare", () => {
|
|
332
|
-
expect(() =>
|
|
333
|
-
declareKind({
|
|
334
|
-
kind: ApiResourceKind.agent,
|
|
335
|
-
schema: AgentSchema,
|
|
336
|
-
source: "test/bad.fga",
|
|
337
|
-
relations: [["can_view", computed("viewer")]],
|
|
338
|
-
}),
|
|
339
|
-
).toThrow("relation 'viewer'");
|
|
340
|
-
expect(() =>
|
|
341
|
-
declareKind({
|
|
342
|
-
kind: ApiResourceKind.agent,
|
|
343
|
-
schema: AgentSchema,
|
|
344
|
-
source: "test/bad.fga",
|
|
345
|
-
relations: [["owner", from("admin", "organization")]],
|
|
346
|
-
}),
|
|
347
|
-
).toThrow("tupleset 'organization'");
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it("refuses a relation declared twice", () => {
|
|
351
|
-
expect(() =>
|
|
352
|
-
declareKind({
|
|
353
|
-
kind: ApiResourceKind.agent,
|
|
354
|
-
schema: AgentSchema,
|
|
355
|
-
source: "test/bad.fga",
|
|
356
|
-
relations: [
|
|
357
|
-
["viewer", direct(objectOf("identity_account"))],
|
|
358
|
-
["viewer", direct(usersetOf("organization", "viewer"))],
|
|
359
|
-
],
|
|
360
|
-
}),
|
|
361
|
-
).toThrow("declared twice");
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("refuses a line no .fga file could hold without parentheses, and an operator over one member", () => {
|
|
365
|
-
const declare = (rewrite: Rewrite) =>
|
|
366
|
-
declareKind({
|
|
367
|
-
kind: ApiResourceKind.agent,
|
|
368
|
-
schema: AgentSchema,
|
|
369
|
-
source: "test/bad.fga",
|
|
370
|
-
relations: [
|
|
371
|
-
["organization", direct(objectOf("organization"))],
|
|
372
|
-
["viewer", rewrite],
|
|
373
|
-
],
|
|
374
|
-
});
|
|
375
|
-
expect(() =>
|
|
376
|
-
declare(
|
|
377
|
-
intersection(
|
|
378
|
-
union(direct(objectOf("identity_account")), computed("organization")),
|
|
379
|
-
from("viewer", "organization"),
|
|
380
|
-
),
|
|
381
|
-
),
|
|
382
|
-
).toThrow("nests or inside and");
|
|
383
|
-
expect(() =>
|
|
384
|
-
declare(
|
|
385
|
-
union(
|
|
386
|
-
intersection(
|
|
387
|
-
direct(objectOf("identity_account")),
|
|
388
|
-
from("viewer", "organization"),
|
|
389
|
-
),
|
|
390
|
-
computed("organization"),
|
|
391
|
-
),
|
|
392
|
-
),
|
|
393
|
-
).toThrow("nests and inside or");
|
|
394
|
-
expect(() =>
|
|
395
|
-
declare(intersection(direct(objectOf("identity_account")))),
|
|
396
|
-
).toThrow("fewer than two members");
|
|
397
|
-
expect(() =>
|
|
398
|
-
declare(
|
|
399
|
-
intersection(
|
|
400
|
-
direct(objectOf("identity_account")),
|
|
401
|
-
from("viewer", "organization"),
|
|
402
|
-
),
|
|
403
|
-
),
|
|
404
|
-
).not.toThrow();
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("carries the FGA type name of its kind and its relations in file order", () => {
|
|
408
|
-
const declaration = declareKind({
|
|
409
|
-
kind: ApiResourceKind.mcp_server,
|
|
410
|
-
schema: AgentSchema,
|
|
411
|
-
source: "test/order.fga",
|
|
412
|
-
relations: [
|
|
413
|
-
["organization", direct(objectOf("organization"))],
|
|
414
|
-
["owner", direct(usersetOf("organization", "admin"))],
|
|
415
|
-
["can_edit", computed("owner")],
|
|
416
|
-
],
|
|
417
|
-
});
|
|
418
|
-
expect(declaration.type).toBe("mcp_server");
|
|
419
|
-
expect([...declaration.relations.keys()]).toEqual([
|
|
420
|
-
"organization",
|
|
421
|
-
"owner",
|
|
422
|
-
"can_edit",
|
|
423
|
-
]);
|
|
424
|
-
});
|
|
425
|
-
});
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "../../pipeline/steps/authorization-tuples.js";
|
|
37
37
|
import { rowFactsOf, rowFactsOfEntry } from "../facts.js";
|
|
38
38
|
import { builtInModel } from "../model/index.js";
|
|
39
|
-
import { fixtureRow } from "./support.js";
|
|
39
|
+
import { fixtureRow, hasStoredRows, storedDeclaration } from "./support.js";
|
|
40
40
|
|
|
41
41
|
const CREATOR: CallerIdentity = {
|
|
42
42
|
identityId: "ida_carol",
|
|
@@ -46,7 +46,7 @@ const CREATOR: CallerIdentity = {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
|
|
49
|
-
for (const declaration of builtInModel.declarations) {
|
|
49
|
+
for (const declaration of builtInModel.declarations.filter(hasStoredRows)) {
|
|
50
50
|
for (const visibility of [
|
|
51
51
|
ApiResourceVisibility.visibility_private,
|
|
52
52
|
ApiResourceVisibility.visibility_org,
|
|
@@ -77,10 +77,7 @@ describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
it("carries a legacy row's missing organization as no scope link, where the create-time resolver throws", () => {
|
|
80
|
-
const agent =
|
|
81
|
-
if (agent === undefined) {
|
|
82
|
-
throw new Error("agent is declared in this slice");
|
|
83
|
-
}
|
|
80
|
+
const agent = storedDeclaration("agent");
|
|
84
81
|
const row = fixtureRow(agent, {
|
|
85
82
|
id: "agent-legacy",
|
|
86
83
|
org: "",
|
|
@@ -94,10 +91,7 @@ describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
|
|
|
94
91
|
});
|
|
95
92
|
|
|
96
93
|
it("reads an absent creator stamp as the empty string", () => {
|
|
97
|
-
const agent =
|
|
98
|
-
if (agent === undefined) {
|
|
99
|
-
throw new Error("agent is declared in this slice");
|
|
100
|
-
}
|
|
94
|
+
const agent = storedDeclaration("agent");
|
|
101
95
|
const bare = create(agent.schema, {
|
|
102
96
|
metadata: { id: "agent-bare", org: "acme" },
|
|
103
97
|
});
|
|
@@ -129,7 +123,7 @@ describe("a list candidate against the loaded row — the facts' two producers",
|
|
|
129
123
|
return offered;
|
|
130
124
|
}
|
|
131
125
|
|
|
132
|
-
for (const declaration of builtInModel.declarations) {
|
|
126
|
+
for (const declaration of builtInModel.declarations.filter(hasStoredRows)) {
|
|
133
127
|
for (const visibility of [
|
|
134
128
|
ApiResourceVisibility.visibility_private,
|
|
135
129
|
ApiResourceVisibility.visibility_org,
|