@house-rules/party 0.0.0-stage → 0.1.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.
Files changed (69) hide show
  1. package/AGENTS.md +67 -0
  2. package/LICENSE +21 -0
  3. package/README.md +142 -2
  4. package/dist/adapters/memory/party.d.ts +3 -0
  5. package/dist/adapters/memory/party.js +170 -0
  6. package/dist/adapters/memory/person.d.ts +3 -0
  7. package/dist/adapters/memory/person.js +87 -0
  8. package/dist/adapters/postgres/party.d.ts +4 -0
  9. package/dist/adapters/postgres/party.js +73 -0
  10. package/dist/adapters/postgres/person.d.ts +4 -0
  11. package/dist/adapters/postgres/person.js +63 -0
  12. package/dist/assignment/assignment.d.ts +13 -0
  13. package/dist/assignment/assignment.js +9 -0
  14. package/dist/assignment/holders.d.ts +11 -0
  15. package/dist/assignment/holders.js +7 -0
  16. package/dist/contract.d.ts +9 -0
  17. package/dist/contract.js +9 -0
  18. package/dist/facade.d.ts +22 -0
  19. package/dist/facade.js +35 -0
  20. package/dist/index.d.ts +294 -0
  21. package/dist/index.js +20 -0
  22. package/dist/invitation/invitation.d.ts +36 -0
  23. package/dist/invitation/invitation.js +17 -0
  24. package/dist/invitation/invitations.d.ts +34 -0
  25. package/dist/invitation/invitations.js +70 -0
  26. package/dist/organisation/organisation.d.ts +59 -0
  27. package/dist/organisation/organisation.js +46 -0
  28. package/dist/person/bind-party.d.ts +7 -0
  29. package/dist/person/bind-party.js +42 -0
  30. package/dist/person/operations.d.ts +306 -0
  31. package/dist/person/operations.js +92 -0
  32. package/dist/person/party.d.ts +36 -0
  33. package/dist/person/party.js +55 -0
  34. package/dist/person/permissions.d.ts +6 -0
  35. package/dist/person/permissions.js +5 -0
  36. package/dist/person/person-store.d.ts +21 -0
  37. package/dist/person/person-store.js +3 -0
  38. package/dist/person/person.d.ts +43 -0
  39. package/dist/person/person.js +25 -0
  40. package/dist/person/policy.d.ts +1 -0
  41. package/dist/person/policy.js +6 -0
  42. package/dist/person/principal-operations.d.ts +110 -0
  43. package/dist/person/principal-operations.js +77 -0
  44. package/dist/person/principal.d.ts +126 -0
  45. package/dist/person/principal.js +49 -0
  46. package/dist/person/principals.d.ts +102 -0
  47. package/dist/person/principals.js +73 -0
  48. package/dist/relationship/relationship.d.ts +30 -0
  49. package/dist/relationship/relationship.js +12 -0
  50. package/dist/relationship/removal.d.ts +101 -0
  51. package/dist/relationship/removal.js +22 -0
  52. package/dist/role/access.d.ts +171 -0
  53. package/dist/role/access.js +252 -0
  54. package/dist/role/operations.d.ts +145 -0
  55. package/dist/role/operations.js +215 -0
  56. package/dist/role/people.d.ts +45 -0
  57. package/dist/role/people.js +57 -0
  58. package/dist/role/permission-catalogue.d.ts +2 -0
  59. package/dist/role/permission-catalogue.js +1 -0
  60. package/dist/role/role.d.ts +31 -0
  61. package/dist/role/role.js +12 -0
  62. package/dist/storage.d.ts +38 -0
  63. package/dist/storage.js +4 -0
  64. package/migrations/0001_party_person.sql +5 -0
  65. package/migrations/0002_party_model.sql +27 -0
  66. package/migrations/0003_party_invitation.sql +11 -0
  67. package/migrations/0004_publish_staff.sql +4 -0
  68. package/migrations/0005_principals.sql +18 -0
  69. package/package.json +57 -3
@@ -0,0 +1,92 @@
1
+ import { ActorId, defineOperation, NoInput } from "@house-rules/domain/contract";
2
+ import { Schema } from "effect";
3
+ import { OrganisationRelation, PartyDenied, PartyNotFound, PartyUnavailable, } from "../organisation/organisation.js";
4
+ import { AccessOperations } from "../role/operations.js";
5
+ import { PartyPermissions } from "./permissions.js";
6
+ import { PrincipalOperations } from "./principal-operations.js";
7
+ import { Person, PersonAlreadyExists, PersonCallerDenied, PersonInputInvalid, PersonNotFound, PersonStoreUnavailable, PersonUpdateRefused, } from "./person.js";
8
+ const createPerson = defineOperation("Party.createPerson", {
9
+ description: "Create a person during trusted account setup.",
10
+ input: Schema.Struct({ displayName: Schema.String, id: Schema.optionalKey(ActorId) }),
11
+ output: Person,
12
+ failure: Schema.Union([
13
+ PersonAlreadyExists,
14
+ PersonCallerDenied,
15
+ PersonInputInvalid,
16
+ PersonStoreUnavailable,
17
+ ]),
18
+ permission: "party:create",
19
+ edges: [],
20
+ transactional: true,
21
+ });
22
+ const showPerson = defineOperation("Party.showPerson", {
23
+ description: "Show one person's display name by id.",
24
+ input: Schema.Struct({ id: ActorId }),
25
+ output: Person,
26
+ failure: Schema.Union([PersonNotFound, PersonStoreUnavailable]),
27
+ permission: PartyPermissions.read,
28
+ edges: [],
29
+ readOnly: true,
30
+ });
31
+ const showMe = defineOperation("Party.showMe", {
32
+ description: "Show the signed-in person: their id and display name.",
33
+ input: NoInput,
34
+ output: Person,
35
+ failure: Schema.Union([PersonNotFound, PersonStoreUnavailable]),
36
+ permission: PartyPermissions.read,
37
+ edges: [],
38
+ readOnly: true,
39
+ });
40
+ const renamePerson = defineOperation("Party.renamePerson", {
41
+ description: "Change your own display name.",
42
+ input: Schema.Struct({ id: ActorId, displayName: Schema.String }),
43
+ output: Person,
44
+ failure: Schema.Union([
45
+ PersonInputInvalid,
46
+ PersonNotFound,
47
+ PersonUpdateRefused,
48
+ PersonStoreUnavailable,
49
+ ]),
50
+ permission: PartyPermissions.update,
51
+ edges: [],
52
+ transactional: true,
53
+ });
54
+ const listPeople = defineOperation("Party.listPeople", {
55
+ description: "List everyone for global admins, or only your organisations' staff for scoped admins.",
56
+ input: NoInput,
57
+ output: Schema.Array(Schema.Struct({
58
+ ...Person.fields,
59
+ assignments: Schema.Array(Schema.Struct({
60
+ roleKey: Schema.String,
61
+ roleName: Schema.String,
62
+ organisationId: Schema.NullOr(Schema.String),
63
+ organisationName: Schema.NullOr(Schema.String),
64
+ })),
65
+ organisations: Schema.Array(OrganisationRelation),
66
+ })),
67
+ failure: Schema.Union([PartyDenied, PartyUnavailable]),
68
+ permission: "party:admin",
69
+ readOnly: true,
70
+ });
71
+ const listMembers = defineOperation("Party.listMembers", {
72
+ description: "List an organisation's members and scoped roles only when you administer it.",
73
+ input: Schema.Struct({ organisationId: Schema.String }),
74
+ output: Schema.Array(Schema.Struct({
75
+ ...Person.fields,
76
+ relation: Schema.NullOr(Schema.String),
77
+ roles: Schema.Array(Schema.Struct({ roleKey: Schema.String, roleName: Schema.String })),
78
+ })),
79
+ failure: Schema.Union([PartyNotFound, PartyUnavailable]),
80
+ permission: "party:read",
81
+ readOnly: true,
82
+ });
83
+ export const PartyOperations = {
84
+ ...PrincipalOperations,
85
+ listMembers,
86
+ listPeople,
87
+ createPerson,
88
+ showPerson,
89
+ showMe,
90
+ renamePerson,
91
+ ...AccessOperations,
92
+ };
@@ -0,0 +1,36 @@
1
+ import { ActorId } from "@house-rules/domain";
2
+ import { Effect } from "effect";
3
+ import type { PersonStore } from "../storage.js";
4
+ import { PersonAlreadyExists, PersonInputInvalid, PersonNotFound, PersonUpdateRefused } from "./person.js";
5
+ export declare const makeParty: (store: PersonStore["Service"]) => {
6
+ createPerson: (input: {
7
+ readonly displayName: string;
8
+ readonly id?: ActorId;
9
+ }) => Effect.Effect<{
10
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
11
+ readonly displayName: string;
12
+ readonly createdAt: number;
13
+ }, import("@house-rules/domain").NoOpenUnit | PersonAlreadyExists | PersonInputInvalid | import("./person.js").PersonStoreUnavailable, never>;
14
+ get: (actor: {
15
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
16
+ }, id: string & import("effect/Brand").Brand<"ActorId">) => Effect.Effect<{
17
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
18
+ readonly displayName: string;
19
+ readonly createdAt: number;
20
+ }, PersonNotFound | import("./person.js").PersonStoreUnavailable, never>;
21
+ me: (actor: {
22
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
23
+ }) => Effect.Effect<{
24
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
25
+ readonly displayName: string;
26
+ readonly createdAt: number;
27
+ }, PersonNotFound | import("./person.js").PersonStoreUnavailable, never>;
28
+ rename: (actor: {
29
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
30
+ }, id: string & import("effect/Brand").Brand<"ActorId">, displayName: string) => Effect.Effect<{
31
+ readonly id: string & import("effect/Brand").Brand<"ActorId">;
32
+ readonly displayName: string;
33
+ readonly createdAt: number;
34
+ }, import("@house-rules/domain").NoOpenUnit | PersonInputInvalid | PersonNotFound | import("./person.js").PersonStoreUnavailable | PersonUpdateRefused, never>;
35
+ erase: (id: string & import("effect/Brand").Brand<"ActorId">) => Effect.Effect<void, import("@house-rules/domain").NoOpenUnit | import("./person.js").PersonStoreUnavailable, never>;
36
+ };
@@ -0,0 +1,55 @@
1
+ import { ActorId, UnitOfWork } from "@house-rules/domain";
2
+ import { DateTime, Effect, Random, Schema } from "effect";
3
+ import { PartyPermissions } from "./permissions.js";
4
+ import { DisplayName, PersonAlreadyExists, PersonInputInvalid, PersonNotFound, PersonUpdateRefused, } from "./person.js";
5
+ import { personPolicy } from "./policy.js";
6
+ export const makeParty = (store) => {
7
+ const decodeName = Schema.decodeUnknownEffect(DisplayName);
8
+ const decodeId = Schema.decodeUnknownEffect(ActorId);
9
+ const invalid = () => new PersonInputInvalid({ message: "Invalid person input." });
10
+ const newId = Effect.all(Array.from({ length: 4 }, () => Random.nextIntBetween(0, 0xffff_ffff))).pipe(Effect.map((words) => words.map((word) => word.toString(16).padStart(8, "0")).join("")), Effect.map((hex) => ActorId.make(hex)));
11
+ const relationOf = (actor, id) => (actor.id === id ? "self" : "other");
12
+ const get = Effect.fn("Party.get")(function* (actor, id) {
13
+ if (!personPolicy.allows(relationOf(actor, id), PartyPermissions.read))
14
+ return yield* new PersonNotFound();
15
+ const person = yield* store.find(id);
16
+ if (person === undefined)
17
+ return yield* new PersonNotFound();
18
+ return person;
19
+ });
20
+ return {
21
+ createPerson: Effect.fn("Party.createPerson")(function* (input) {
22
+ yield* UnitOfWork.required;
23
+ const displayName = yield* decodeName(input.displayName).pipe(Effect.mapError(invalid));
24
+ const id = input.id === undefined
25
+ ? yield* newId
26
+ : yield* decodeId(input.id).pipe(Effect.mapError(invalid));
27
+ const person = {
28
+ id,
29
+ displayName,
30
+ createdAt: DateTime.toEpochMillis(yield* DateTime.now),
31
+ };
32
+ if (!(yield* store.insertIfFree(person)))
33
+ return yield* new PersonAlreadyExists();
34
+ return person;
35
+ }),
36
+ get,
37
+ me: Effect.fn("Party.me")(function* (actor) {
38
+ return yield* get(actor, actor.id);
39
+ }),
40
+ rename: Effect.fn("Party.rename")(function* (actor, id, displayName) {
41
+ yield* UnitOfWork.required;
42
+ if (!personPolicy.allows(relationOf(actor, id), PartyPermissions.update))
43
+ return yield* new PersonUpdateRefused();
44
+ const decoded = yield* decodeName(displayName).pipe(Effect.mapError(invalid));
45
+ const person = yield* store.rename(id, decoded);
46
+ if (person === undefined)
47
+ return yield* new PersonNotFound();
48
+ return person;
49
+ }),
50
+ erase: Effect.fn("Party.erase")(function* (id) {
51
+ yield* UnitOfWork.required;
52
+ yield* store.erase(id);
53
+ }),
54
+ };
55
+ };
@@ -0,0 +1,6 @@
1
+ export declare const PartyPermissions: {
2
+ readonly read: "party:read";
3
+ readonly admin: "party:admin";
4
+ readonly update: "party:update";
5
+ };
6
+ export type PartyPermission = (typeof PartyPermissions)[keyof typeof PartyPermissions];
@@ -0,0 +1,5 @@
1
+ export const PartyPermissions = {
2
+ read: "party:read",
3
+ admin: "party:admin",
4
+ update: "party:update",
5
+ };
@@ -0,0 +1,21 @@
1
+ import type { ActorId, NoOpenUnit } from "@house-rules/domain";
2
+ import { Context, type Effect } from "effect";
3
+ import type { Person, PersonStoreUnavailable } from "./person.js";
4
+ import type { Principal } from "./principal.js";
5
+ declare const PersonStore_base: Context.ServiceClass<PersonStore, "@house-rules/party/PersonStore", {
6
+ readonly principal: (id: ActorId) => Effect.Effect<Principal | undefined, PersonStoreUnavailable>;
7
+ readonly principalByHandle: (handle: string) => Effect.Effect<Principal | undefined, PersonStoreUnavailable>;
8
+ readonly principalsByIds: (ids: readonly ActorId[]) => Effect.Effect<readonly Principal[], PersonStoreUnavailable>;
9
+ readonly insertPrincipal: (principal: Principal) => Effect.Effect<boolean, PersonStoreUnavailable | NoOpenUnit>;
10
+ readonly lockPrincipal: (handle: string) => Effect.Effect<Principal | undefined, PersonStoreUnavailable | NoOpenUnit>;
11
+ readonly principalForUpdate: (id: ActorId) => Effect.Effect<Principal | undefined, PersonStoreUnavailable | NoOpenUnit>;
12
+ readonly disable: (id: ActorId, disabledAt: number) => Effect.Effect<Principal | undefined, PersonStoreUnavailable | NoOpenUnit>;
13
+ readonly find: (id: ActorId) => Effect.Effect<Person | undefined, PersonStoreUnavailable>;
14
+ readonly list: (ids: readonly ActorId[] | null) => Effect.Effect<readonly Person[], PersonStoreUnavailable>;
15
+ readonly insertIfFree: (person: Person) => Effect.Effect<boolean, PersonStoreUnavailable | NoOpenUnit>;
16
+ readonly rename: (id: ActorId, displayName: string) => Effect.Effect<Person | undefined, PersonStoreUnavailable | NoOpenUnit>;
17
+ readonly erase: (id: ActorId) => Effect.Effect<void, PersonStoreUnavailable | NoOpenUnit>;
18
+ }>;
19
+ export declare class PersonStore extends PersonStore_base {
20
+ }
21
+ export {};
@@ -0,0 +1,3 @@
1
+ import { Context } from "effect";
2
+ export class PersonStore extends Context.Service()("@house-rules/party/PersonStore") {
3
+ }
@@ -0,0 +1,43 @@
1
+ import { Schema } from "effect";
2
+ export declare const DisplayName: Schema.Trim;
3
+ export type DisplayName = typeof DisplayName.Type;
4
+ export declare const Person: Schema.Struct<{
5
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
6
+ readonly displayName: Schema.Trim;
7
+ readonly createdAt: Schema.Int;
8
+ }>;
9
+ export type Person = typeof Person.Type;
10
+ declare const PersonCallerDenied_base: Schema.Class<PersonCallerDenied, Schema.TaggedStruct<"PersonCallerDenied", {
11
+ readonly kind: Schema.tag<"forbidden">;
12
+ }>, import("effect/Cause").YieldableError>;
13
+ export declare class PersonCallerDenied extends PersonCallerDenied_base {
14
+ }
15
+ declare const PersonNotFound_base: Schema.Class<PersonNotFound, Schema.TaggedStruct<"PersonNotFound", {
16
+ readonly kind: Schema.tag<"not_found">;
17
+ }>, import("effect/Cause").YieldableError>;
18
+ export declare class PersonNotFound extends PersonNotFound_base {
19
+ }
20
+ declare const PersonUpdateRefused_base: Schema.Class<PersonUpdateRefused, Schema.TaggedStruct<"PersonUpdateRefused", {
21
+ readonly kind: Schema.tag<"forbidden">;
22
+ }>, import("effect/Cause").YieldableError>;
23
+ export declare class PersonUpdateRefused extends PersonUpdateRefused_base {
24
+ }
25
+ declare const PersonAlreadyExists_base: Schema.Class<PersonAlreadyExists, Schema.TaggedStruct<"PersonAlreadyExists", {
26
+ readonly kind: Schema.tag<"conflict">;
27
+ }>, import("effect/Cause").YieldableError>;
28
+ export declare class PersonAlreadyExists extends PersonAlreadyExists_base {
29
+ }
30
+ declare const PersonInputInvalid_base: Schema.Class<PersonInputInvalid, Schema.TaggedStruct<"PersonInputInvalid", {
31
+ readonly kind: Schema.tag<"invalid">;
32
+ readonly message: Schema.String;
33
+ }>, import("effect/Cause").YieldableError>;
34
+ export declare class PersonInputInvalid extends PersonInputInvalid_base {
35
+ }
36
+ declare const PersonStoreUnavailable_base: Schema.Class<PersonStoreUnavailable, Schema.TaggedStruct<"PersonStoreUnavailable", {
37
+ readonly kind: Schema.tag<"unavailable">;
38
+ readonly cause: Schema.Defect;
39
+ }>, import("effect/Cause").YieldableError>;
40
+ export declare class PersonStoreUnavailable extends PersonStoreUnavailable_base {
41
+ get message(): string;
42
+ }
43
+ export {};
@@ -0,0 +1,25 @@
1
+ import { ActorId, errorKind } from "@house-rules/domain/contract";
2
+ import { Schema } from "effect";
3
+ export const DisplayName = Schema.Trim.check(Schema.isMinLength(1), Schema.isMaxLength(120));
4
+ export const Person = Schema.Struct({
5
+ id: ActorId,
6
+ displayName: DisplayName,
7
+ createdAt: Schema.Int,
8
+ });
9
+ export class PersonCallerDenied extends Schema.TaggedError()("PersonCallerDenied", { kind: errorKind("forbidden") }) {
10
+ }
11
+ export class PersonNotFound extends Schema.TaggedError()("PersonNotFound", {
12
+ kind: errorKind("not_found"),
13
+ }) {
14
+ }
15
+ export class PersonUpdateRefused extends Schema.TaggedError()("PersonUpdateRefused", { kind: errorKind("forbidden") }) {
16
+ }
17
+ export class PersonAlreadyExists extends Schema.TaggedError()("PersonAlreadyExists", { kind: errorKind("conflict") }) {
18
+ }
19
+ export class PersonInputInvalid extends Schema.TaggedError()("PersonInputInvalid", { kind: errorKind("invalid"), message: Schema.String }) {
20
+ }
21
+ export class PersonStoreUnavailable extends Schema.TaggedError()("PersonStoreUnavailable", { kind: errorKind("unavailable"), cause: Schema.Defect() }) {
22
+ get message() {
23
+ return "People are unavailable. Try again shortly.";
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ export declare const personPolicy: import("@house-rules/domain").PolicyBuilder<"other" | "self", "party:read" | "party:update">;
@@ -0,0 +1,6 @@
1
+ import { definePolicy } from "@house-rules/domain";
2
+ import { PartyPermissions } from "./permissions.js";
3
+ export const personPolicy = definePolicy({
4
+ self: [PartyPermissions.read, PartyPermissions.update],
5
+ other: [PartyPermissions.read],
6
+ });
@@ -0,0 +1,110 @@
1
+ import { Schema } from "effect";
2
+ import { PartyUnavailable } from "../organisation/organisation.js";
3
+ import { PersonAlreadyExists, PersonCallerDenied, PersonInputInvalid, PersonNotFound, PersonStoreUnavailable } from "./person.js";
4
+ import { PrincipalKindRefused, PrincipalDisabledRefused } from "./principal.js";
5
+ export declare const PrincipalOperations: {
6
+ readonly createPrincipal: import("@house-rules/domain/contract").Operation<"Party.createPrincipal", Schema.Struct<{
7
+ readonly id: Schema.optionalKey<Schema.brand<Schema.NonEmptyString, "ActorId">>;
8
+ readonly displayName: Schema.String;
9
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
10
+ readonly handle: Schema.String;
11
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
12
+ }>, Schema.Struct<{
13
+ readonly principal: Schema.Struct<{
14
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
15
+ readonly displayName: Schema.Trim;
16
+ readonly createdAt: Schema.Int;
17
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
18
+ readonly handle: Schema.String;
19
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
20
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
21
+ }>;
22
+ readonly fact: Schema.Struct<{
23
+ readonly kind: Schema.Literal<"principal.created">;
24
+ readonly after: Schema.Struct<{
25
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
26
+ readonly displayName: Schema.Trim;
27
+ readonly createdAt: Schema.Int;
28
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
29
+ readonly handle: Schema.String;
30
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
31
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
32
+ }>;
33
+ }>;
34
+ }>, Schema.Union<readonly [typeof PersonAlreadyExists, typeof PersonCallerDenied, typeof PersonInputInvalid, typeof PersonStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
35
+ readonly disable: import("@house-rules/domain/contract").Operation<"Party.disable", Schema.Struct<{
36
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
37
+ }>, Schema.Struct<{
38
+ readonly principal: Schema.Struct<{
39
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
40
+ readonly displayName: Schema.Trim;
41
+ readonly createdAt: Schema.Int;
42
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
43
+ readonly handle: Schema.String;
44
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
45
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
46
+ }>;
47
+ readonly fact: Schema.NullOr<Schema.Struct<{
48
+ readonly kind: Schema.Literal<"principal.disabled">;
49
+ readonly before: Schema.Struct<{
50
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
51
+ readonly displayName: Schema.Trim;
52
+ readonly createdAt: Schema.Int;
53
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
54
+ readonly handle: Schema.String;
55
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
56
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
57
+ }>;
58
+ readonly after: Schema.Struct<{
59
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
60
+ readonly displayName: Schema.Trim;
61
+ readonly createdAt: Schema.Int;
62
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
63
+ readonly handle: Schema.String;
64
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
65
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
66
+ }>;
67
+ }>>;
68
+ }>, Schema.Union<readonly [typeof PersonNotFound, typeof PersonCallerDenied, typeof PersonStoreUnavailable]>, NoInfer<readonly []>, false, true, true, true>;
69
+ readonly lockActive: import("@house-rules/domain/contract").Operation<"Party.lockActive", Schema.Struct<{
70
+ readonly handle: Schema.String;
71
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
72
+ }>, Schema.Struct<{
73
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
74
+ readonly displayName: Schema.Trim;
75
+ readonly createdAt: Schema.Int;
76
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
77
+ readonly handle: Schema.String;
78
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
79
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
80
+ }>, Schema.Union<readonly [typeof PersonNotFound, typeof PrincipalKindRefused, typeof PrincipalDisabledRefused, typeof PersonStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false>;
81
+ readonly principalsByIds: import("@house-rules/domain/contract").Operation<"Party.principalsByIds", Schema.Struct<{
82
+ readonly ids: Schema.$Array<Schema.brand<Schema.NonEmptyString, "ActorId">>;
83
+ }>, Schema.$Array<Schema.Struct<{
84
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
85
+ readonly displayName: Schema.Trim;
86
+ readonly createdAt: Schema.Int;
87
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
88
+ readonly handle: Schema.String;
89
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
90
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
91
+ }>>, typeof PersonStoreUnavailable, NoInfer<readonly []>, true, false, false, false>;
92
+ readonly findPrincipal: import("@house-rules/domain/contract").Operation<"Party.findPrincipal", Schema.Struct<{
93
+ readonly handle: Schema.String;
94
+ }>, Schema.Struct<{
95
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
96
+ readonly displayName: Schema.Trim;
97
+ readonly createdAt: Schema.Int;
98
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
99
+ readonly handle: Schema.String;
100
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
101
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
102
+ }>, Schema.Union<readonly [typeof PersonNotFound, typeof PersonStoreUnavailable]>, NoInfer<readonly []>, true, false, false, false>;
103
+ readonly holdersOf: import("@house-rules/domain/contract").Operation<"Party.holdersOf", Schema.Struct<{
104
+ readonly role: Schema.NonEmptyString;
105
+ readonly scope: Schema.optionalKey<Schema.NullOr<Schema.NonEmptyString>>;
106
+ }>, Schema.Struct<{
107
+ readonly ids: Schema.$Array<Schema.brand<Schema.NonEmptyString, "ActorId">>;
108
+ readonly truncated: Schema.Boolean;
109
+ }>, typeof PartyUnavailable, NoInfer<readonly []>, true, false, false, false>;
110
+ };
@@ -0,0 +1,77 @@
1
+ import { ActorId, defineOperation } from "@house-rules/domain/contract";
2
+ import { Schema } from "effect";
3
+ import { PartyUnavailable } from "../organisation/organisation.js";
4
+ import { RoleKey } from "../role/role.js";
5
+ import { PersonAlreadyExists, PersonCallerDenied, PersonInputInvalid, PersonNotFound, PersonStoreUnavailable, } from "./person.js";
6
+ import { CreatePrincipal, CreatedPrincipal, DisabledPrincipal, Principal, PrincipalKind, PrincipalKindRefused, PrincipalDisabledRefused, } from "./principal.js";
7
+ const createPrincipal = defineOperation("Party.createPrincipal", {
8
+ description: "Create a person or agent principal during trusted provisioning.",
9
+ input: CreatePrincipal,
10
+ output: CreatedPrincipal,
11
+ failure: Schema.Union([
12
+ PersonAlreadyExists,
13
+ PersonCallerDenied,
14
+ PersonInputInvalid,
15
+ PersonStoreUnavailable,
16
+ ]),
17
+ permission: "party:create",
18
+ transactional: true,
19
+ });
20
+ const disable = defineOperation("Party.disable", {
21
+ description: "Permanently disable a principal during trusted provisioning.",
22
+ input: Schema.Struct({ id: ActorId }),
23
+ output: DisabledPrincipal,
24
+ failure: Schema.Union([PersonNotFound, PersonCallerDenied, PersonStoreUnavailable]),
25
+ permission: "party:update",
26
+ transactional: true,
27
+ destructive: true,
28
+ needsApproval: true,
29
+ });
30
+ const lockActive = defineOperation("Party.lockActive", {
31
+ description: "Lock an active principal of the requested kind until this unit ends.",
32
+ input: Schema.Struct({ handle: Schema.String, kind: PrincipalKind }),
33
+ output: Principal,
34
+ failure: Schema.Union([
35
+ PersonNotFound,
36
+ PrincipalKindRefused,
37
+ PrincipalDisabledRefused,
38
+ PersonStoreUnavailable,
39
+ ]),
40
+ permission: "party:read",
41
+ transactional: true,
42
+ });
43
+ const principalsByIds = defineOperation("Party.principalsByIds", {
44
+ description: "Read principal names and standing for a person or system caller.",
45
+ input: Schema.Struct({ ids: Schema.Array(ActorId) }),
46
+ output: Schema.Array(Principal),
47
+ failure: PersonStoreUnavailable,
48
+ permission: "party:read",
49
+ readOnly: true,
50
+ });
51
+ const findPrincipal = defineOperation("Party.findPrincipal", {
52
+ description: "Find a principal by its exact handle, including disabled principals.",
53
+ input: Schema.Struct({ handle: Schema.String }),
54
+ output: Principal,
55
+ failure: Schema.Union([PersonNotFound, PersonStoreUnavailable]),
56
+ permission: "party:read",
57
+ readOnly: true,
58
+ });
59
+ const holdersOf = defineOperation("Party.holdersOf", {
60
+ description: "Read bounded principal ids assigned a role in one exact scope, global when omitted.",
61
+ input: Schema.Struct({
62
+ role: RoleKey,
63
+ scope: Schema.optionalKey(Schema.NullOr(Schema.NonEmptyString)),
64
+ }),
65
+ output: Schema.Struct({ ids: Schema.Array(ActorId), truncated: Schema.Boolean }),
66
+ failure: PartyUnavailable,
67
+ permission: "party:read",
68
+ readOnly: true,
69
+ });
70
+ export const PrincipalOperations = {
71
+ createPrincipal,
72
+ disable,
73
+ lockActive,
74
+ principalsByIds,
75
+ findPrincipal,
76
+ holdersOf,
77
+ };
@@ -0,0 +1,126 @@
1
+ import { Schema } from "effect";
2
+ import { Person } from "./person.js";
3
+ export declare const PrincipalKind: Schema.Literals<readonly ["person", "agent"]>;
4
+ export type PrincipalKind = typeof PrincipalKind.Type;
5
+ export declare const PrincipalHandle: Schema.String;
6
+ export declare const Principal: Schema.Struct<{
7
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
8
+ readonly displayName: Schema.Trim;
9
+ readonly createdAt: Schema.Int;
10
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
11
+ readonly handle: Schema.String;
12
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
13
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
14
+ }>;
15
+ export type Principal = typeof Principal.Type;
16
+ export declare const CreatePrincipal: Schema.Struct<{
17
+ readonly id: Schema.optionalKey<Schema.brand<Schema.NonEmptyString, "ActorId">>;
18
+ readonly displayName: Schema.String;
19
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
20
+ readonly handle: Schema.String;
21
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
22
+ }>;
23
+ export type CreatePrincipal = typeof CreatePrincipal.Type;
24
+ export declare const PrincipalCreated: Schema.Struct<{
25
+ readonly kind: Schema.Literal<"principal.created">;
26
+ readonly after: Schema.Struct<{
27
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
28
+ readonly displayName: Schema.Trim;
29
+ readonly createdAt: Schema.Int;
30
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
31
+ readonly handle: Schema.String;
32
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
33
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
34
+ }>;
35
+ }>;
36
+ export declare const PrincipalDisabled: Schema.Struct<{
37
+ readonly kind: Schema.Literal<"principal.disabled">;
38
+ readonly before: Schema.Struct<{
39
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
40
+ readonly displayName: Schema.Trim;
41
+ readonly createdAt: Schema.Int;
42
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
43
+ readonly handle: Schema.String;
44
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
45
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
46
+ }>;
47
+ readonly after: Schema.Struct<{
48
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
49
+ readonly displayName: Schema.Trim;
50
+ readonly createdAt: Schema.Int;
51
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
52
+ readonly handle: Schema.String;
53
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
54
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
55
+ }>;
56
+ }>;
57
+ export declare const CreatedPrincipal: Schema.Struct<{
58
+ readonly principal: Schema.Struct<{
59
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
60
+ readonly displayName: Schema.Trim;
61
+ readonly createdAt: Schema.Int;
62
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
63
+ readonly handle: Schema.String;
64
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
65
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
66
+ }>;
67
+ readonly fact: Schema.Struct<{
68
+ readonly kind: Schema.Literal<"principal.created">;
69
+ readonly after: Schema.Struct<{
70
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
71
+ readonly displayName: Schema.Trim;
72
+ readonly createdAt: Schema.Int;
73
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
74
+ readonly handle: Schema.String;
75
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
76
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
77
+ }>;
78
+ }>;
79
+ }>;
80
+ export type CreatedPrincipal = typeof CreatedPrincipal.Type;
81
+ export declare const DisabledPrincipal: Schema.Struct<{
82
+ readonly principal: Schema.Struct<{
83
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
84
+ readonly displayName: Schema.Trim;
85
+ readonly createdAt: Schema.Int;
86
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
87
+ readonly handle: Schema.String;
88
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
89
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
90
+ }>;
91
+ readonly fact: Schema.NullOr<Schema.Struct<{
92
+ readonly kind: Schema.Literal<"principal.disabled">;
93
+ readonly before: Schema.Struct<{
94
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
95
+ readonly displayName: Schema.Trim;
96
+ readonly createdAt: Schema.Int;
97
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
98
+ readonly handle: Schema.String;
99
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
100
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
101
+ }>;
102
+ readonly after: Schema.Struct<{
103
+ readonly id: Schema.brand<Schema.NonEmptyString, "ActorId">;
104
+ readonly displayName: Schema.Trim;
105
+ readonly createdAt: Schema.Int;
106
+ readonly kind: Schema.Literals<readonly ["person", "agent"]>;
107
+ readonly handle: Schema.String;
108
+ readonly createdBy: Schema.NullOr<Schema.brand<Schema.NonEmptyString, "ActorId">>;
109
+ readonly disabledAt: Schema.NullOr<Schema.Int>;
110
+ }>;
111
+ }>>;
112
+ }>;
113
+ export type DisabledPrincipal = typeof DisabledPrincipal.Type;
114
+ declare const PrincipalDisabledRefused_base: Schema.Class<PrincipalDisabledRefused, Schema.TaggedStruct<"PrincipalDisabledRefused", {
115
+ readonly kind: Schema.tag<"forbidden">;
116
+ }>, import("effect/Cause").YieldableError>;
117
+ export declare class PrincipalDisabledRefused extends PrincipalDisabledRefused_base {
118
+ }
119
+ declare const PrincipalKindRefused_base: Schema.Class<PrincipalKindRefused, Schema.TaggedStruct<"PrincipalKindRefused", {
120
+ readonly kind: Schema.tag<"forbidden">;
121
+ }>, import("effect/Cause").YieldableError>;
122
+ export declare class PrincipalKindRefused extends PrincipalKindRefused_base {
123
+ }
124
+ export declare const legacyPrincipal: (person: Person) => Principal;
125
+ export declare const personOf: ({ id, displayName, createdAt }: Person) => Person;
126
+ export {};