@haneinfo/assignment 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 (60) hide show
  1. package/LICENSE +14 -0
  2. package/agent-guide.md +95 -0
  3. package/dist/assignment.controller.d.ts +53 -0
  4. package/dist/assignment.controller.d.ts.map +1 -0
  5. package/dist/assignment.controller.js +96 -0
  6. package/dist/assignment.controller.js.map +1 -0
  7. package/dist/assignment.module.d.ts +10 -0
  8. package/dist/assignment.module.d.ts.map +1 -0
  9. package/dist/assignment.module.js +58 -0
  10. package/dist/assignment.module.js.map +1 -0
  11. package/dist/assignment.service.d.ts +92 -0
  12. package/dist/assignment.service.d.ts.map +1 -0
  13. package/dist/assignment.service.js +267 -0
  14. package/dist/assignment.service.js.map +1 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +29 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/kind.d.ts +8 -0
  20. package/dist/kind.d.ts.map +1 -0
  21. package/dist/kind.js +37 -0
  22. package/dist/kind.js.map +1 -0
  23. package/dist/mcp-tools.d.ts +16 -0
  24. package/dist/mcp-tools.d.ts.map +1 -0
  25. package/dist/mcp-tools.js +79 -0
  26. package/dist/mcp-tools.js.map +1 -0
  27. package/dist/plugin.d.ts +10 -0
  28. package/dist/plugin.d.ts.map +1 -0
  29. package/dist/plugin.js +85 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/dist/prisma-store.d.ts +7 -0
  32. package/dist/prisma-store.d.ts.map +1 -0
  33. package/dist/prisma-store.js +67 -0
  34. package/dist/prisma-store.js.map +1 -0
  35. package/dist/semantics.d.ts +9 -0
  36. package/dist/semantics.d.ts.map +1 -0
  37. package/dist/semantics.js +80 -0
  38. package/dist/semantics.js.map +1 -0
  39. package/dist/store.d.ts +52 -0
  40. package/dist/store.d.ts.map +1 -0
  41. package/dist/store.js +63 -0
  42. package/dist/store.js.map +1 -0
  43. package/dist/testing.d.ts +19 -0
  44. package/dist/testing.d.ts.map +1 -0
  45. package/dist/testing.js +23 -0
  46. package/dist/testing.js.map +1 -0
  47. package/dist/types.d.ts +24 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +3 -0
  50. package/dist/types.js.map +1 -0
  51. package/examples/definitions/equipment-loan/equipment-manager-approval.assignment.json +21 -0
  52. package/haneinfo.plugin.json +70 -0
  53. package/package.json +81 -3
  54. package/plugin.d.ts +2 -0
  55. package/plugin.js +4 -0
  56. package/prisma/assignment.prisma +32 -0
  57. package/schemas/assignment.spec.json +104 -0
  58. package/testing.d.ts +2 -0
  59. package/testing.js +2 -0
  60. package/README.md +0 -3
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaAssignmentStore = void 0;
4
+ class PrismaTx {
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async saveBindings(bindings) {
9
+ if (bindings.length === 0)
10
+ return;
11
+ await this.db.assignmentBinding.createMany({
12
+ data: bindings.map((binding) => ({
13
+ ...binding,
14
+ definition: binding.definition,
15
+ })),
16
+ });
17
+ }
18
+ async bindings(instanceId) {
19
+ const rows = await this.db.assignmentBinding.findMany({ where: { instanceId } });
20
+ return rows.map((row) => ({
21
+ ...row,
22
+ definition: row.definition,
23
+ }));
24
+ }
25
+ async createTasks(tasks) {
26
+ const created = [];
27
+ for (const task of tasks)
28
+ created.push(await this.db.assignmentTask.create({ data: task }));
29
+ return created;
30
+ }
31
+ task(id) {
32
+ return this.db.assignmentTask.findUnique({ where: { id } });
33
+ }
34
+ tasks(filter) {
35
+ return this.db.assignmentTask.findMany({
36
+ where: Object.fromEntries(Object.entries(filter).filter(([, value]) => value !== undefined)),
37
+ orderBy: [{ createdAt: 'desc' }],
38
+ take: 500,
39
+ });
40
+ }
41
+ async updateTask(id, expectedStatus, patch) {
42
+ const { count } = await this.db.assignmentTask.updateMany({
43
+ where: { id, status: expectedStatus },
44
+ data: patch,
45
+ });
46
+ return count > 0;
47
+ }
48
+ async lastRound(instanceId, assignmentCode) {
49
+ const result = await this.db.assignmentTask.aggregate({
50
+ where: { instanceId, assignmentCode },
51
+ _max: { round: true },
52
+ });
53
+ return result._max.round ?? 0;
54
+ }
55
+ }
56
+ class PrismaAssignmentStore {
57
+ constructor(prisma) {
58
+ this.prisma = prisma;
59
+ }
60
+ run(tx, work) {
61
+ if (tx !== undefined)
62
+ return work(new PrismaTx(tx), tx);
63
+ return this.prisma.$transaction((client) => work(new PrismaTx(client), client));
64
+ }
65
+ }
66
+ exports.PrismaAssignmentStore = PrismaAssignmentStore;
67
+ //# sourceMappingURL=prisma-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-store.js","sourceRoot":"","sources":["../src/prisma-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Semantic rules of an Assignment. It hangs off a StateMachine, so most rules look into it through
3
+ * the validation context: the state must exist and not be FINAL, every response's transition must
4
+ * leave that state. A FORM_FIELD_USER must name a `user_select` field of the machine's subject Form.
5
+ */
6
+ import { type DefinitionDiagnostic, type DefinitionValidationContext } from '@haneinfo/plugin-api';
7
+ import type { AssignmentSpec } from './types';
8
+ export declare function validateAssignmentSpec(spec: AssignmentSpec, context?: DefinitionValidationContext): DefinitionDiagnostic[];
9
+ //# sourceMappingURL=semantics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantics.d.ts","sourceRoot":"","sources":["../src/semantics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAIjC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAuB9C,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,2BAA2B,GACpC,oBAAoB,EAAE,CAoIxB"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * Semantic rules of an Assignment. It hangs off a StateMachine, so most rules look into it through
4
+ * the validation context: the state must exist and not be FINAL, every response's transition must
5
+ * leave that state. A FORM_FIELD_USER must name a `user_select` field of the machine's subject Form.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.validateAssignmentSpec = validateAssignmentSpec;
9
+ const plugin_api_1 = require("@haneinfo/plugin-api");
10
+ function formFieldTypes(fields, out = new Map()) {
11
+ for (const field of fields) {
12
+ out.set(field.id ?? field.key, field.type);
13
+ if (field.children)
14
+ formFieldTypes(field.children, out);
15
+ }
16
+ return out;
17
+ }
18
+ function validateAssignmentSpec(spec, context) {
19
+ const diagnostics = [];
20
+ const add = (code, message, segments, suggestion, severity = 'ERROR') => diagnostics.push((0, plugin_api_1.definitionDiagnostic)(code, message, (0, plugin_api_1.toJsonPointer)(['spec', ...segments]), {
21
+ suggestion,
22
+ severity,
23
+ }));
24
+ const keys = new Set();
25
+ spec.responses.forEach((response, index) => {
26
+ if (keys.has(response.key))
27
+ add('DUPLICATE_RESPONSE_KEY', `response key "${response.key}" is used twice`, ['responses', index, 'key'], 'Give every response a unique key');
28
+ keys.add(response.key);
29
+ if (response.decisive && spec.policy !== 'JOINT') {
30
+ add('DECISIVE_NOT_JOINT', '"decisive" only applies to the JOINT policy', ['responses', index, 'decisive'], 'Remove it; with SINGLE and ANY_ONE every response decides');
31
+ }
32
+ });
33
+ if (spec.policy === 'JOINT' &&
34
+ spec.responses.filter((response) => !response.decisive).length !== 1) {
35
+ add('JOINT_NEEDS_ONE_AGREEMENT', 'JOINT needs exactly one non-decisive response — the one everybody must give', ['responses'], 'Mark the others (e.g. reject) as "decisive": true');
36
+ }
37
+ if (spec.policy === 'SINGLE' &&
38
+ (spec.assignees.length > 1 ||
39
+ spec.assignees.some((a) => a.type === 'ROLE' || (a.type === 'SPECIFIC_USER' && a.userIds.length > 1)))) {
40
+ add('SINGLE_MAY_RESOLVE_MANY', 'SINGLE expects one assignee, but these resolvers can produce several', ['assignees'], 'Use ANY_ONE when any of them may respond', 'WARNING');
41
+ }
42
+ if (spec.dueIn !== undefined && ((0, plugin_api_1.parseIsoDuration)(spec.dueIn) ?? 0) <= 0) {
43
+ add('INVALID_DURATION', `"${spec.dueIn}" is not a positive ISO 8601 duration`, ['dueIn'], 'Use for example "P2D" or "PT4H"; months and years are not supported');
44
+ }
45
+ const machine = context?.resolve(spec.stateMachine)?.spec;
46
+ if (!machine)
47
+ return diagnostics; // the registry reports the missing reference itself
48
+ const state = machine.states.find((candidate) => candidate.key === spec.onEnter);
49
+ if (!state) {
50
+ add('UNKNOWN_STATE', `"${spec.onEnter}" is not a state of StateMachine/${spec.stateMachine.code}`, ['onEnter'], `Use one of: ${machine.states.map((s) => s.key).join(', ')}`);
51
+ }
52
+ else if (state.type === 'FINAL') {
53
+ add('ASSIGNMENT_ON_FINAL_STATE', `"${spec.onEnter}" is FINAL, so nobody could act on the task`, ['onEnter'], 'Open the assignment on a state that has transitions out');
54
+ }
55
+ spec.responses.forEach((response, index) => {
56
+ const transition = machine.transitions.find((candidate) => candidate.key === response.transition);
57
+ if (!transition) {
58
+ add('UNKNOWN_TRANSITION', `"${response.transition}" is not a transition of StateMachine/${spec.stateMachine.code}`, ['responses', index, 'transition'], `Use one of: ${machine.transitions.map((t) => t.key).join(', ')}`);
59
+ }
60
+ else if (!transition.from.includes(spec.onEnter)) {
61
+ add('TRANSITION_NOT_FROM_STATE', `"${response.transition}" does not leave "${spec.onEnter}"`, ['responses', index, 'transition'], `Use a transition whose "from" contains "${spec.onEnter}"`);
62
+ }
63
+ });
64
+ const subject = machine.subject ? context?.resolve(machine.subject) : undefined;
65
+ const fieldTypes = subject
66
+ ? formFieldTypes(subject.spec.fields ?? [])
67
+ : undefined;
68
+ spec.assignees.forEach((assignee, index) => {
69
+ if (assignee.type !== 'FORM_FIELD_USER')
70
+ return;
71
+ if (!fieldTypes) {
72
+ add('NO_SUBJECT_FORM', 'FORM_FIELD_USER needs the state machine to have a subject Form', ['assignees', index], 'Give the state machine a subject, or use another resolver');
73
+ }
74
+ else if (fieldTypes.get(assignee.fieldId) !== 'user_select') {
75
+ add('NOT_A_USER_FIELD', `"${assignee.fieldId}" is not a user_select field of the subject Form`, ['assignees', index, 'fieldId'], 'Reference a user_select field by id');
76
+ }
77
+ });
78
+ return diagnostics;
79
+ }
80
+ //# sourceMappingURL=semantics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantics.js","sourceRoot":"","sources":["../src/semantics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /** Persistence of the Assignment definitions an instance pinned, and of the tasks people respond to. */
2
+ import type { DefinitionEnvelope } from '@haneinfo/plugin-api';
3
+ import type { AssignmentSpec } from './types';
4
+ export declare const ASSIGNMENT_STORE: unique symbol;
5
+ export interface BindingRecord {
6
+ instanceId: string;
7
+ assignmentCode: string;
8
+ version: number;
9
+ definition: DefinitionEnvelope<AssignmentSpec>;
10
+ }
11
+ export type TaskStatus = 'OPEN' | 'RESPONDED' | 'CLOSED' | 'CANCELLED';
12
+ export interface TaskRecord {
13
+ id: string;
14
+ instanceId: string;
15
+ assignmentCode: string;
16
+ assignmentVersion: number;
17
+ /** Increments each time the instance re-enters the state and the assignment opens again. */
18
+ round: number;
19
+ assigneeId: string;
20
+ status: TaskStatus;
21
+ title: string;
22
+ response: string | null;
23
+ comment: string | null;
24
+ dueAt: Date | null;
25
+ respondedAt: Date | null;
26
+ createdAt: Date;
27
+ }
28
+ export interface AssignmentStoreTx {
29
+ saveBindings(bindings: BindingRecord[]): Promise<void>;
30
+ bindings(instanceId: string): Promise<BindingRecord[]>;
31
+ createTasks(tasks: Omit<TaskRecord, 'id'>[]): Promise<TaskRecord[]>;
32
+ task(id: string): Promise<TaskRecord | null>;
33
+ tasks(filter: {
34
+ instanceId?: string;
35
+ assignmentCode?: string;
36
+ round?: number;
37
+ assigneeId?: string;
38
+ status?: TaskStatus;
39
+ }): Promise<TaskRecord[]>;
40
+ /** Compare-and-set on status; returns false when it changed meanwhile. */
41
+ updateTask(id: string, expectedStatus: TaskStatus, patch: Partial<Pick<TaskRecord, 'status' | 'response' | 'comment' | 'respondedAt'>>): Promise<boolean>;
42
+ lastRound(instanceId: string, assignmentCode: string): Promise<number>;
43
+ }
44
+ export interface AssignmentStore {
45
+ run<T>(tx: unknown, work: (store: AssignmentStoreTx, tx: unknown) => Promise<T>): Promise<T>;
46
+ }
47
+ /** For tests. A transaction it opens itself rolls back on error. */
48
+ export declare class InMemoryAssignmentStore implements AssignmentStore {
49
+ private state;
50
+ run<T>(tx: unknown, work: (store: AssignmentStoreTx, tx: unknown) => Promise<T>): Promise<T>;
51
+ }
52
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,wGAAwG;AAExG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,eAAO,MAAM,gBAAgB,eAA0C,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;CAChD;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEvE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACvD,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC7C,KAAK,CAAC,MAAM,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,UAAU,CAAC;KACrB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,CACR,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,UAAU,EAC1B,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC,CAAC,GAClF,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9F;AA+DD,oEAAoE;AACpE,qBAAa,uBAAwB,YAAW,eAAe;IAC7D,OAAO,CAAC,KAAK,CAAuD;IAE9D,GAAG,CAAC,CAAC,EACT,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1D,OAAO,CAAC,CAAC,CAAC;CAUd"}
package/dist/store.js ADDED
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /** Persistence of the Assignment definitions an instance pinned, and of the tasks people respond to. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.InMemoryAssignmentStore = exports.ASSIGNMENT_STORE = void 0;
5
+ exports.ASSIGNMENT_STORE = Symbol.for('haneinfo.assignment.store');
6
+ const copy = (value) => structuredClone(value);
7
+ class MemoryTx {
8
+ constructor(state) {
9
+ this.state = state;
10
+ }
11
+ async saveBindings(bindings) {
12
+ this.state.bindings.push(...copy(bindings));
13
+ }
14
+ async bindings(instanceId) {
15
+ return this.state.bindings.filter((b) => b.instanceId === instanceId).map(copy);
16
+ }
17
+ async createTasks(tasks) {
18
+ const created = tasks.map((task) => ({ ...copy(task), id: `task_${this.state.nextId++}` }));
19
+ this.state.tasks.push(...created);
20
+ return created.map(copy);
21
+ }
22
+ async task(id) {
23
+ const found = this.state.tasks.find((t) => t.id === id);
24
+ return found ? copy(found) : null;
25
+ }
26
+ async tasks(filter) {
27
+ return this.state.tasks
28
+ .filter((t) => Object.entries(filter).every(([key, value]) => value === undefined || t[key] === value))
29
+ .map(copy);
30
+ }
31
+ async updateTask(id, expectedStatus, patch) {
32
+ const found = this.state.tasks.find((t) => t.id === id);
33
+ if (!found || found.status !== expectedStatus)
34
+ return false;
35
+ Object.assign(found, copy(patch));
36
+ return true;
37
+ }
38
+ async lastRound(instanceId, assignmentCode) {
39
+ return Math.max(0, ...this.state.tasks
40
+ .filter((t) => t.instanceId === instanceId && t.assignmentCode === assignmentCode)
41
+ .map((t) => t.round));
42
+ }
43
+ }
44
+ /** For tests. A transaction it opens itself rolls back on error. */
45
+ class InMemoryAssignmentStore {
46
+ constructor() {
47
+ this.state = { bindings: [], tasks: [], nextId: 1 };
48
+ }
49
+ async run(tx, work) {
50
+ if (tx !== undefined)
51
+ return work(new MemoryTx(this.state), tx);
52
+ const snapshot = structuredClone(this.state);
53
+ try {
54
+ return await work(new MemoryTx(this.state), { inMemory: true });
55
+ }
56
+ catch (error) {
57
+ this.state = snapshot;
58
+ throw error;
59
+ }
60
+ }
61
+ }
62
+ exports.InMemoryAssignmentStore = InMemoryAssignmentStore;
63
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /** `@haneinfo/assignment/testing` — the assignment runtime on in-memory stores, attached to a test state machine. */
2
+ import type { DefinitionKindRegistration, NotificationInboxPort, RbacPolicyPort } from '@haneinfo/plugin-api';
3
+ import { AssignmentService } from './assignment.service';
4
+ import { InMemoryAssignmentStore } from './store';
5
+ export { InMemoryAssignmentStore } from './store';
6
+ export declare function createTestAssignmentRuntime(options?: {
7
+ kinds?: DefinitionKindRegistration[];
8
+ rbac?: RbacPolicyPort;
9
+ clock?: () => Date;
10
+ notifications?: NotificationInboxPort;
11
+ }): {
12
+ assignments: AssignmentService;
13
+ assignmentStore: InMemoryAssignmentStore;
14
+ runtime: import("@haneinfo/state-machine/dist/runtime.service").StateMachineRuntime;
15
+ store: import("@haneinfo/state-machine/testing").InMemoryStateMachineStore;
16
+ registry: import("@haneinfo/definition-registry/dist/definition-registry.service").DefinitionRegistryService;
17
+ audit: unknown[];
18
+ };
19
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,qHAAqH;AAErH,OAAO,KAAK,EACV,0BAA0B,EAC1B,qBAAqB,EACrB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAgB,2BAA2B,CACzC,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACrC,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,aAAa,CAAC,EAAE,qBAAqB,CAAC;CAClC;;;;;;;EAmBP"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /** `@haneinfo/assignment/testing` — the assignment runtime on in-memory stores, attached to a test state machine. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.InMemoryAssignmentStore = void 0;
5
+ exports.createTestAssignmentRuntime = createTestAssignmentRuntime;
6
+ const testing_1 = require("@haneinfo/state-machine/testing");
7
+ const assignment_service_1 = require("./assignment.service");
8
+ const kind_1 = require("./kind");
9
+ const store_1 = require("./store");
10
+ var store_2 = require("./store");
11
+ Object.defineProperty(exports, "InMemoryAssignmentStore", { enumerable: true, get: function () { return store_2.InMemoryAssignmentStore; } });
12
+ function createTestAssignmentRuntime(options = {}) {
13
+ const base = (0, testing_1.createTestStateMachineRuntime)({
14
+ kinds: [kind_1.assignmentDefinitionKind, ...(options.kinds ?? [])],
15
+ rbac: options.rbac,
16
+ clock: options.clock,
17
+ });
18
+ const assignmentStore = new store_1.InMemoryAssignmentStore();
19
+ const assignments = new assignment_service_1.AssignmentService(assignmentStore, base.runtime, base.registry, options.rbac, options.notifications, options.clock, 'test');
20
+ assignments.onModuleInit();
21
+ return { ...base, assignments, assignmentStore };
22
+ }
23
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":";AAAA,qHAAqH;;;AAcrH,kEAyBC;AAhCD,6DAAgF;AAChF,6DAAyD;AACzD,iCAAkD;AAClD,mCAAkD;AAElD,iCAAkD;AAAzC,gHAAA,uBAAuB,OAAA;AAEhC,SAAgB,2BAA2B,CACzC,UAKI,EAAE;IAEN,MAAM,IAAI,GAAG,IAAA,uCAA6B,EAAC;QACzC,KAAK,EAAE,CAAC,+BAAwB,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC,CAAC;IACH,MAAM,eAAe,GAAG,IAAI,+BAAuB,EAAE,CAAC;IACtD,MAAM,WAAW,GAAG,IAAI,sCAAiB,CACvC,eAAe,EACf,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,QAAQ,EACb,OAAO,CAAC,IAAI,EACZ,OAAO,CAAC,aAAa,EACrB,OAAO,CAAC,KAAK,EACb,MAAM,CACP,CAAC;IACF,WAAW,CAAC,YAAY,EAAE,CAAC;IAC3B,OAAO,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;AACnD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { PersonResolver } from '@haneinfo/plugin-api';
2
+ export type AssignmentPolicy = 'SINGLE' | 'ANY_ONE' | 'JOINT';
3
+ export interface AssignmentResponse {
4
+ key: string;
5
+ label: string;
6
+ /** Transition of the state machine this response takes. */
7
+ transition: string;
8
+ /** JOINT only: completes the assignment at once. */
9
+ decisive?: boolean;
10
+ }
11
+ export interface AssignmentSpec {
12
+ stateMachine: {
13
+ kind: 'StateMachine';
14
+ code: string;
15
+ };
16
+ onEnter: string;
17
+ title?: string;
18
+ assignees: PersonResolver[];
19
+ policy: AssignmentPolicy;
20
+ responses: AssignmentResponse[];
21
+ /** ISO 8601 duration; only marks the due time — a Timer decides what happens. */
22
+ dueIn?: string;
23
+ }
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ {
2
+ "kind": "Assignment",
3
+ "schemaVersion": 1,
4
+ "metadata": {
5
+ "code": "equipment-manager-approval",
6
+ "name": "Approve equipment loan",
7
+ "description": "Any equipment manager approves or rejects a submitted loan within two days."
8
+ },
9
+ "spec": {
10
+ "stateMachine": { "kind": "StateMachine", "code": "equipment-loan-flow" },
11
+ "onEnter": "SUBMITTED",
12
+ "title": "Equipment loan waiting for approval",
13
+ "assignees": [{ "type": "ROLE", "role": "EQUIPMENT_MANAGER" }],
14
+ "policy": "ANY_ONE",
15
+ "responses": [
16
+ { "key": "approve", "label": "Approve", "transition": "approve" },
17
+ { "key": "reject", "label": "Reject", "transition": "reject" }
18
+ ],
19
+ "dueIn": "P2D"
20
+ }
21
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "schemaVersion": "haneinfo.plugin/v1",
3
+ "id": "assignment",
4
+ "displayName": "Assignment",
5
+ "cardinality": "singleton",
6
+ "distribution": "official",
7
+ "engine": {
8
+ "haneinfoPluginApi": "^1.6.0",
9
+ "node": ">=22.0.0",
10
+ "frameworks": {
11
+ "@nestjs/common": "^11.0.5",
12
+ "@nestjs/core": "^11.0.5",
13
+ "@prisma/client": "^6.2.0"
14
+ }
15
+ },
16
+ "provides": [],
17
+ "requires": [
18
+ "haneinfo.prisma",
19
+ "haneinfo.principal-context",
20
+ "haneinfo.definition-registry",
21
+ "haneinfo.state-machine-runtime"
22
+ ],
23
+ "optionalRequires": ["haneinfo.rbac-policy", "haneinfo.notification-inbox"],
24
+ "facets": {
25
+ "backend": {
26
+ "modulePath": "./dist/assignment.module.js",
27
+ "exportName": "AssignmentModule",
28
+ "controllerRoutes": ["assignments"],
29
+ "definitionKinds": [
30
+ {
31
+ "kind": "Assignment",
32
+ "schemaVersion": 1,
33
+ "specSchema": "schemas/assignment.spec.json",
34
+ "displayName": "Assignment"
35
+ }
36
+ ],
37
+ "mcpTools": [
38
+ {
39
+ "name": "list_my_assignments",
40
+ "requiredScopes": ["{namespace}:assignment:read"]
41
+ }
42
+ ],
43
+ "events": [
44
+ {
45
+ "type": "assignment.opened",
46
+ "schemaVersion": 1,
47
+ "description": "Tasks were opened; payload has assignmentCode, round, dueAt, assigneeIds"
48
+ },
49
+ {
50
+ "type": "assignment.closed",
51
+ "schemaVersion": 1,
52
+ "description": "The round completed or was cancelled; payload has assignmentCode, round, outcome"
53
+ }
54
+ ],
55
+ "agentGuide": "./agent-guide.md"
56
+ },
57
+ "prisma": {
58
+ "owns": ["AssignmentBinding", "AssignmentTask"],
59
+ "schemaFragment": "prisma/assignment.prisma",
60
+ "schemaDigest": "sha256:9f959ee518aac39217503bc666d06ec4aec444dadeb47a9204fd19425f6108d0"
61
+ },
62
+ "permissions": {
63
+ "definitions": [
64
+ "assignment:definition:read",
65
+ "assignment:definition:write",
66
+ "assignment:definition:publish"
67
+ ]
68
+ }
69
+ }
70
+ }
package/package.json CHANGED
@@ -1,6 +1,84 @@
1
1
  {
2
2
  "name": "@haneinfo/assignment",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Assignment mechanism: Assignment definitions (who must respond when a state machine enters a state) and the task runtime, registered with @haneinfo/definition-registry.",
6
+ "files": [
7
+ "dist",
8
+ "examples",
9
+ "schemas",
10
+ "prisma",
11
+ "agent-guide.md",
12
+ "haneinfo.plugin.json",
13
+ "plugin.js",
14
+ "plugin.d.ts",
15
+ "testing.js",
16
+ "testing.d.ts"
17
+ ],
18
+ "type": "commonjs",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./plugin": {
27
+ "types": "./dist/plugin.d.ts",
28
+ "default": "./dist/plugin.js"
29
+ },
30
+ "./testing": {
31
+ "types": "./dist/testing.d.ts",
32
+ "default": "./dist/testing.js"
33
+ },
34
+ "./schemas/assignment.spec.json": "./schemas/assignment.spec.json",
35
+ "./prisma/assignment.prisma": "./prisma/assignment.prisma",
36
+ "./haneinfo.plugin.json": "./haneinfo.plugin.json",
37
+ "./agent-guide.md": "./agent-guide.md",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "dependencies": {
41
+ "@haneinfo/plugin-api": "1.6.0"
42
+ },
43
+ "peerDependencies": {
44
+ "@haneinfo/common": "^0.3.4",
45
+ "@haneinfo/definition-registry": "^0.1.0",
46
+ "@haneinfo/state-machine": "^0.1.0",
47
+ "@haneinfo/plugin-host-nest": "^2.0.0",
48
+ "@nestjs/common": "^11.0.0",
49
+ "@nestjs/core": "^11.0.0",
50
+ "@prisma/client": "^6.19.0",
51
+ "zod": "^4.4.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@haneinfo/definition-registry": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@nestjs/common": "^11.0.0",
60
+ "@nestjs/core": "^11.0.0",
61
+ "@prisma/client": "^6.19.0",
62
+ "@types/node": "^24.13.0",
63
+ "typescript": "^5.9.3",
64
+ "vitest": "^4.1.11",
65
+ "zod": "^4.4.0",
66
+ "@haneinfo/definition-registry": "0.1.0",
67
+ "@haneinfo/common": "0.3.4",
68
+ "@haneinfo/form": "0.1.0",
69
+ "@haneinfo/plugin-host-nest": "2.1.0",
70
+ "@haneinfo/state-machine": "0.1.0",
71
+ "@haneinfo/plugin-testkit": "2.3.0"
72
+ },
73
+ "license": "UNLICENSED",
74
+ "publishConfig": {
75
+ "registry": "https://registry.npmjs.org/",
76
+ "access": "public"
77
+ },
78
+ "scripts": {
79
+ "build": "tsc -p tsconfig.build.json",
80
+ "dev": "tsc -p tsconfig.build.json --watch",
81
+ "test": "vitest run",
82
+ "typecheck": "tsc --noEmit"
83
+ }
6
84
  }
package/plugin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // See plugin.js for why this file exists (classic/node10 moduleResolution compatibility shim).
2
+ export * from './dist/plugin';
package/plugin.js ADDED
@@ -0,0 +1,4 @@
1
+ // Compatibility shim for consumers whose TypeScript config uses "classic"/"node10"
2
+ // moduleResolution, which resolves `@haneinfo/assignment/plugin` by looking for a real file
3
+ // at the package root -- it does not consult package.json's "exports" map at all.
4
+ module.exports = require('./dist/plugin');
@@ -0,0 +1,32 @@
1
+ /// The Assignment definitions a state-machine instance pinned when it started. @internal
2
+ model AssignmentBinding {
3
+ instanceId String @map("instance_id")
4
+ assignmentCode String @map("assignment_code")
5
+ version Int
6
+ definition Json
7
+
8
+ @@id([instanceId, assignmentCode])
9
+ @@map("assignment_bindings")
10
+ }
11
+
12
+ /// One person's task in one round of an assignment. Status is compare-and-set. @internal
13
+ model AssignmentTask {
14
+ id String @id @default(cuid())
15
+ instanceId String @map("instance_id")
16
+ assignmentCode String @map("assignment_code")
17
+ assignmentVersion Int @map("assignment_version")
18
+ round Int
19
+ assigneeId String @map("assignee_id")
20
+ /// OPEN, RESPONDED, CLOSED (decided by someone else) or CANCELLED (the instance left the state).
21
+ status String
22
+ title String
23
+ response String?
24
+ comment String?
25
+ dueAt DateTime? @map("due_at")
26
+ respondedAt DateTime? @map("responded_at")
27
+ createdAt DateTime @default(now()) @map("created_at")
28
+
29
+ @@index([assigneeId, status])
30
+ @@index([instanceId, assignmentCode, round])
31
+ @@map("assignment_tasks")
32
+ }