@astrale-domains/shell 0.1.0-beta.12 → 0.1.0-beta.14
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 +67 -46
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/user/index.d.ts +1 -0
- package/dist/actions/user/index.js +1 -0
- package/dist/actions/user/resolve-home.d.ts +3 -0
- package/dist/actions/user/resolve-home.js +19 -0
- package/dist/application.d.ts +6 -0
- package/dist/application.js +30 -0
- package/dist/entrypoints/application.d.ts +1 -0
- package/dist/entrypoints/application.js +1 -0
- package/dist/entrypoints/runtime.d.ts +1 -0
- package/dist/entrypoints/runtime.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mutations/group/index.d.ts +1 -0
- package/dist/mutations/group/index.js +1 -0
- package/dist/mutations/group/membership.d.ts +19 -0
- package/dist/mutations/group/membership.js +19 -0
- package/dist/mutations/space/access.d.ts +25 -0
- package/dist/mutations/space/access.js +23 -0
- package/dist/mutations/space/default.d.ts +19 -0
- package/dist/mutations/space/default.js +23 -0
- package/dist/mutations/space/index.d.ts +2 -0
- package/dist/mutations/space/index.js +2 -0
- package/dist/mutations/user/index.d.ts +1 -0
- package/dist/mutations/user/index.js +1 -0
- package/dist/mutations/user/repair.d.ts +37 -0
- package/dist/mutations/user/repair.js +83 -0
- package/dist/queries/space/access.d.ts +9 -0
- package/dist/queries/space/access.js +12 -0
- package/dist/queries/space/admin.d.ts +9 -0
- package/dist/queries/space/admin.js +20 -0
- package/dist/queries/space/index.d.ts +3 -0
- package/dist/queries/space/index.js +3 -0
- package/dist/queries/space/member.d.ts +9 -0
- package/dist/queries/space/member.js +17 -0
- package/dist/queries/user/groups.d.ts +9 -0
- package/dist/queries/user/groups.js +17 -0
- package/dist/queries/user/home.d.ts +9 -0
- package/dist/queries/user/home.js +21 -0
- package/dist/queries/user/index.d.ts +5 -0
- package/dist/queries/user/index.js +5 -0
- package/dist/queries/user/owned-spaces.d.ts +9 -0
- package/dist/queries/user/owned-spaces.js +17 -0
- package/dist/queries/user/profile.d.ts +4 -0
- package/dist/queries/user/profile.js +16 -0
- package/dist/queries/user/setup.d.ts +13 -0
- package/dist/queries/user/setup.js +16 -0
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +17 -0
- package/dist/schema/domain/index.d.ts +69 -1640
- package/dist/schema/domain/index.js +3 -2
- package/dist/schema/group/class.d.ts +13 -321
- package/dist/schema/group/class.js +3 -2
- package/dist/schema/group/methods.d.ts +2 -2
- package/dist/schema/group/methods.js +1 -5
- package/dist/schema/index.d.ts +6 -6
- package/dist/schema/space/class.d.ts +10 -78
- package/dist/schema/space/class.js +3 -2
- package/dist/schema/space/methods.d.ts +4 -4
- package/dist/schema/space/methods.js +1 -5
- package/dist/schema/user/class.d.ts +14 -274
- package/dist/schema/user/class.js +5 -5
- package/dist/workflows/group/assign.d.ts +3 -0
- package/dist/workflows/group/assign.js +3 -0
- package/dist/workflows/group/change.d.ts +6 -0
- package/dist/workflows/group/change.js +31 -0
- package/dist/workflows/group/index.d.ts +3 -0
- package/dist/workflows/group/index.js +3 -0
- package/dist/workflows/group/unassign.d.ts +3 -0
- package/dist/workflows/group/unassign.js +3 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +3 -0
- package/dist/workflows/setup/admin.d.ts +3 -0
- package/dist/workflows/setup/admin.js +3 -0
- package/dist/workflows/setup/index.d.ts +5 -0
- package/dist/workflows/setup/index.js +5 -0
- package/dist/workflows/setup/member.d.ts +3 -0
- package/dist/workflows/setup/member.js +3 -0
- package/dist/workflows/setup/plan.d.ts +18 -0
- package/dist/workflows/setup/plan.js +88 -0
- package/dist/workflows/setup/provision.d.ts +5 -0
- package/dist/workflows/setup/provision.js +60 -0
- package/dist/workflows/setup/run.d.ts +7 -0
- package/dist/workflows/setup/run.js +74 -0
- package/dist/workflows/space/access.d.ts +7 -0
- package/dist/workflows/space/access.js +41 -0
- package/dist/workflows/space/add.d.ts +3 -0
- package/dist/workflows/space/add.js +3 -0
- package/dist/workflows/space/default.d.ts +3 -0
- package/dist/workflows/space/default.js +37 -0
- package/dist/workflows/space/index.d.ts +4 -0
- package/dist/workflows/space/index.js +4 -0
- package/dist/workflows/space/remove.d.ts +3 -0
- package/dist/workflows/space/remove.js +3 -0
- package/package.json +27 -11
package/README.md
CHANGED
|
@@ -1,71 +1,92 @@
|
|
|
1
1
|
# Shell Domain
|
|
2
2
|
|
|
3
|
-
Shell is the minimal Kernel-native application plane. Its durable model is
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
nodes.
|
|
3
|
+
Shell is the minimal Kernel-native application plane. Its durable model is the concrete `User`,
|
|
4
|
+
`Group`, and `Space` Node Classes, five relationships, four Policies, two Functions, six Methods,
|
|
5
|
+
four Kernel View declarations, and three deterministic Core nodes.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
Identity registration and Shell business setup are separate boundaries. A real external user first
|
|
8
|
+
creates a local key and self-provisions that key as the exact Shell `User` Class. The CLI journey is:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
astrale identity create alice
|
|
12
|
+
astrale identity register alice \
|
|
13
|
+
--class /:shell.astrale.ai:class.User \
|
|
14
|
+
--props '{"shell.astrale.ai:class.User.property.displayName":"Alice"}' \
|
|
15
|
+
--as <root> -i <instance>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Under the current public contract, Root submits the self-proven registration for each Shell User;
|
|
19
|
+
it does not author that User's Shell memberships or Space facts. Root calls `setupAdmin` only to
|
|
20
|
+
converge the first administrator, using the exact registered issuer and subject returned by
|
|
21
|
+
`identity register`. After that, an authenticated Shell administrator calls `setupMember` or
|
|
22
|
+
`setupAdmin` for subsequent registered Users. Both setup Functions are authorized by
|
|
23
|
+
`maySetupUsers` against membership in the Core Administrators Group.
|
|
24
|
+
They converge the User profile, personal Space, ownership/default facts, Members membership, and—only
|
|
25
|
+
for `setupAdmin`—Administrators membership. They do not create credentials, grant humans dynamic
|
|
26
|
+
`can_*` authority, infer identity from a local CLI name, or replace self-proven registration.
|
|
13
27
|
|
|
14
28
|
The Methods are:
|
|
15
29
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
30
|
+
- User: `resolveHome`;
|
|
31
|
+
- Group: `assignUser`, `unassignUser`; and
|
|
32
|
+
- Space: `addUser`, `removeUser`, `setDefault`.
|
|
19
33
|
|
|
20
|
-
Installation authors the `admin`, `member`, `default-space
|
|
21
|
-
|
|
22
|
-
the atomic identity provisioning as Shell, then callers assign the User explicitly to a Group. Shell
|
|
23
|
-
infers no authority from email, claims, path spelling, or creation order.
|
|
34
|
+
Installation authors the `admin`, `member`, and `default-space` Core nodes. Shell infers no authority
|
|
35
|
+
from email, claims, path spelling, creation order, or a successful handler-local boolean.
|
|
24
36
|
|
|
25
37
|
## Source ownership
|
|
26
38
|
|
|
27
39
|
- `schema/` owns the complete Domain contract, public Method values, Policies, relationships, DSL
|
|
28
40
|
Views, and Core graph;
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
|
|
32
|
-
- `
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
submodules; and
|
|
37
|
-
- root `tests/` owns cross-layer and system evidence, while each semantic submodule owns focused
|
|
38
|
-
evidence in `__tests__/`.
|
|
41
|
+
- `queries/` owns canonical graph observations through the invocation-bound Query executor;
|
|
42
|
+
- `mutations/` owns canonical graph changes and exact preconditions;
|
|
43
|
+
- `actions/` owns the single-operation `User.resolveHome` implementation;
|
|
44
|
+
- `workflows/` owns setup, Group membership, and Space access/default journeys plus
|
|
45
|
+
unknown-outcome observation; and
|
|
46
|
+
- each semantic owner keeps focused evidence in its own `__tests__/` directory, while root tests
|
|
47
|
+
remain reserved for cross-layer or physical evidence.
|
|
39
48
|
|
|
40
49
|
No current responsibility earns a `states/`, `capabilities/`, `integrations/`, `migrations/`,
|
|
41
|
-
`views/`,
|
|
42
|
-
|
|
50
|
+
`views/`, or `ui/` source layer. DSL View declarations remain in Schema; a future frontend
|
|
51
|
+
projection or UI must establish its own concrete responsibility before adding those layers.
|
|
43
52
|
|
|
44
|
-
Shell never re-declares shared metadata properties. `Group` and `Space`
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
interface references awaits the recorded Kernel authoring-default decision; no timestamps are
|
|
48
|
-
authored by Shell.
|
|
53
|
+
Shell never re-declares shared metadata properties. `Group` and `Space` name `K.Named` and
|
|
54
|
+
`K.Descriptable` in their `implements` lists because those Kernel Classes own the admitted
|
|
55
|
+
`name` and `description` properties; no timestamps are authored by Shell.
|
|
49
56
|
|
|
50
57
|
## Execution and evidence
|
|
51
58
|
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
the SDK's canonical Auth, Query, Mutation, Result, Workflow, and Step
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
`application.ts` and `runtime.ts` form the complete remote product implementation. Runtime admission
|
|
60
|
+
is exhaustive: `User.resolveHome` is one Action, and both setup Functions plus all five mutating
|
|
61
|
+
Methods are Workflows. They use the SDK's canonical Auth, Query, Mutation, Result, Workflow, and Step
|
|
62
|
+
contracts. Named Workflow steps describe semantic operations but do not claim process-restart
|
|
63
|
+
durability, exactly-once execution, compensation, or crash recovery.
|
|
57
64
|
|
|
58
65
|
The executable contract lives in Shell's semantic source, public facades, and tests. Product
|
|
59
66
|
decisions, journeys, legacy disposition, dependency gates, and qualification evidence live in
|
|
60
67
|
[`.history/`](.history/). Product Domains intentionally have no `.spec/` tree; Kernel authoring
|
|
61
68
|
values come through semantic `@astrale-os/sdk/*` facades.
|
|
62
69
|
|
|
63
|
-
Domain authors import the schema facade from `@astrale-domains/shell`.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
Domain authors import the schema facade from `@astrale-domains/shell`. Worker deployments import the
|
|
71
|
+
application descriptor from `@astrale-domains/shell/application` and the runtime entrypoint from
|
|
72
|
+
`@astrale-domains/shell/runtime`, so bundling the remote runtime does not traverse the build-only
|
|
73
|
+
schema facade.
|
|
74
|
+
|
|
75
|
+
### Publication boundary
|
|
76
|
+
|
|
77
|
+
`@astrale-domains/shell` is an executable platform package and a deliberate exception to the normal
|
|
78
|
+
Domain publication product. It publishes the remote Shell application and runtime needed by platform
|
|
79
|
+
Worker deployments through the explicit `./application` and `./runtime` subpaths, while its root remains
|
|
80
|
+
Schema-only. Its build therefore compiles the explicit roots in `tsconfig.package.json` and applies
|
|
81
|
+
Shell-owned checks to the emitted platform closure. Tarball qualification then verifies the exact
|
|
82
|
+
exports, isolated entrypoints, absence of source maps, and an install using only public npm
|
|
83
|
+
dependencies.
|
|
84
|
+
|
|
85
|
+
The generic `astrale-domain package` command remains Schema-only and exposes only the Domain root plus
|
|
86
|
+
package metadata. This Shell exception must not be copied to ordinary product Domains or used to
|
|
87
|
+
weaken that SDK contract. Deployment consumers must use the explicit executable subpaths so a linked
|
|
88
|
+
schema import cannot traverse Shell's private application or runtime implementation.
|
|
67
89
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
authentication tokens; standard Domain token exchange is owned by Protocol, Server, and SDK.
|
|
90
|
+
Source, package, and deterministic test qualification remain distinct from installed-instance,
|
|
91
|
+
released-package, browser, and live-credential evidence. Shell does not broker authentication
|
|
92
|
+
tokens; standard Domain token exchange is owned by Protocol, Server, and SDK.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './user/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './user/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resolveHomeAction } from './resolve-home.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resolveHomeAction } from './resolve-home.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { InvocationError } from '@astrale-os/sdk/execution';
|
|
3
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
4
|
+
import { observeHome } from '#queries/user';
|
|
5
|
+
export const resolveHomeAction = defineAction()('User.resolveHome', async ({ domain, query, self }) => {
|
|
6
|
+
const observed = await query(observeHome, Path.id(self).raw);
|
|
7
|
+
if (observed.spaces.length > 1) {
|
|
8
|
+
throw new InvocationError('CONFLICT', 'The Shell User has more than one default Space.', {
|
|
9
|
+
reason: { code: 'MULTIPLE_DEFAULT_SPACES', details: {} },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const home = observed.spaces[0];
|
|
13
|
+
if (home !== undefined && home.class !== domain.classes.Space.key) {
|
|
14
|
+
throw new InvocationError('CONFLICT', 'The Shell User default target is not a Shell Space.', {
|
|
15
|
+
reason: { code: 'DEFAULT_SPACE_CLASS_CONFLICT', details: {} },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return Object.freeze({ userId: self, personalSpaceId: home?.id ?? null });
|
|
19
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Application, type Frontend, type Requirements } from '@astrale-os/sdk/application';
|
|
2
|
+
import runtime from './runtime.js';
|
|
3
|
+
import { ShellSchema } from './schema/index.js';
|
|
4
|
+
export declare const frontend: Frontend<typeof ShellSchema>;
|
|
5
|
+
export declare const application: Application<typeof ShellSchema, typeof runtime, typeof frontend, undefined, Requirements>;
|
|
6
|
+
export default application;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineApplication, defineFrontend, external, requirements, } from '@astrale-os/sdk/application';
|
|
2
|
+
import { K } from '@astrale-os/sdk/schema';
|
|
3
|
+
import runtime from './runtime.js';
|
|
4
|
+
import { ShellSchema } from './schema/index.js';
|
|
5
|
+
export const frontend = defineFrontend({
|
|
6
|
+
schema: ShellSchema,
|
|
7
|
+
source: external('https://app.astrale.ai'),
|
|
8
|
+
routes: {
|
|
9
|
+
group: '/groups',
|
|
10
|
+
home: '/',
|
|
11
|
+
space: '/spaces',
|
|
12
|
+
user: '/users',
|
|
13
|
+
},
|
|
14
|
+
entrypoint: 'home',
|
|
15
|
+
});
|
|
16
|
+
const administration = requirements({
|
|
17
|
+
callables: [
|
|
18
|
+
K.classes.Identity.static.methods.whois,
|
|
19
|
+
K.functions.provision,
|
|
20
|
+
K.functions.query,
|
|
21
|
+
K.functions.mutate,
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
export const application = defineApplication({
|
|
25
|
+
schema: ShellSchema,
|
|
26
|
+
runtime,
|
|
27
|
+
frontend,
|
|
28
|
+
requirements: administration,
|
|
29
|
+
});
|
|
30
|
+
export default application;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { application, default } from '../application.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { application, default } from '../application.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../runtime.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../runtime.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Group, ORIGIN, S, ShellCore, ShellSchema, Space, User, adminGroup, administers_space, belongsToSpace, default_space, defaultSpace, groupAssignUser, groupCore, groupUnassignUser, groupView, homeView, mayAdministerSpace, maySetupUsers, memberGroup, member_of_group, member_of_space, owns_space, ownsSpace, resolveHome, setupAdmin, setupMember, spaceAddUser, spaceCore, spaceRemoveUser, spaceSetDefault, spaceView, userView, type GroupMembershipInput, type GroupMembershipResult, type ResolveHomeResult, type SetDefaultSpaceInput, type SetDefaultSpaceResult, type SetupUserInput, type SetupUserResult, type Shell, type SpaceUserAccess, type SpaceUserAccessInput, type SpaceUserAccessResult, type SpaceUserInput, type SpaceUserResult, } from './schema/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Group, ORIGIN, S, ShellCore, ShellSchema, Space, User, adminGroup, administers_space, belongsToSpace, default_space, defaultSpace, groupAssignUser, groupCore, groupUnassignUser, groupView, homeView, mayAdministerSpace, maySetupUsers, memberGroup, member_of_group, member_of_space, owns_space, ownsSpace, resolveHome, setupAdmin, setupMember, spaceAddUser, spaceCore, spaceRemoveUser, spaceSetDefault, spaceView, userView, } from './schema/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { changeGroupMembership, type GroupMembershipChange, type GroupMembershipChangeResult, } from './membership.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { changeGroupMembership, } from './membership.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NodeId } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST } from '@astrale-os/sdk/query';
|
|
4
|
+
import { type Mutation } from '@astrale-os/sdk/mutation';
|
|
5
|
+
import type { Shell } from '#schema';
|
|
6
|
+
export interface GroupMembershipChange {
|
|
7
|
+
readonly user: string;
|
|
8
|
+
readonly group: NodeId;
|
|
9
|
+
readonly present: boolean;
|
|
10
|
+
readonly guard: QueryPrecondition<QueryNodeAST>;
|
|
11
|
+
}
|
|
12
|
+
export type GroupMembershipChangeResult = {
|
|
13
|
+
readonly ok: true;
|
|
14
|
+
} | {
|
|
15
|
+
readonly ok: false;
|
|
16
|
+
readonly code: string;
|
|
17
|
+
readonly outcomeKnown: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare const changeGroupMembership: Mutation<Shell, GroupMembershipChange, GroupMembershipChangeResult>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
2
|
+
import { defineMutation } from '@astrale-os/sdk/mutation';
|
|
3
|
+
export const changeGroupMembership = defineMutation()((domain) => ({
|
|
4
|
+
id: 'shell.group.membership',
|
|
5
|
+
build(change, mutation) {
|
|
6
|
+
mutation.expect.query(change.guard);
|
|
7
|
+
const edge = {
|
|
8
|
+
class: domain.classes.member_of_group,
|
|
9
|
+
source: Path.parse(change.user),
|
|
10
|
+
target: Path.id(change.group),
|
|
11
|
+
};
|
|
12
|
+
if (change.present)
|
|
13
|
+
mutation.createEdge(edge);
|
|
14
|
+
else
|
|
15
|
+
mutation.deleteEdge(edge);
|
|
16
|
+
},
|
|
17
|
+
project: () => Object.freeze({ ok: true }),
|
|
18
|
+
reject: (failure) => Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown }),
|
|
19
|
+
}));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NodeId } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST } from '@astrale-os/sdk/query';
|
|
4
|
+
import { type Mutation } from '@astrale-os/sdk/mutation';
|
|
5
|
+
import type { Shell } from '#schema';
|
|
6
|
+
export interface SpaceAccessChange {
|
|
7
|
+
readonly user: string;
|
|
8
|
+
readonly space: NodeId;
|
|
9
|
+
readonly addMember: boolean;
|
|
10
|
+
readonly removeMember: boolean;
|
|
11
|
+
readonly addAdmin: boolean;
|
|
12
|
+
readonly removeAdmin: boolean;
|
|
13
|
+
readonly guards: {
|
|
14
|
+
readonly member: QueryPrecondition<QueryNodeAST>;
|
|
15
|
+
readonly admin: QueryPrecondition<QueryNodeAST>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export type SpaceAccessChangeResult = {
|
|
19
|
+
readonly ok: true;
|
|
20
|
+
} | {
|
|
21
|
+
readonly ok: false;
|
|
22
|
+
readonly code: string;
|
|
23
|
+
readonly outcomeKnown: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare const changeSpaceAccess: Mutation<Shell, SpaceAccessChange, SpaceAccessChangeResult>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
2
|
+
import { defineMutation } from '@astrale-os/sdk/mutation';
|
|
3
|
+
export const changeSpaceAccess = defineMutation()((domain) => ({
|
|
4
|
+
id: 'shell.space.access',
|
|
5
|
+
build(change, mutation) {
|
|
6
|
+
mutation.expect.query(change.guards.member);
|
|
7
|
+
mutation.expect.query(change.guards.admin);
|
|
8
|
+
const source = Path.parse(change.user);
|
|
9
|
+
const target = Path.id(change.space);
|
|
10
|
+
const member = { class: domain.classes.member_of_space, source, target };
|
|
11
|
+
const admin = { class: domain.classes.administers_space, source, target };
|
|
12
|
+
if (change.removeMember)
|
|
13
|
+
mutation.deleteEdge(member);
|
|
14
|
+
if (change.removeAdmin)
|
|
15
|
+
mutation.deleteEdge(admin);
|
|
16
|
+
if (change.addMember)
|
|
17
|
+
mutation.createEdge(member);
|
|
18
|
+
if (change.addAdmin)
|
|
19
|
+
mutation.createEdge(admin);
|
|
20
|
+
},
|
|
21
|
+
project: () => Object.freeze({ ok: true }),
|
|
22
|
+
reject: (failure) => Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown }),
|
|
23
|
+
}));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NodeId } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST } from '@astrale-os/sdk/query';
|
|
4
|
+
import { type Mutation } from '@astrale-os/sdk/mutation';
|
|
5
|
+
import type { Shell } from '#schema';
|
|
6
|
+
export interface DefaultSpaceChange {
|
|
7
|
+
readonly user: NodeId;
|
|
8
|
+
readonly previous?: NodeId;
|
|
9
|
+
readonly next: NodeId;
|
|
10
|
+
readonly guard: QueryPrecondition<QueryNodeAST>;
|
|
11
|
+
}
|
|
12
|
+
export type DefaultSpaceChangeResult = {
|
|
13
|
+
readonly ok: true;
|
|
14
|
+
} | {
|
|
15
|
+
readonly ok: false;
|
|
16
|
+
readonly code: string;
|
|
17
|
+
readonly outcomeKnown: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare const changeDefaultSpace: Mutation<Shell, DefaultSpaceChange, DefaultSpaceChangeResult>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
2
|
+
import { defineMutation } from '@astrale-os/sdk/mutation';
|
|
3
|
+
export const changeDefaultSpace = defineMutation()((domain) => ({
|
|
4
|
+
id: 'shell.space.default',
|
|
5
|
+
build(change, mutation) {
|
|
6
|
+
mutation.expect.query(change.guard);
|
|
7
|
+
const source = Path.id(change.user);
|
|
8
|
+
if (change.previous !== undefined) {
|
|
9
|
+
mutation.deleteEdge({
|
|
10
|
+
class: domain.classes.default_space,
|
|
11
|
+
source,
|
|
12
|
+
target: Path.id(change.previous),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
mutation.createEdge({
|
|
16
|
+
class: domain.classes.default_space,
|
|
17
|
+
source,
|
|
18
|
+
target: Path.id(change.next),
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
project: () => Object.freeze({ ok: true }),
|
|
22
|
+
reject: (failure) => Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown }),
|
|
23
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { repairUser, type UserRepair, type UserRepairResult } from './repair.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { repairUser } from './repair.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { NodeId } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST } from '@astrale-os/sdk/query';
|
|
4
|
+
import { type Mutation } from '@astrale-os/sdk/mutation';
|
|
5
|
+
import type { Shell } from '#schema';
|
|
6
|
+
export interface UserRepair {
|
|
7
|
+
readonly userId: NodeId;
|
|
8
|
+
readonly currentDisplayName?: string;
|
|
9
|
+
readonly displayName?: string;
|
|
10
|
+
readonly home: {
|
|
11
|
+
readonly kind: 'existing';
|
|
12
|
+
readonly id: NodeId;
|
|
13
|
+
readonly currentName: string;
|
|
14
|
+
readonly name?: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'create';
|
|
17
|
+
readonly name: string;
|
|
18
|
+
};
|
|
19
|
+
readonly addDefault: boolean;
|
|
20
|
+
readonly addOwnership: boolean;
|
|
21
|
+
readonly addMember: boolean;
|
|
22
|
+
readonly addAdmin: boolean;
|
|
23
|
+
readonly guards: {
|
|
24
|
+
readonly home: QueryPrecondition<QueryNodeAST>;
|
|
25
|
+
readonly ownedSpaces: QueryPrecondition<QueryNodeAST>;
|
|
26
|
+
readonly groups: QueryPrecondition<QueryNodeAST>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export type UserRepairResult = {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly personalSpaceId: NodeId;
|
|
32
|
+
} | {
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly code: string;
|
|
35
|
+
readonly outcomeKnown: boolean;
|
|
36
|
+
};
|
|
37
|
+
export declare const repairUser: Mutation<Shell, UserRepair, UserRepairResult>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { LocalBinding } from '@astrale-os/sdk/graph';
|
|
2
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
3
|
+
import { defineMutation } from '@astrale-os/sdk/mutation';
|
|
4
|
+
const personalSpace = LocalBinding('personalSpace');
|
|
5
|
+
export const repairUser = defineMutation()((domain) => ({
|
|
6
|
+
id: 'shell.user.repair',
|
|
7
|
+
build(repair, mutation) {
|
|
8
|
+
mutation.expect.query(repair.guards.home);
|
|
9
|
+
mutation.expect.query(repair.guards.ownedSpaces);
|
|
10
|
+
mutation.expect.query(repair.guards.groups);
|
|
11
|
+
mutation.expect.node({
|
|
12
|
+
node: Path.id(repair.userId),
|
|
13
|
+
class: domain.classes.User,
|
|
14
|
+
props: repair.currentDisplayName === undefined
|
|
15
|
+
? { absent: ['displayName'] }
|
|
16
|
+
: { equals: { displayName: repair.currentDisplayName } },
|
|
17
|
+
});
|
|
18
|
+
if (repair.displayName !== undefined) {
|
|
19
|
+
mutation.updateNode({
|
|
20
|
+
node: Path.id(repair.userId),
|
|
21
|
+
class: domain.classes.User,
|
|
22
|
+
props: { set: { displayName: repair.displayName } },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const home = repair.home.kind === 'create'
|
|
26
|
+
? mutation.createNode({
|
|
27
|
+
as: personalSpace,
|
|
28
|
+
class: domain.classes.Space,
|
|
29
|
+
props: { name: repair.home.name },
|
|
30
|
+
})
|
|
31
|
+
: Path.id(repair.home.id);
|
|
32
|
+
if (repair.home.kind === 'existing' && repair.home.name !== undefined) {
|
|
33
|
+
mutation.expect.node({
|
|
34
|
+
node: Path.id(repair.home.id),
|
|
35
|
+
class: domain.classes.Space,
|
|
36
|
+
props: { equals: { name: repair.home.currentName } },
|
|
37
|
+
});
|
|
38
|
+
mutation.updateNode({
|
|
39
|
+
node: Path.id(repair.home.id),
|
|
40
|
+
class: domain.classes.Space,
|
|
41
|
+
props: { set: { name: repair.home.name } },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (repair.addOwnership) {
|
|
45
|
+
mutation.createEdge({
|
|
46
|
+
class: domain.classes.owns_space,
|
|
47
|
+
source: Path.id(repair.userId),
|
|
48
|
+
target: home,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (repair.addDefault) {
|
|
52
|
+
mutation.createEdge({
|
|
53
|
+
class: domain.classes.default_space,
|
|
54
|
+
source: Path.id(repair.userId),
|
|
55
|
+
target: home,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (repair.addMember) {
|
|
59
|
+
mutation.createEdge({
|
|
60
|
+
class: domain.classes.member_of_group,
|
|
61
|
+
source: Path.id(repair.userId),
|
|
62
|
+
target: Path.project(domain.core.nodes.member.ref),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (repair.addAdmin) {
|
|
66
|
+
mutation.createEdge({
|
|
67
|
+
class: domain.classes.member_of_group,
|
|
68
|
+
source: Path.id(repair.userId),
|
|
69
|
+
target: Path.project(domain.core.nodes.admin.ref),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
project(result, repair) {
|
|
74
|
+
const personalSpaceId = repair.home.kind === 'existing' ? repair.home.id : result.createdNodes[personalSpace];
|
|
75
|
+
if (personalSpaceId === undefined) {
|
|
76
|
+
throw new TypeError('Shell User repair omitted its created personal Space identity.');
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({ ok: true, personalSpaceId });
|
|
79
|
+
},
|
|
80
|
+
reject(failure) {
|
|
81
|
+
return Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown });
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CompositeQueryDefinition } from '@astrale-os/sdk/query';
|
|
2
|
+
import type { Shell } from '#schema';
|
|
3
|
+
import { type AdminSpacesObservation } from './admin.js';
|
|
4
|
+
import { type MemberSpacesObservation } from './member.js';
|
|
5
|
+
export interface DirectSpaceAccessObservation {
|
|
6
|
+
readonly admin: AdminSpacesObservation;
|
|
7
|
+
readonly member: MemberSpacesObservation;
|
|
8
|
+
}
|
|
9
|
+
export declare const observeDirectSpaceAccess: CompositeQueryDefinition<Shell, string, DirectSpaceAccessObservation>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineCompositeQuery } from '@astrale-os/sdk/query';
|
|
2
|
+
import { observeAdminSpaces } from './admin.js';
|
|
3
|
+
import { observeMemberSpaces } from './member.js';
|
|
4
|
+
export const observeDirectSpaceAccess = defineCompositeQuery()(() => ({
|
|
5
|
+
id: 'shell.space.direct-access',
|
|
6
|
+
compose({ combine, query }, user) {
|
|
7
|
+
return combine({
|
|
8
|
+
admin: query('admin', observeAdminSpaces, user),
|
|
9
|
+
member: query('member', observeMemberSpaces, user),
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Node } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST, SingleQueryDefinition } from '@astrale-os/sdk/query';
|
|
4
|
+
import type { Shell } from '#schema';
|
|
5
|
+
export interface AdminSpacesObservation {
|
|
6
|
+
readonly spaces: readonly Node[];
|
|
7
|
+
readonly guard: QueryPrecondition<QueryNodeAST>;
|
|
8
|
+
}
|
|
9
|
+
export declare const observeAdminSpaces: SingleQueryDefinition<Shell, string, AdminSpacesObservation>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
2
|
+
import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
|
|
3
|
+
export const observeAdminSpaces = defineQuery()((domain) => ({
|
|
4
|
+
id: 'shell.space.admin-access',
|
|
5
|
+
build(user) {
|
|
6
|
+
const source = Query.from({ nodes: [Path.parse(user)] });
|
|
7
|
+
const spaces = source.expand({
|
|
8
|
+
via: [domain.classes.administers_space],
|
|
9
|
+
direction: 'outgoing',
|
|
10
|
+
});
|
|
11
|
+
return spaces.select({ kind: 'nodes', binding: spaces.node, projection: { kind: 'value' } });
|
|
12
|
+
},
|
|
13
|
+
pagination: { maximumPages: 64 },
|
|
14
|
+
project(pages, _user, { ast, complete }) {
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
spaces: queryResult.completeNodeValues(pages, 'direct administered Spaces'),
|
|
17
|
+
guard: Object.freeze({ query: ast, equals: complete }),
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Node } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST, SingleQueryDefinition } from '@astrale-os/sdk/query';
|
|
4
|
+
import type { Shell } from '#schema';
|
|
5
|
+
export interface MemberSpacesObservation {
|
|
6
|
+
readonly spaces: readonly Node[];
|
|
7
|
+
readonly guard: QueryPrecondition<QueryNodeAST>;
|
|
8
|
+
}
|
|
9
|
+
export declare const observeMemberSpaces: SingleQueryDefinition<Shell, string, MemberSpacesObservation>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
2
|
+
import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
|
|
3
|
+
export const observeMemberSpaces = defineQuery()((domain) => ({
|
|
4
|
+
id: 'shell.space.member-access',
|
|
5
|
+
build(user) {
|
|
6
|
+
const source = Query.from({ nodes: [Path.parse(user)] });
|
|
7
|
+
const spaces = source.expand({ via: [domain.classes.member_of_space], direction: 'outgoing' });
|
|
8
|
+
return spaces.select({ kind: 'nodes', binding: spaces.node, projection: { kind: 'value' } });
|
|
9
|
+
},
|
|
10
|
+
pagination: { maximumPages: 64 },
|
|
11
|
+
project(pages, _user, { ast, complete }) {
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
spaces: queryResult.completeNodeValues(pages, 'direct member Spaces'),
|
|
14
|
+
guard: Object.freeze({ query: ast, equals: complete }),
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Node } from '@astrale-os/sdk/graph/node';
|
|
2
|
+
import type { QueryPrecondition } from '@astrale-os/sdk/mutation';
|
|
3
|
+
import type { QueryNodeAST, SingleQueryDefinition } from '@astrale-os/sdk/query';
|
|
4
|
+
import type { Shell } from '#schema';
|
|
5
|
+
export interface UserGroupsObservation {
|
|
6
|
+
readonly groups: readonly Node[];
|
|
7
|
+
readonly guard: QueryPrecondition<QueryNodeAST>;
|
|
8
|
+
}
|
|
9
|
+
export declare const observeUserGroups: SingleQueryDefinition<Shell, string, UserGroupsObservation>;
|