@astrale-domains/shell 0.1.6 → 0.1.8
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 +73 -173
- package/dist/domain.d.ts +499 -31
- package/dist/domain.js +99 -78
- package/dist/functions/application/index.js +2 -1
- package/dist/functions/application/legacy/index.js +1 -0
- package/dist/functions/application/legacy/list-entrypoints.js +8 -0
- package/dist/functions/authority/capabilities.js +1 -0
- package/dist/functions/authority/composition.js +47 -0
- package/dist/functions/authority/index.js +2 -0
- package/dist/functions/authority/roles.js +113 -0
- package/dist/functions/directory/disconnect-directory.js +35 -0
- package/dist/functions/directory/index.js +1 -0
- package/dist/functions/group/assign-member.js +7 -0
- package/dist/functions/group/change-group-membership.js +101 -20
- package/dist/functions/group/create-group.js +23 -0
- package/dist/functions/group/delete-group.js +33 -0
- package/dist/functions/group/deprovision-group.js +27 -0
- package/dist/functions/group/group-errors.js +43 -0
- package/dist/functions/group/group-membership-errors.js +3 -1
- package/dist/functions/group/group.js +51 -0
- package/dist/functions/group/index.js +8 -2
- package/dist/functions/group/legacy/index.js +1 -0
- package/dist/functions/group/legacy/membership.js +64 -0
- package/dist/functions/group/membership-laws.js +37 -0
- package/dist/functions/group/provision-group.js +33 -0
- package/dist/functions/group/unassign-member.js +7 -0
- package/dist/functions/group/update-group.js +55 -0
- package/dist/functions/invitation/index.js +2 -0
- package/dist/functions/invitation/invitation-errors.js +13 -0
- package/dist/functions/invitation/invitation-methods.js +41 -0
- package/dist/functions/invitation/send-invitation.js +131 -0
- package/dist/functions/user/account.js +35 -0
- package/dist/functions/user/create-user.js +45 -0
- package/dist/functions/user/deactivate-user.js +19 -0
- package/dist/functions/user/delete-user.js +34 -0
- package/dist/functions/user/deprovision-user.js +21 -0
- package/dist/functions/user/index.js +15 -1
- package/dist/functions/user/invite-errors.js +18 -0
- package/dist/functions/user/invite-user.js +42 -34
- package/dist/functions/user/legacy/claimed-account.js +11 -0
- package/dist/functions/user/legacy/index.js +4 -0
- package/dist/functions/user/legacy/invite.js +20 -0
- package/dist/functions/user/legacy/migrate-accounts.js +98 -0
- package/dist/functions/user/legacy/record-profile.js +19 -0
- package/dist/functions/user/protect-administrators.js +16 -0
- package/dist/functions/user/provision-user.js +70 -0
- package/dist/functions/user/reactivate-user.js +19 -0
- package/dist/functions/user/read-account.js +12 -0
- package/dist/functions/user/request-invitation.js +17 -0
- package/dist/functions/user/update-profile.js +22 -0
- package/dist/functions/user/update-user.js +68 -0
- package/dist/functions/user/user-errors.js +69 -0
- package/dist/functions/user/write-account.js +57 -0
- package/dist/functions/view-access/index.js +2 -1
- package/dist/functions/view-access/{assign-views.js → legacy/assign-views.js} +11 -7
- package/dist/functions/view-access/legacy/index.js +1 -0
- package/dist/functions/view-access/{view-access-errors.js → legacy/view-access-errors.js} +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/integrations/user-invitation/invite-user-request.d.ts +6 -0
- package/dist/legacy/index.js +1 -0
- package/dist/legacy/release.js +33 -0
- package/dist/mutations/composition/grant-role.js +13 -0
- package/dist/mutations/composition/index.js +1 -0
- package/dist/mutations/group/index.js +1 -1
- package/dist/mutations/group/legacy/index.js +1 -0
- package/dist/mutations/group/legacy/membership-class.js +7 -0
- package/dist/mutations/group/set-group-membership.js +60 -19
- package/dist/mutations/identity/create-profile.js +37 -0
- package/dist/mutations/identity/delete-identity.js +28 -0
- package/dist/mutations/identity/index.js +4 -0
- package/dist/mutations/identity/write-account.js +109 -0
- package/dist/mutations/identity/write-group.js +72 -0
- package/dist/mutations/invitation/index.js +1 -0
- package/dist/mutations/invitation/write-invitation.js +79 -0
- package/dist/mutations/user/legacy/index.js +1 -0
- package/dist/mutations/user/{record-user-profile.js → legacy/record-user-profile.js} +5 -2
- package/dist/providers/admin-enrollment/create-admin-enrollment-provider.js +6 -3
- package/dist/providers/admin-enrollment/legacy/index.js +1 -0
- package/dist/providers/admin-enrollment/legacy/send-without-profile.js +16 -0
- package/dist/queries/application/legacy/index.js +1 -0
- package/dist/queries/application/legacy/read-installed-entrypoints.js +47 -0
- package/dist/queries/composition/index.js +2 -0
- package/dist/queries/composition/read-extensions.js +34 -0
- package/dist/queries/composition/read-role-nodes.js +76 -0
- package/dist/queries/group/index.js +2 -1
- package/dist/queries/group/legacy/index.js +3 -0
- package/dist/queries/group/legacy/read-core-groups.js +46 -0
- package/dist/queries/group/legacy/read-member-of-group.js +24 -0
- package/dist/queries/group/legacy/read-members-of-group.js +27 -0
- package/dist/queries/group/read-group-members.js +73 -0
- package/dist/queries/group/read-group-membership-endpoints.js +11 -0
- package/dist/queries/group/read-group-membership.js +6 -21
- package/dist/queries/identity/index.js +1 -0
- package/dist/queries/identity/read-identities.js +29 -0
- package/dist/queries/invitation/index.js +1 -0
- package/dist/queries/invitation/read-invitation.js +50 -0
- package/dist/queries/profile/index.js +1 -0
- package/dist/queries/profile/read-profile.js +15 -0
- package/dist/queries/role/index.js +1 -0
- package/dist/queries/role/read-role-node.js +21 -0
- package/dist/queries/user/legacy/index.js +1 -0
- package/dist/queries/user/legacy/read-user-profile.js +27 -0
- package/dist/runtime.d.ts +20147 -466
- package/dist/runtime.js +34 -7
- package/dist/schema/index.d.ts +7 -4
- package/dist/schema/index.js +8 -4
- package/dist/schema/legacy/index.js +1 -0
- package/dist/schema/legacy/schema.js +32 -0
- package/dist/schema/modules/application/legacy/entrypoint-entry.js +11 -0
- package/dist/schema/modules/application/legacy/functions/index.js +1 -0
- package/dist/schema/modules/application/legacy/functions/list-entrypoints.js +22 -0
- package/dist/schema/modules/application/legacy/index.js +2 -0
- package/dist/schema/modules/directory/functions/disconnect-directory.d.ts +13 -0
- package/dist/schema/modules/directory/functions/disconnect-directory.js +15 -0
- package/dist/schema/modules/directory/functions/index.d.ts +1 -0
- package/dist/schema/modules/directory/functions/index.js +1 -0
- package/dist/schema/modules/directory/index.d.ts +1 -0
- package/dist/schema/modules/directory/index.js +1 -0
- package/dist/schema/modules/group/classes/group-profile-link.d.ts +34 -0
- package/dist/schema/modules/group/classes/group-profile-link.js +21 -0
- package/dist/schema/modules/group/classes/group-profile.d.ts +15 -0
- package/dist/schema/modules/group/classes/group-profile.js +20 -0
- package/dist/schema/modules/group/classes/group.d.ts +87 -1
- package/dist/schema/modules/group/classes/group.js +38 -7
- package/dist/schema/modules/group/classes/index.d.ts +2 -1
- package/dist/schema/modules/group/classes/index.js +3 -1
- package/dist/schema/modules/group/functions/assign-member.js +17 -0
- package/dist/schema/modules/group/functions/assign-members.js +17 -0
- package/dist/schema/modules/group/functions/create-group.js +20 -0
- package/dist/schema/modules/group/functions/delete-group.js +12 -0
- package/dist/schema/modules/group/functions/deprovision-group.js +14 -0
- package/dist/schema/modules/group/functions/index.js +9 -2
- package/dist/schema/modules/group/functions/provision-group.js +14 -0
- package/dist/schema/modules/group/functions/unassign-member.js +17 -0
- package/dist/schema/modules/group/functions/unassign-members.js +17 -0
- package/dist/schema/modules/group/functions/update-group.js +20 -0
- package/dist/schema/modules/group/index.d.ts +3 -3
- package/dist/schema/modules/group/index.js +4 -3
- package/dist/schema/modules/group/legacy/admission.d.ts +9 -0
- package/dist/schema/modules/group/legacy/admission.js +9 -0
- package/dist/schema/modules/group/legacy/classes/index.d.ts +1 -0
- package/dist/schema/modules/group/legacy/classes/index.js +1 -0
- package/dist/schema/modules/group/{classes → legacy/classes}/member-of-group.d.ts +10 -1
- package/dist/schema/modules/group/legacy/classes/member-of-group.js +32 -0
- package/dist/schema/modules/group/legacy/core-groups.d.ts +22 -0
- package/dist/schema/modules/group/legacy/core-groups.js +28 -0
- package/dist/schema/modules/group/legacy/index.d.ts +5 -0
- package/dist/schema/modules/group/legacy/index.js +6 -0
- package/dist/schema/modules/group/legacy/policies/belongs-to-group.d.ts +5 -0
- package/dist/schema/modules/group/legacy/policies/belongs-to-group.js +9 -0
- package/dist/schema/modules/group/legacy/policies/index.d.ts +2 -0
- package/dist/schema/modules/group/legacy/policies/index.js +2 -0
- package/dist/schema/modules/group/legacy/policies/owns-membership-group.d.ts +6 -0
- package/dist/schema/modules/group/legacy/policies/owns-membership-group.js +12 -0
- package/dist/schema/modules/group/legacy/relaxations.d.ts +21 -0
- package/dist/schema/modules/group/legacy/relaxations.js +27 -0
- package/dist/schema/modules/group/policies/describes-group-of-subject.d.ts +2 -0
- package/dist/schema/modules/group/policies/describes-group-of-subject.js +7 -0
- package/dist/schema/modules/group/policies/index.d.ts +2 -1
- package/dist/schema/modules/group/policies/index.js +2 -1
- package/dist/schema/modules/group/policies/is-group-profile-source.d.ts +2 -0
- package/dist/schema/modules/group/policies/is-group-profile-source.js +6 -0
- package/dist/schema/modules/group/types/group-membership-input.js +8 -0
- package/dist/schema/modules/group/types/group.js +10 -0
- package/dist/schema/modules/group/types/index.js +2 -1
- package/dist/schema/modules/invitation/classes/index.js +2 -0
- package/dist/schema/modules/invitation/classes/invitation-user.js +17 -0
- package/dist/schema/modules/invitation/classes/invitation.js +32 -0
- package/dist/schema/modules/invitation/functions/accept.js +16 -0
- package/dist/schema/modules/invitation/functions/cancel.js +12 -0
- package/dist/schema/modules/invitation/functions/index.js +4 -0
- package/dist/schema/modules/invitation/functions/resend.js +12 -0
- package/dist/schema/modules/invitation/functions/send.js +20 -0
- package/dist/schema/modules/invitation/index.js +2 -0
- package/dist/schema/modules/invitation/types/index.js +1 -0
- package/dist/schema/modules/invitation/types/invitation-status.js +5 -0
- package/dist/schema/modules/namespace/classes/alias.d.ts +2 -1
- package/dist/schema/modules/namespace/classes/alias.js +11 -2
- package/dist/schema/modules/namespace/functions/link.js +3 -2
- package/dist/schema/modules/namespace/functions/rename.js +3 -2
- package/dist/schema/modules/namespace/functions/unlink.js +3 -2
- package/dist/schema/modules/role/classes/index.d.ts +1 -0
- package/dist/schema/modules/role/classes/index.js +1 -0
- package/dist/schema/modules/role/classes/role.d.ts +16 -0
- package/dist/schema/modules/role/classes/role.js +19 -0
- package/dist/schema/modules/role/core/index.d.ts +1 -0
- package/dist/schema/modules/role/core/index.js +1 -0
- package/dist/schema/modules/role/core/roles.d.ts +39 -0
- package/dist/schema/modules/role/core/roles.js +31 -0
- package/dist/schema/modules/role/index.d.ts +2 -0
- package/dist/schema/modules/role/index.js +2 -0
- package/dist/schema/modules/user/classes/index.d.ts +2 -0
- package/dist/schema/modules/user/classes/index.js +3 -0
- package/dist/schema/modules/user/classes/user-profile-link.d.ts +35 -0
- package/dist/schema/modules/user/classes/user-profile-link.js +22 -0
- package/dist/schema/modules/user/classes/user-profile.d.ts +41 -0
- package/dist/schema/modules/user/classes/user-profile.js +30 -0
- package/dist/schema/modules/user/classes/user.d.ts +133 -8
- package/dist/schema/modules/user/classes/user.js +46 -15
- package/dist/schema/modules/user/functions/create-user.js +21 -0
- package/dist/schema/modules/user/functions/deactivate-user.js +12 -0
- package/dist/schema/modules/user/functions/delete-user.js +12 -0
- package/dist/schema/modules/user/functions/deprovision-user.js +14 -0
- package/dist/schema/modules/user/functions/index.js +8 -1
- package/dist/schema/modules/user/functions/invite-user.js +23 -4
- package/dist/schema/modules/user/functions/provision-user.js +25 -0
- package/dist/schema/modules/user/functions/reactivate-user.js +12 -0
- package/dist/schema/modules/user/functions/update-profile.js +16 -0
- package/dist/schema/modules/user/functions/update-user.js +22 -0
- package/dist/schema/modules/user/index.d.ts +2 -1
- package/dist/schema/modules/user/index.js +2 -1
- package/dist/schema/modules/user/legacy/account-properties.js +24 -0
- package/dist/schema/modules/user/legacy/functions/index.js +2 -0
- package/dist/schema/modules/user/legacy/functions/migrate-accounts.js +23 -0
- package/dist/schema/modules/user/legacy/functions/record-profile.js +18 -0
- package/dist/schema/modules/user/legacy/index.js +5 -0
- package/dist/schema/modules/user/legacy/invite.js +15 -0
- package/dist/schema/modules/user/legacy/relaxations.js +37 -0
- package/dist/schema/modules/user/policies/describes-subject.d.ts +2 -0
- package/dist/schema/modules/user/policies/describes-subject.js +7 -0
- package/dist/schema/modules/user/policies/index.d.ts +2 -0
- package/dist/schema/modules/user/policies/index.js +2 -0
- package/dist/schema/modules/user/policies/is-profile-source.d.ts +2 -0
- package/dist/schema/modules/user/policies/is-profile-source.js +6 -0
- package/dist/schema/modules/user/types/account.d.ts +12 -0
- package/dist/schema/modules/user/types/account.js +12 -0
- package/dist/schema/modules/user/types/index.d.ts +1 -1
- package/dist/schema/modules/user/types/index.js +1 -1
- package/dist/schema/modules/view-access/classes/view-access.d.ts +7 -11
- package/dist/schema/modules/view-access/classes/view-access.js +17 -12
- package/dist/schema/modules/view-access/index.d.ts +1 -2
- package/dist/schema/modules/view-access/index.js +1 -2
- package/dist/schema/modules/view-access/legacy/functions/assign-views.js +22 -0
- package/dist/schema/modules/view-access/legacy/functions/index.js +1 -0
- package/dist/schema/modules/view-access/legacy/index.js +2 -0
- package/dist/schema/modules/view-access/legacy/view-assignment.js +24 -0
- package/dist/schema/modules/view-access/policies/holds-view-access.d.ts +2 -0
- package/dist/schema/modules/view-access/policies/holds-view-access.js +5 -0
- package/dist/schema/modules/view-access/policies/index.d.ts +1 -0
- package/dist/schema/modules/view-access/policies/index.js +1 -0
- package/dist/schema/policies/has-role.d.ts +6 -0
- package/dist/schema/policies/has-role.js +9 -0
- package/dist/schema/policies/index.d.ts +1 -0
- package/dist/schema/policies/index.js +1 -4
- package/dist/schema/policies/legacy/administers-shell-class.js +21 -0
- package/dist/schema/policies/legacy/index.js +4 -0
- package/dist/schema/policies/legacy/is-self.js +9 -0
- package/dist/schema/policies/legacy/owns-class.js +12 -0
- package/dist/schema/policies/legacy/uses-function.js +9 -0
- package/dist/schema/schema.d.ts +1937 -119
- package/dist/schema/schema.js +35 -15
- package/dist/schema/types/attributes.js +15 -0
- package/dist/schema/types/index.js +1 -0
- package/dist/utils/attributes/attributes.js +12 -0
- package/dist/utils/attributes/index.js +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/same-json/index.js +1 -0
- package/dist/utils/same-json/same-json.js +9 -0
- package/package.json +3 -3
- package/dist/functions/application/list-entrypoints.js +0 -6
- package/dist/functions/group/assign-member-to-group.js +0 -4
- package/dist/functions/group/unassign-member-from-group.js +0 -4
- package/dist/functions/user/record-profile.js +0 -18
- package/dist/mutations/user/index.js +0 -1
- package/dist/queries/application/index.js +0 -1
- package/dist/queries/application/read-installed-entrypoints.js +0 -28
- package/dist/queries/user/index.js +0 -1
- package/dist/queries/user/read-user-profile.js +0 -9
- package/dist/schema/modules/application/functions/index.d.ts +0 -1
- package/dist/schema/modules/application/functions/index.js +0 -1
- package/dist/schema/modules/application/functions/list-entrypoints.d.ts +0 -15
- package/dist/schema/modules/application/functions/list-entrypoints.js +0 -16
- package/dist/schema/modules/application/index.d.ts +0 -2
- package/dist/schema/modules/application/index.js +0 -2
- package/dist/schema/modules/application/types/entrypoint-entry.d.ts +0 -8
- package/dist/schema/modules/application/types/entrypoint-entry.js +0 -7
- package/dist/schema/modules/application/types/index.d.ts +0 -1
- package/dist/schema/modules/application/types/index.js +0 -1
- package/dist/schema/modules/group/classes/member-of-group.js +0 -12
- package/dist/schema/modules/group/core/administrators-group.d.ts +0 -1
- package/dist/schema/modules/group/core/administrators-group.js +0 -6
- package/dist/schema/modules/group/core/applications-group.d.ts +0 -1
- package/dist/schema/modules/group/core/applications-group.js +0 -6
- package/dist/schema/modules/group/core/index.d.ts +0 -3
- package/dist/schema/modules/group/core/index.js +0 -3
- package/dist/schema/modules/group/core/members-group.d.ts +0 -1
- package/dist/schema/modules/group/core/members-group.js +0 -6
- package/dist/schema/modules/group/functions/assign-member-to-group.js +0 -11
- package/dist/schema/modules/group/functions/unassign-member-from-group.js +0 -11
- package/dist/schema/modules/group/policies/belongs-to-group.d.ts +0 -1
- package/dist/schema/modules/group/policies/belongs-to-group.js +0 -5
- package/dist/schema/modules/user/functions/record-profile.js +0 -14
- package/dist/schema/modules/user/types/user-profile.d.ts +0 -7
- package/dist/schema/modules/user/types/user-profile.js +0 -7
- package/dist/schema/modules/view-access/core/index.d.ts +0 -1
- package/dist/schema/modules/view-access/core/index.js +0 -1
- package/dist/schema/modules/view-access/core/view-only-group.d.ts +0 -5
- package/dist/schema/modules/view-access/core/view-only-group.js +0 -10
- package/dist/schema/modules/view-access/functions/assign-views.d.ts +0 -16
- package/dist/schema/modules/view-access/functions/assign-views.js +0 -17
- package/dist/schema/modules/view-access/functions/index.d.ts +0 -1
- package/dist/schema/modules/view-access/functions/index.js +0 -1
- package/dist/schema/modules/view-access/types/index.js +0 -1
- package/dist/schema/modules/view-access/types/view-assignment.js +0 -16
- package/dist/schema/policies/administers-shell-class.js +0 -11
- package/dist/schema/policies/is-self.js +0 -5
- package/dist/schema/policies/owns-class.js +0 -7
- package/dist/schema/policies/uses-function.js +0 -5
package/dist/domain.js
CHANGED
|
@@ -1,96 +1,117 @@
|
|
|
1
1
|
import { defineDomain, defineFrontend, vite, requirements, } from '@astrale-os/sdk/domain';
|
|
2
|
-
import { K
|
|
2
|
+
import { K } from '@astrale-os/sdk/schema';
|
|
3
3
|
import { ShellSchema, S } from './schema/index.js';
|
|
4
|
+
// Seam L-SHELL-7, L-SHELL-9, L-SHELL-11: the bindings legacy groups and callers still need.
|
|
5
|
+
import { legacyRelease } from './legacy/index.js';
|
|
4
6
|
import runtime, {} from './runtime.js';
|
|
5
7
|
const frontend = defineFrontend({
|
|
6
8
|
schema: ShellSchema,
|
|
7
9
|
source: vite({ config: './frontend/vite.config.ts' }),
|
|
8
10
|
routes: { application: { path: '/', handshake: 'shell' } },
|
|
9
11
|
});
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The Roles the Shell release binds: `admin` is the only granter of every Shell Role and of the
|
|
14
|
+
* grantable Kernel Roles (R2); compositions are release bindings, not Core Edges (AM1).
|
|
15
|
+
*/
|
|
16
|
+
const domainRequirements = requirements(legacyRelease({
|
|
17
|
+
roles: [
|
|
18
|
+
// Profiles, member ⊂ admin, and their atomic Roles (AM1); admin grants them all.
|
|
19
|
+
{
|
|
20
|
+
role: S.core.nodes['workspace.member'],
|
|
21
|
+
members: [S.core.nodes['workspace.admin']],
|
|
22
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
23
|
+
},
|
|
24
|
+
{ role: S.core.nodes['workspace.admin'], granters: [S.core.nodes['workspace.admin']] },
|
|
25
|
+
// A member reads profiles only (user-profile.reader, group-profile.reader); the accounts and
|
|
26
|
+
// groups themselves are read by administrators.
|
|
27
|
+
{
|
|
28
|
+
role: S.core.nodes['user.reader'],
|
|
29
|
+
members: [S.core.nodes['workspace.admin']],
|
|
30
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
role: S.core.nodes['user.writer'],
|
|
34
|
+
members: [S.core.nodes['workspace.admin']],
|
|
35
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
role: S.core.nodes['group.reader'],
|
|
39
|
+
members: [S.core.nodes['workspace.admin']],
|
|
40
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
role: S.core.nodes['group.writer'],
|
|
44
|
+
members: [S.core.nodes['workspace.admin']],
|
|
45
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
46
|
+
},
|
|
47
|
+
{ role: S.core.nodes['directory.provisioner'], granters: [S.core.nodes['workspace.admin']] },
|
|
48
|
+
// A member reads every profile; it changes its own once an administrator allows it.
|
|
49
|
+
{
|
|
50
|
+
role: S.core.nodes['user-profile.reader'],
|
|
51
|
+
members: [S.core.nodes['workspace.member']],
|
|
52
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
53
|
+
},
|
|
54
|
+
// Off by default: an administrator lets members change their own profile from the console
|
|
55
|
+
// (workspace.member → self-writer). Left out of the release so an installation keeps that
|
|
56
|
+
// choice.
|
|
57
|
+
{
|
|
58
|
+
role: S.core.nodes['user-profile.self-writer'],
|
|
59
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
role: S.core.nodes['group-profile.reader'],
|
|
63
|
+
members: [S.core.nodes['workspace.member']],
|
|
64
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
65
|
+
},
|
|
66
|
+
// Whoever holds it opens the OS view; a member holds it, and an administrator through member.
|
|
67
|
+
{
|
|
68
|
+
role: S.core.nodes['os.viewer'],
|
|
69
|
+
members: [S.core.nodes['workspace.member']],
|
|
70
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
71
|
+
},
|
|
72
|
+
// Each profile holds the Kernel profile of its rank; Kernel admin is granted by Root alone.
|
|
73
|
+
// Kernel reader is no profile's: admin grants it to whoever only opens the Views it is given
|
|
74
|
+
// (AM10).
|
|
75
|
+
{ role: K.core.nodes.reader, granters: [S.core.nodes['workspace.admin']] },
|
|
76
|
+
{
|
|
77
|
+
role: K.core.nodes.writer,
|
|
78
|
+
members: [S.core.nodes['workspace.member']],
|
|
79
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
80
|
+
},
|
|
81
|
+
{ role: K.core.nodes.admin, members: [S.core.nodes['workspace.admin']] },
|
|
82
|
+
// Shell itself reads compositions (U4, U5, G3) and runs its own Queries and Mutations.
|
|
27
83
|
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
S.classes.User.static.methods.invite,
|
|
32
|
-
S.classes.User.methods.recordProfile,
|
|
33
|
-
S.classes.Group.static.methods.assignMember,
|
|
34
|
-
S.classes.Group.static.methods.unassignMember,
|
|
35
|
-
S.classes.Namespace.methods.link,
|
|
36
|
-
S.classes.Namespace.methods.unlink,
|
|
37
|
-
S.classes.Namespace.methods.rename,
|
|
38
|
-
// Core Edges cannot author Kernel extends_with, so administrators hold it directly.
|
|
39
|
-
S.functions.listEntrypoints,
|
|
40
|
-
S.functions.assignViews,
|
|
41
|
-
],
|
|
42
|
-
classes: [
|
|
43
|
-
{ class: S.classes.User, operations: ['create', 'read', 'traverse', 'update', 'delete'] },
|
|
44
|
-
{
|
|
45
|
-
class: S.classes.Group,
|
|
46
|
-
operations: ['create', 'read', 'traverse', 'update', 'delete'],
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
class: S.classes.member_of_group,
|
|
50
|
-
operations: ['create', 'read', 'traverse', 'update', 'delete'],
|
|
51
|
-
},
|
|
52
|
-
{ class: S.classes.Namespace, operations: ['read', 'traverse'] },
|
|
53
|
-
{ class: S.classes.alias, operations: ['create', 'read', 'traverse', 'update', 'delete'] },
|
|
54
|
-
// assignViews gives and withdraws Views; a Mutation may too. view_access carries no property.
|
|
55
|
-
{ class: S.classes.view_access, operations: ['create', 'read', 'traverse', 'delete'] },
|
|
56
|
-
{ class: K.classes.extends_with, operations: ['create', 'read', 'traverse', 'delete'] },
|
|
57
|
-
],
|
|
58
|
-
authority: {
|
|
59
|
-
grant: ['create', 'read', 'update', 'delete', 'traverse', 'use', 'revoke'],
|
|
60
|
-
revoke: ['create', 'read', 'update', 'delete', 'traverse', 'use', 'grant'],
|
|
61
|
-
},
|
|
84
|
+
role: K.core.nodes['authority.reader'],
|
|
85
|
+
members: [requirements.domain],
|
|
86
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
62
87
|
},
|
|
63
88
|
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
K.functions.download,
|
|
68
|
-
// Upload exposes no data: its receipt binds to the caller, and attaching it still takes a
|
|
69
|
-
// Mutation under the caller's rights. The Kernel bounds it per blob and per Instance quota.
|
|
70
|
-
K.functions.upload,
|
|
71
|
-
K.functions.introspect,
|
|
72
|
-
K.functions.query,
|
|
73
|
-
K.functions.mutate,
|
|
74
|
-
K.classes.View.static.methods.resolve,
|
|
75
|
-
// The workspace opens only on this catalogue: an invited member must be able to read it.
|
|
76
|
-
S.functions.listEntrypoints,
|
|
77
|
-
// The GUI records the email and name each account signs in with; isSelf bounds it.
|
|
78
|
-
S.classes.User.methods.recordProfile,
|
|
79
|
-
],
|
|
89
|
+
role: K.core.nodes['graph.reader'],
|
|
90
|
+
members: [requirements.domain],
|
|
91
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
80
92
|
},
|
|
81
93
|
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
subject: S.core.nodes.applications,
|
|
86
|
-
functions: [
|
|
87
|
-
S.functions.listEntrypoints,
|
|
88
|
-
K.classes.View.static.methods.resolve,
|
|
89
|
-
K.functions.query,
|
|
90
|
-
],
|
|
94
|
+
role: K.core.nodes['graph.writer'],
|
|
95
|
+
members: [requirements.domain],
|
|
96
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
91
97
|
},
|
|
98
|
+
// admin grants the other parts of reader and writer and the readers outside them (R2).
|
|
99
|
+
{ role: K.core.nodes['graph.manager'], granters: [S.core.nodes['workspace.admin']] },
|
|
100
|
+
{ role: K.core.nodes['blob.reader'], granters: [S.core.nodes['workspace.admin']] },
|
|
101
|
+
{ role: K.core.nodes['blob.writer'], granters: [S.core.nodes['workspace.admin']] },
|
|
102
|
+
{ role: K.core.nodes['blob.manager'], granters: [S.core.nodes['workspace.admin']] },
|
|
103
|
+
// A member reads every View, so the OS view lists every application; anyone else reads only
|
|
104
|
+
// the Views given to it by `view_access`. Independent of `os.viewer`, which only shows the OS.
|
|
105
|
+
{
|
|
106
|
+
role: K.core.nodes['view.reader'],
|
|
107
|
+
members: [S.core.nodes['workspace.member']],
|
|
108
|
+
granters: [S.core.nodes['workspace.admin']],
|
|
109
|
+
},
|
|
110
|
+
{ role: K.core.nodes['domain.reader'], granters: [S.core.nodes['workspace.admin']] },
|
|
111
|
+
{ role: K.core.nodes['execution.reader'], granters: [S.core.nodes['workspace.admin']] },
|
|
112
|
+
{ role: K.core.nodes['journal.reader'], granters: [S.core.nodes['workspace.admin']] },
|
|
92
113
|
],
|
|
93
|
-
});
|
|
114
|
+
}));
|
|
94
115
|
export const domain = defineDomain({
|
|
95
116
|
schema: ShellSchema,
|
|
96
117
|
runtime,
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
// Seam L-SHELL-11: the legacy handler the Runtime registers.
|
|
2
|
+
export { legacyListEntrypoints } from './legacy/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { legacyListEntrypoints } from './list-entrypoints.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { legacyReadInstalledEntrypoints } from '../../../queries/application/legacy/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated One graph read with Shell's authority: every Domain with an `entrypoint` Edge, its
|
|
5
|
+
* name and its entrypoint View (L-SHELL-11). Replaced by the visitor's own Query (V2). Removal:
|
|
6
|
+
* Shell major (delivery step 5) after 1Pact step 4.
|
|
7
|
+
*/
|
|
8
|
+
export const legacyListEntrypoints = defineAction()('listEntrypoints', async ({ query }) => ({ entrypoints: [...(await query(legacyReadInstalledEntrypoints, {}))] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readExtensions } from '../../queries/composition/index.js';
|
|
2
|
+
/** A composition deeper than this is refused as a defect rather than walked further. */
|
|
3
|
+
const MAXIMUM_DEPTH = 32;
|
|
4
|
+
/** Identities one frontier Query starts from. */
|
|
5
|
+
const FRONTIER = 256;
|
|
6
|
+
/**
|
|
7
|
+
* Every Identity reached from `start` along Kernel `extends_with`, with its Class: `outgoing`, what
|
|
8
|
+
* `start` holds; `incoming`, who holds `start`. The Kernel refuses cycles, so the walk ends.
|
|
9
|
+
*/
|
|
10
|
+
export async function walkComposition({ query }, start, direction) {
|
|
11
|
+
const reached = new Map();
|
|
12
|
+
let frontier = [start];
|
|
13
|
+
for (let depth = 0; frontier.length > 0; depth += 1) {
|
|
14
|
+
if (depth === MAXIMUM_DEPTH)
|
|
15
|
+
throw new RangeError('The composition is deeper than Shell walks.');
|
|
16
|
+
const next = [];
|
|
17
|
+
for (let index = 0; index < frontier.length; index += FRONTIER) {
|
|
18
|
+
const [first, ...others] = frontier.slice(index, index + FRONTIER);
|
|
19
|
+
if (first === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
const edges = await query(readExtensions, {
|
|
22
|
+
identities: [first, ...others],
|
|
23
|
+
direction,
|
|
24
|
+
});
|
|
25
|
+
for (const edge of edges) {
|
|
26
|
+
const [far, farClass] = direction === 'outgoing'
|
|
27
|
+
? [edge.target, edge.targetClass]
|
|
28
|
+
: [edge.source, edge.sourceClass];
|
|
29
|
+
if (far === start || reached.has(far))
|
|
30
|
+
continue;
|
|
31
|
+
reached.set(far, farClass);
|
|
32
|
+
next.push(far);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
frontier = next;
|
|
36
|
+
}
|
|
37
|
+
return reached;
|
|
38
|
+
}
|
|
39
|
+
/** The `extends_with` Edges into one Identity: its direct members. */
|
|
40
|
+
export async function readMembers({ query }, identity) {
|
|
41
|
+
return query(readExtensions, { identities: [identity], direction: 'incoming' });
|
|
42
|
+
}
|
|
43
|
+
/** The Identities one Identity extends directly. */
|
|
44
|
+
export async function readExtensionTargets({ query }, identity) {
|
|
45
|
+
const edges = await query(readExtensions, { identities: [identity], direction: 'outgoing' });
|
|
46
|
+
return new Set(edges.map(({ target }) => target));
|
|
47
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readRoleKeys } from '../../queries/composition/index.js';
|
|
2
|
+
import { readIdentities } from '../../queries/identity/index.js';
|
|
3
|
+
import { readRoleId, readRoleNode } from '../../queries/role/index.js';
|
|
4
|
+
import { walkComposition } from './composition.js';
|
|
5
|
+
/** Kernel reader and writer and their parts: every other Kernel Role administers (U4). */
|
|
6
|
+
const KERNEL_ORDINARY = new Set([
|
|
7
|
+
'reader',
|
|
8
|
+
'writer',
|
|
9
|
+
'graph.reader',
|
|
10
|
+
'graph.writer',
|
|
11
|
+
'graph.manager',
|
|
12
|
+
'blob.reader',
|
|
13
|
+
'blob.writer',
|
|
14
|
+
'blob.manager',
|
|
15
|
+
'view.reader',
|
|
16
|
+
'domain.reader',
|
|
17
|
+
]);
|
|
18
|
+
const SHELL_ADMINISTRATIVE = new Set([
|
|
19
|
+
'workspace.admin',
|
|
20
|
+
'user.writer',
|
|
21
|
+
'group.writer',
|
|
22
|
+
'directory.provisioner',
|
|
23
|
+
]);
|
|
24
|
+
const keyOf = (ref) => `${ref.origin}:${ref.name}`;
|
|
25
|
+
/** Identities one Role read starts from. */
|
|
26
|
+
const ROLE_STEP = 256;
|
|
27
|
+
/**
|
|
28
|
+
* One handler's view of authority. Each composition, and the Roles it holds, is read once; only the
|
|
29
|
+
* Roles a composition reaches are read, never the catalogue of every installed Role.
|
|
30
|
+
*/
|
|
31
|
+
export function authority(capabilities) {
|
|
32
|
+
const S = capabilities.domain;
|
|
33
|
+
const kernel = S.dependencies.kernel.origin;
|
|
34
|
+
const compositions = new Map();
|
|
35
|
+
const composition = (identity) => {
|
|
36
|
+
let walked = compositions.get(identity);
|
|
37
|
+
if (walked === undefined) {
|
|
38
|
+
walked = walkComposition(capabilities, identity, 'outgoing');
|
|
39
|
+
compositions.set(identity, walked);
|
|
40
|
+
}
|
|
41
|
+
return walked;
|
|
42
|
+
};
|
|
43
|
+
const held = new Map();
|
|
44
|
+
/** The Core keys of the Roles an Identity holds, `<origin>:<key>`. */
|
|
45
|
+
const heldKeys = (identity) => {
|
|
46
|
+
let keys = held.get(identity);
|
|
47
|
+
if (keys === undefined) {
|
|
48
|
+
keys = (async () => {
|
|
49
|
+
const reached = [...(await composition(identity)).keys()];
|
|
50
|
+
const found = [];
|
|
51
|
+
for (let index = 0; index < reached.length; index += ROLE_STEP) {
|
|
52
|
+
const [first, ...others] = reached.slice(index, index + ROLE_STEP);
|
|
53
|
+
if (first === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
const roles = await capabilities.query(readRoleKeys, { identities: [first, ...others] });
|
|
56
|
+
found.push(...roles.values());
|
|
57
|
+
}
|
|
58
|
+
return found;
|
|
59
|
+
})();
|
|
60
|
+
held.set(identity, keys);
|
|
61
|
+
}
|
|
62
|
+
return keys;
|
|
63
|
+
};
|
|
64
|
+
const nodes = new Map();
|
|
65
|
+
const roleNode = (role) => {
|
|
66
|
+
let node = nodes.get(role);
|
|
67
|
+
if (node === undefined) {
|
|
68
|
+
node = capabilities.query(readRoleId, { role: S.core.nodes[role].ref });
|
|
69
|
+
nodes.set(role, node);
|
|
70
|
+
}
|
|
71
|
+
return node;
|
|
72
|
+
};
|
|
73
|
+
const administrative = (key) => {
|
|
74
|
+
const [origin, name] = [key.slice(0, key.indexOf(':')), key.slice(key.indexOf(':') + 1)];
|
|
75
|
+
return ((origin === S.origin && SHELL_ADMINISTRATIVE.has(name)) ||
|
|
76
|
+
(origin === kernel && !KERNEL_ORDINARY.has(name)));
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
roleNode,
|
|
80
|
+
async callerHolds(role) {
|
|
81
|
+
const caller = capabilities.graph.caller;
|
|
82
|
+
if (caller === undefined)
|
|
83
|
+
return false;
|
|
84
|
+
return caller.query(readRoleNode, { role: S.core.nodes[role].ref });
|
|
85
|
+
},
|
|
86
|
+
async heldRole(identity) {
|
|
87
|
+
const held = await heldKeys(identity);
|
|
88
|
+
const admin = keyOf(S.core.nodes['workspace.admin'].ref);
|
|
89
|
+
const role = held.includes(admin) ? admin : held[0];
|
|
90
|
+
if (role === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
return role.startsWith(`${S.origin}:`) ? role.slice(S.origin.length + 1) : role;
|
|
93
|
+
},
|
|
94
|
+
async holdsAdministration(identity) {
|
|
95
|
+
return (await heldKeys(identity)).some(administrative);
|
|
96
|
+
},
|
|
97
|
+
async activeAdministrators() {
|
|
98
|
+
const admin = await roleNode('workspace.admin');
|
|
99
|
+
if (admin === undefined)
|
|
100
|
+
return [];
|
|
101
|
+
const holders = [...(await walkComposition(capabilities, admin, 'incoming')).keys()];
|
|
102
|
+
const [first, ...others] = holders;
|
|
103
|
+
if (first === undefined)
|
|
104
|
+
return [];
|
|
105
|
+
const accounts = await capabilities.query(readIdentities, {
|
|
106
|
+
kind: 'User',
|
|
107
|
+
ids: [first, ...others],
|
|
108
|
+
});
|
|
109
|
+
const frozen = S.classes.User.properties.frozen.key;
|
|
110
|
+
return accounts.filter(({ props }) => props[frozen] !== true).map(({ id }) => id);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { toGroup } from '../group/index.js';
|
|
3
|
+
import { toAccount, userErrors, writeAccount } from '../user/index.js';
|
|
4
|
+
import { updateGroup } from '../../mutations/identity/index.js';
|
|
5
|
+
import { readIdentities } from '../../queries/identity/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* The directory is disconnected (`dsync.deleted`): its accounts and groups become local. Their
|
|
8
|
+
* `directory` and `externalId` are erased; an account the directory had frozen stays frozen. Replaying it changes nothing (A3).
|
|
9
|
+
*/
|
|
10
|
+
export const disconnectDirectory = defineAction()('disconnectDirectory', { errors: userErrors }, async (context) => {
|
|
11
|
+
const { directory } = context.input;
|
|
12
|
+
const [users, groups] = await Promise.all([
|
|
13
|
+
context.query(readIdentities, { kind: 'User', where: { directory } }),
|
|
14
|
+
context.query(readIdentities, { kind: 'Group', where: { directory } }),
|
|
15
|
+
]);
|
|
16
|
+
for (const node of users) {
|
|
17
|
+
const account = toAccount(context.domain, node);
|
|
18
|
+
const written = await writeAccount(context, account, {
|
|
19
|
+
directory: null,
|
|
20
|
+
externalId: null,
|
|
21
|
+
});
|
|
22
|
+
if (written === 'DUPLICATE_KEY' || written === 'CHANGED') {
|
|
23
|
+
throw context.error('ACCOUNT_CHANGED', {});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const node of groups) {
|
|
27
|
+
const group = toGroup(context.domain, node);
|
|
28
|
+
await context.mutate(updateGroup, {
|
|
29
|
+
group: group.id,
|
|
30
|
+
set: {},
|
|
31
|
+
unset: ['directory', 'externalId'],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { users: users.length, groups: groups.length };
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { disconnectDirectory } from './disconnect-directory.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { changeGroupMembers, changeGroupMembership } from './change-group-membership.js';
|
|
3
|
+
import { groupMembershipErrors } from './group-membership-errors.js';
|
|
4
|
+
/** Make one account or group a member of a group, in one read and one write. */
|
|
5
|
+
export const assignMember = defineAction()('Group.assignMember', { errors: groupMembershipErrors }, (context) => changeGroupMembership(context, true));
|
|
6
|
+
/** Make several accounts or groups members of a group, in one commit. */
|
|
7
|
+
export const assignMembers = defineAction()('Group.assignMembers', { errors: groupMembershipErrors }, (context) => changeGroupMembers(context, context.input, true));
|
|
@@ -1,34 +1,115 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { setGroupMemberships } from '../../mutations/group/index.js';
|
|
2
|
+
import { readGroupMembers, readGroupMembersEndpoints } from '../../queries/group/index.js';
|
|
3
|
+
import { readIdentities } from '../../queries/identity/index.js';
|
|
3
4
|
import { canonicalNodeId } from '../../utils/canonical-node-path/index.js';
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import { readGroup } from './group.js';
|
|
6
|
+
// Seam L-SHELL-8, L-SHELL-9: legacy groups also keep `member_of_group`; a Domain's own Group
|
|
7
|
+
// subclass changes its members under that Domain's authority.
|
|
8
|
+
import { legacyChangeForeignMembership, legacyMembersEdges } from './legacy/index.js';
|
|
9
|
+
import { membershipRefusal } from './membership-laws.js';
|
|
10
|
+
/**
|
|
11
|
+
* Add or withdraw direct members of one Shell group: Kernel `extends_with` from each member to the
|
|
12
|
+
* group (G1), under G2 and G3, checked once for the group. The Kernel refuses a cycle. All the
|
|
13
|
+
* members change in one commit, guarded by what was observed of them; a member already where it is
|
|
14
|
+
* asked to be changes nothing, so a retry is harmless. After an unknown outcome the change is
|
|
15
|
+
* observed again, and an unconfirmed outcome stays explicit.
|
|
16
|
+
*/
|
|
17
|
+
export async function changeGroupMembers(context, input, present, { checkMembers = true } = {}) {
|
|
6
18
|
const invalid = () => context.error('INVALID_GROUP_MEMBERSHIP_REFERENCE', {});
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
19
|
+
const group = canonicalNodeId(input.group, invalid);
|
|
20
|
+
const [first, ...others] = new Set(input.members.map((member) => canonicalNodeId(member, invalid)));
|
|
21
|
+
if (first === undefined)
|
|
22
|
+
throw invalid();
|
|
23
|
+
const members = [first, ...others];
|
|
24
|
+
// A fixed number of reads, whatever the number of members: one per kind, never one per member.
|
|
25
|
+
const [identities, observed, stored] = await Promise.all([
|
|
26
|
+
// A list of members is not a typed Path input: each must be an account or a group.
|
|
27
|
+
checkMembers ? identitiesOnly(context, members) : true,
|
|
28
|
+
observeMembers(context, { members, group }, present),
|
|
29
|
+
readGroup(context, group),
|
|
30
|
+
]);
|
|
31
|
+
if (!identities)
|
|
32
|
+
throw invalid();
|
|
33
|
+
if (members.some((member) => !observed.visible.has(member)) || !observed.visible.has(group)) {
|
|
34
|
+
throw context.error('GROUP_MEMBERSHIP_ENDPOINTS_UNAVAILABLE', {});
|
|
35
|
+
}
|
|
36
|
+
if (observed.groupClass !== context.domain.classes.Group.key) {
|
|
37
|
+
for (const member of members) {
|
|
38
|
+
await legacyChangeForeignMembership(context, { member, group }, present);
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
if (stored === undefined)
|
|
43
|
+
throw context.error('GROUP_NOT_FOUND', {});
|
|
44
|
+
const refusal = await membershipRefusal(context, stored);
|
|
45
|
+
if (refusal !== undefined) {
|
|
46
|
+
if (refusal.code === 'GROUP_ROLE_PROTECTED') {
|
|
47
|
+
throw context.error(refusal.code, { role: refusal.role });
|
|
48
|
+
}
|
|
49
|
+
throw context.error(refusal.code, {});
|
|
50
|
+
}
|
|
51
|
+
if (observed.changes.length === 0)
|
|
20
52
|
return {};
|
|
21
|
-
const result = await context.
|
|
53
|
+
const result = await context.mutate(setGroupMemberships, {
|
|
54
|
+
group,
|
|
55
|
+
guards: observed.guards,
|
|
56
|
+
changes: observed.changes,
|
|
57
|
+
});
|
|
22
58
|
if (result.ok)
|
|
23
59
|
return {};
|
|
24
60
|
if (result.outcomeKnown) {
|
|
61
|
+
if (result.rule === 'cycle')
|
|
62
|
+
throw context.error('GROUP_MEMBERSHIP_CYCLE', {});
|
|
25
63
|
throw context.error('GROUP_MEMBERSHIP_CONFLICT', { cause: result.code });
|
|
26
64
|
}
|
|
27
|
-
const recovered = await context
|
|
28
|
-
if (
|
|
65
|
+
const recovered = await observeMembers(context, { members, group }, present);
|
|
66
|
+
if (recovered.changes.length === 0)
|
|
29
67
|
return {};
|
|
30
68
|
throw context.error('GROUP_MEMBERSHIP_UNCONFIRMED', {
|
|
31
69
|
cause: result.code,
|
|
32
70
|
outcomeKnown: false,
|
|
33
71
|
});
|
|
34
72
|
}
|
|
73
|
+
/** Add or withdraw one direct member of a Shell group. */
|
|
74
|
+
export const changeGroupMembership = (context, present) =>
|
|
75
|
+
// The member Path input already restricts its Class to an account or a group.
|
|
76
|
+
changeGroupMembers(context, { group: context.input.group, members: [context.input.member] }, present, { checkMembers: false });
|
|
77
|
+
/** Whether every member is an account or a group, a Domain's subclass included. */
|
|
78
|
+
async function identitiesOnly(context, members) {
|
|
79
|
+
const [first, ...others] = members;
|
|
80
|
+
if (first === undefined)
|
|
81
|
+
return false;
|
|
82
|
+
const ids = [first, ...others];
|
|
83
|
+
const found = await Promise.all([
|
|
84
|
+
context.query(readIdentities, { kind: 'User', ids }),
|
|
85
|
+
context.query(readIdentities, { kind: 'Group', ids }),
|
|
86
|
+
]);
|
|
87
|
+
const known = new Set(found.flat().map((node) => node.id));
|
|
88
|
+
return members.every((member) => known.has(member));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The members and their membership Edges to the group as they are, in three reads whatever their
|
|
92
|
+
* number, with the guards that keep them so and the Edges to change for the state asked for.
|
|
93
|
+
*/
|
|
94
|
+
async function observeMembers(context, selection, present) {
|
|
95
|
+
const [endpoints, authority, legacy] = await Promise.all([
|
|
96
|
+
context.query(readGroupMembersEndpoints, selection),
|
|
97
|
+
context.query(readGroupMembers, selection),
|
|
98
|
+
legacyMembersEdges(context, selection, present),
|
|
99
|
+
]);
|
|
100
|
+
const changes = [];
|
|
101
|
+
for (const member of selection.members) {
|
|
102
|
+
if (authority.present.has(member) !== present) {
|
|
103
|
+
changes.push({ member, edge: 'authority', present });
|
|
104
|
+
}
|
|
105
|
+
const wanted = legacy.wanted(member);
|
|
106
|
+
if (legacy.present.has(member) !== wanted)
|
|
107
|
+
changes.push({ member, edge: 'legacy', present: wanted });
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
visible: endpoints.visible,
|
|
111
|
+
...(endpoints.groupClass === undefined ? {} : { groupClass: endpoints.groupClass }),
|
|
112
|
+
guards: [endpoints.guard, authority.guard, legacy.guard],
|
|
113
|
+
changes,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
3
|
+
import { createGroup } from '../../mutations/identity/index.js';
|
|
4
|
+
import { MAX_ATTRIBUTES, boundedAttributes } from '../../utils/index.js';
|
|
5
|
+
import { groupErrors } from './group-errors.js';
|
|
6
|
+
/** Create a local group. An empty group holds nothing: creating one grants no right. */
|
|
7
|
+
export const create = defineAction()('Group.create', { errors: groupErrors }, async ({ input, mutate, error }) => {
|
|
8
|
+
const attributes = input.attributes === undefined ? undefined : boundedAttributes(input.attributes);
|
|
9
|
+
if (input.attributes !== undefined && attributes === undefined) {
|
|
10
|
+
throw error('GROUP_ATTRIBUTES_TOO_MANY', { max: MAX_ATTRIBUTES });
|
|
11
|
+
}
|
|
12
|
+
const result = await mutate(createGroup, {
|
|
13
|
+
values: {
|
|
14
|
+
name: input.name,
|
|
15
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
16
|
+
...(input.icon === undefined || input.icon === null ? {} : { icon: input.icon }),
|
|
17
|
+
...(attributes === undefined ? {} : { attributes }),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!result.ok)
|
|
21
|
+
throw error('GROUP_CHANGED', {});
|
|
22
|
+
return { group: Path.id(result.group).raw };
|
|
23
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { readMembers } from '../authority/index.js';
|
|
3
|
+
import { deleteIdentity } from '../../mutations/identity/index.js';
|
|
4
|
+
import { readProfile } from '../../queries/profile/index.js';
|
|
5
|
+
import { groupErrors } from './group-errors.js';
|
|
6
|
+
import { isProvidedGroup, readGroup } from './group.js';
|
|
7
|
+
import { roleProtected } from './membership-laws.js';
|
|
8
|
+
/**
|
|
9
|
+
* Withdraw a local group's memberships, incoming and outgoing, and delete it (G4). Only its directory
|
|
10
|
+
* deletes a provided group (G2); only an admin deletes a group that holds a Role (G3).
|
|
11
|
+
*/
|
|
12
|
+
export const remove = defineAction()('Group.delete', { errors: groupErrors }, async (context) => {
|
|
13
|
+
const { error, self } = context;
|
|
14
|
+
const group = await readGroup(context, self);
|
|
15
|
+
if (group === undefined)
|
|
16
|
+
throw error('GROUP_NOT_FOUND', {});
|
|
17
|
+
if (isProvidedGroup(group))
|
|
18
|
+
throw error('GROUP_DIRECTORY_MANAGED', {});
|
|
19
|
+
const role = await roleProtected(context, group);
|
|
20
|
+
if (role !== undefined)
|
|
21
|
+
throw error('GROUP_ROLE_PROTECTED', { role });
|
|
22
|
+
const members = await readMembers(context, self);
|
|
23
|
+
const profile = await context.query(readProfile, { kind: 'Group', node: self });
|
|
24
|
+
const result = await context.mutate(deleteIdentity, {
|
|
25
|
+
kind: 'Group',
|
|
26
|
+
node: self,
|
|
27
|
+
members,
|
|
28
|
+
...(profile === undefined ? {} : { profile }),
|
|
29
|
+
});
|
|
30
|
+
if (!result.ok)
|
|
31
|
+
throw error('GROUP_CHANGED', {});
|
|
32
|
+
return {};
|
|
33
|
+
});
|