@msm-core/jobs 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 (42) hide show
  1. package/README.md +43 -0
  2. package/dist/approval/policy.d.ts +8 -0
  3. package/dist/approval/policy.d.ts.map +1 -0
  4. package/dist/approval/policy.js +22 -0
  5. package/dist/approval/policy.js.map +1 -0
  6. package/dist/config.d.ts +31 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +16 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/enums.d.ts +16 -0
  11. package/dist/enums.d.ts.map +1 -0
  12. package/dist/enums.js +35 -0
  13. package/dist/enums.js.map +1 -0
  14. package/dist/index.d.ts +39 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +53 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/orchestrator.d.ts +26 -0
  19. package/dist/orchestrator.d.ts.map +1 -0
  20. package/dist/orchestrator.js +408 -0
  21. package/dist/orchestrator.js.map +1 -0
  22. package/dist/port.d.ts +127 -0
  23. package/dist/port.d.ts.map +1 -0
  24. package/dist/port.js +17 -0
  25. package/dist/port.js.map +1 -0
  26. package/dist/reconciler.d.ts +25 -0
  27. package/dist/reconciler.d.ts.map +1 -0
  28. package/dist/reconciler.js +75 -0
  29. package/dist/reconciler.js.map +1 -0
  30. package/dist/resume-token.d.ts +40 -0
  31. package/dist/resume-token.d.ts.map +1 -0
  32. package/dist/resume-token.js +116 -0
  33. package/dist/resume-token.js.map +1 -0
  34. package/dist/types.d.ts +134 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +4 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/workflow/registry.d.ts +10 -0
  39. package/dist/workflow/registry.d.ts.map +1 -0
  40. package/dist/workflow/registry.js +114 -0
  41. package/dist/workflow/registry.js.map +1 -0
  42. package/package.json +47 -0
@@ -0,0 +1,75 @@
1
+ // Wait reconciler — periodically re-enqueues waiting_time jobs whose resumeAt has
2
+ // passed. The durability backstop. Ported from kader's job-wait-reconciler.service.ts;
3
+ // the Mongo scan is a JobStore port call, the distributed lock is a LockPort call, and
4
+ // the setInterval loop stays in the platform-side adapter (this is the pure decision).
5
+ const RECONCILE_LOCK_KEY = "jobs:wait_reconcile:lock:v1";
6
+ const QUEUE_NAME = "jobs";
7
+ export function createReconciler(deps) {
8
+ const { jobStore, queue, lock, ops, clock, config } = deps;
9
+ async function reconcileWaitingTimeJobs(asOf = clock.now()) {
10
+ // In "delayed" mode the orchestrator self-enqueues delayed BullMQ ticks; the scan is off.
11
+ if (config.resumeMode === "delayed") {
12
+ return { locked: false, scanned: 0, enqueued: 0, failed: 0, skippedByMode: true, asOf: asOf.toISOString() };
13
+ }
14
+ const locked = await lock.acquire(RECONCILE_LOCK_KEY, Math.max(15_000, deps.lockTtlMs));
15
+ if (!locked) {
16
+ return { locked: false, scanned: 0, enqueued: 0, failed: 0, skippedByMode: false, asOf: asOf.toISOString() };
17
+ }
18
+ const batch = Math.max(1, config.waitReconcileBatchSize);
19
+ const dueJobs = await jobStore.findDueWaitingTime(asOf, batch);
20
+ let enqueued = 0;
21
+ let failed = 0;
22
+ for (const job of dueJobs) {
23
+ try {
24
+ const dedupeKey = job.resumeAt ? `${job.resumeAt.getTime()}` : `reconcile:${asOf.getTime()}`;
25
+ await queue.enqueue({
26
+ jobId: job.jobId,
27
+ tenantId: job.tenantId,
28
+ reason: "waiting_time_reconcile",
29
+ dedupeKey,
30
+ });
31
+ enqueued += 1;
32
+ ops.record({
33
+ jobId: job.jobId,
34
+ tenantId: job.tenantId,
35
+ missionId: job.missionId,
36
+ source: "job_wait_reconciler",
37
+ eventType: "job.resumed.time",
38
+ status: "queued",
39
+ queueName: QUEUE_NAME,
40
+ employeeId: job.employeeId,
41
+ correlationId: job.correlationId,
42
+ metadata: {
43
+ reason: "waiting_time_reconcile",
44
+ resumeAt: job.resumeAt ? new Date(job.resumeAt).toISOString() : null,
45
+ },
46
+ });
47
+ }
48
+ catch (error) {
49
+ failed += 1;
50
+ ops.record({
51
+ jobId: job.jobId,
52
+ tenantId: job.tenantId,
53
+ missionId: job.missionId,
54
+ source: "job_wait_reconciler",
55
+ eventType: "job.resume.reconcile_failed",
56
+ status: "failed",
57
+ queueName: QUEUE_NAME,
58
+ employeeId: job.employeeId,
59
+ correlationId: job.correlationId,
60
+ metadata: { reason: "waiting_time_reconcile", error: error.message },
61
+ });
62
+ }
63
+ }
64
+ return {
65
+ locked: true,
66
+ scanned: dueJobs.length,
67
+ enqueued,
68
+ failed,
69
+ skippedByMode: false,
70
+ asOf: asOf.toISOString(),
71
+ };
72
+ }
73
+ return { reconcileWaitingTimeJobs };
74
+ }
75
+ //# sourceMappingURL=reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciler.js","sourceRoot":"","sources":["../src/reconciler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { NoncePort } from "./port.js";
2
+ export declare class ResumeTokenError extends Error {
3
+ code: string;
4
+ httpStatus: number;
5
+ constructor(code: string, message: string, httpStatus?: number);
6
+ }
7
+ export interface IssueResumeTokenInput {
8
+ tenantId: string;
9
+ jobId: string;
10
+ eventName: string;
11
+ }
12
+ export interface IssuedResumeToken {
13
+ token: string;
14
+ nonce: string;
15
+ expiresAt: string;
16
+ ttlSeconds: number;
17
+ }
18
+ export interface ValidateResumeTokenInput {
19
+ token: string;
20
+ tenantId: string;
21
+ jobId: string;
22
+ eventName: string;
23
+ payload?: unknown;
24
+ }
25
+ export interface ConsumedResumeToken {
26
+ nonce: string;
27
+ expiresAtEpochSeconds: number;
28
+ }
29
+ export interface ResumeTokenDeps {
30
+ secret: string;
31
+ ttlSeconds: number;
32
+ nonce: NoncePort;
33
+ }
34
+ export interface ResumeTokenService {
35
+ issue(input: IssueResumeTokenInput): IssuedResumeToken;
36
+ validateAndConsume(input: ValidateResumeTokenInput): Promise<ConsumedResumeToken>;
37
+ ttlSeconds: number;
38
+ }
39
+ export declare function createResumeTokenService(deps: ResumeTokenDeps): ResumeTokenService;
40
+ //# sourceMappingURL=resume-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-token.d.ts","sourceRoot":"","sources":["../src/resume-token.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAI3C,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;gBACP,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,SAAM;CAM5D;AAUD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAMD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,KAAK,EAAE,qBAAqB,GAAG,iBAAiB,CAAC;IACvD,kBAAkB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,GAAG,kBAAkB,CA2HlF"}
@@ -0,0 +1,116 @@
1
+ // waiting_event resume tokens — pure crypto (jsonwebtoken + node:crypto) with
2
+ // one-time-use replay protection via the injected NoncePort. Ported from kader's
3
+ // job-resume-token.service.ts. `jsonwebtoken` is the engine's only runtime dep
4
+ // (pure crypto, not infrastructure).
5
+ import { randomUUID } from "node:crypto";
6
+ import jwt from "jsonwebtoken";
7
+ const RESUME_NONCE_KEY_PREFIX = "jobs:resume:nonce:v1";
8
+ export class ResumeTokenError extends Error {
9
+ code;
10
+ httpStatus;
11
+ constructor(code, message, httpStatus = 401) {
12
+ super(message);
13
+ this.name = "ResumeTokenError";
14
+ this.code = code;
15
+ this.httpStatus = httpStatus;
16
+ }
17
+ }
18
+ function isNonEmptyString(value) {
19
+ return typeof value === "string" && value.trim().length > 0;
20
+ }
21
+ export function createResumeTokenService(deps) {
22
+ const ttlSeconds = Math.max(60, deps.ttlSeconds);
23
+ function getSecret() {
24
+ if (!isNonEmptyString(deps.secret)) {
25
+ throw new ResumeTokenError("JOB_EVENT_TOKEN_SECRET_INVALID", "Resume token signing secret is not configured", 500);
26
+ }
27
+ return deps.secret;
28
+ }
29
+ function buildNonceKey(claims) {
30
+ return `${RESUME_NONCE_KEY_PREFIX}:${claims.tenantId}:${claims.jobId}:${claims.eventName}:${claims.nonce}`;
31
+ }
32
+ function parseClaims(token) {
33
+ const secret = getSecret();
34
+ let decoded;
35
+ try {
36
+ decoded = jwt.verify(token, secret, { algorithms: ["HS256"] });
37
+ }
38
+ catch (error) {
39
+ throw new ResumeTokenError("JOB_EVENT_TOKEN_INVALID", "Event token is invalid or expired", 401);
40
+ }
41
+ if (typeof decoded === "string") {
42
+ throw new ResumeTokenError("JOB_EVENT_TOKEN_INVALID", "Event token payload is malformed", 401);
43
+ }
44
+ const candidate = decoded;
45
+ if (candidate.typ !== "job_resume" ||
46
+ !isNonEmptyString(candidate.tenantId) ||
47
+ !isNonEmptyString(candidate.jobId) ||
48
+ !isNonEmptyString(candidate.eventName) ||
49
+ !isNonEmptyString(candidate.nonce)) {
50
+ throw new ResumeTokenError("JOB_EVENT_TOKEN_INVALID", "Event token claims are malformed", 401);
51
+ }
52
+ return candidate;
53
+ }
54
+ function assertTokenBinding(claims, expected) {
55
+ if (claims.tenantId !== expected.tenantId ||
56
+ claims.jobId !== expected.jobId ||
57
+ claims.eventName !== expected.eventName) {
58
+ throw new ResumeTokenError("JOB_EVENT_TOKEN_INVALID", "Event token does not match requested binding", 401);
59
+ }
60
+ }
61
+ function assertPayloadBinding(payload, claims) {
62
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
63
+ return;
64
+ const rec = payload;
65
+ const mismatches = [];
66
+ if ("tenantId" in rec && rec.tenantId !== claims.tenantId)
67
+ mismatches.push("tenantId");
68
+ if ("jobId" in rec && rec.jobId !== claims.jobId)
69
+ mismatches.push("jobId");
70
+ if ("eventName" in rec && rec.eventName !== claims.eventName)
71
+ mismatches.push("eventName");
72
+ if ("nonce" in rec && rec.nonce !== claims.nonce)
73
+ mismatches.push("nonce");
74
+ if (mismatches.length > 0) {
75
+ throw new ResumeTokenError("JOB_EVENT_PAYLOAD_INVALID", "Event payload binding mismatch", 400);
76
+ }
77
+ }
78
+ async function consumeNonce(claims) {
79
+ const nowEpochSeconds = Math.floor(Date.now() / 1000);
80
+ const tokenExpirySeconds = typeof claims.exp === "number" ? claims.exp : nowEpochSeconds + ttlSeconds;
81
+ const nonceTtl = Math.max(ttlSeconds, tokenExpirySeconds - nowEpochSeconds, 1);
82
+ const key = buildNonceKey(claims);
83
+ let claimed;
84
+ try {
85
+ claimed = await deps.nonce.claimOnce(key, nonceTtl);
86
+ }
87
+ catch (error) {
88
+ throw new ResumeTokenError("JOB_EVENT_REPLAY_UNAVAILABLE", "Could not validate event replay protection", 503);
89
+ }
90
+ if (!claimed) {
91
+ throw new ResumeTokenError("JOB_EVENT_TOKEN_REPLAY", "Event token has already been consumed", 409);
92
+ }
93
+ }
94
+ function issue(input) {
95
+ const secret = getSecret();
96
+ const nonce = randomUUID();
97
+ const token = jwt.sign({ typ: "job_resume", tenantId: input.tenantId, jobId: input.jobId, eventName: input.eventName, nonce }, secret, { algorithm: "HS256", expiresIn: ttlSeconds });
98
+ return {
99
+ token,
100
+ nonce,
101
+ ttlSeconds,
102
+ expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
103
+ };
104
+ }
105
+ async function validateAndConsume(input) {
106
+ const claims = parseClaims(input.token);
107
+ assertTokenBinding(claims, input);
108
+ assertPayloadBinding(input.payload, claims);
109
+ await consumeNonce(claims);
110
+ const nowEpochSeconds = Math.floor(Date.now() / 1000);
111
+ const expiresAtEpochSeconds = typeof claims.exp === "number" ? claims.exp : nowEpochSeconds + ttlSeconds;
112
+ return { nonce: claims.nonce, expiresAtEpochSeconds };
113
+ }
114
+ return { issue, validateAndConsume, ttlSeconds };
115
+ }
116
+ //# sourceMappingURL=resume-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-token.js","sourceRoot":"","sources":["../src/resume-token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ import type { JobStatus, JobStepStatus, JobStepType, JobStepTriggeredBy } from "./enums.js";
2
+ import type { JobApprovalPolicy, JobApprovalTimeoutOutcome } from "./approval/policy.js";
3
+ export interface JobBudget {
4
+ perStep: {
5
+ maxIterations: number;
6
+ maxCostUsd: number;
7
+ timeoutMs: number;
8
+ };
9
+ totalJob: {
10
+ maxSteps: number;
11
+ maxTotalCostUsd: number;
12
+ maxDurationMs: number;
13
+ };
14
+ }
15
+ export interface JobWaitEvent {
16
+ eventName: string | null;
17
+ token: string | null;
18
+ }
19
+ /** A read snapshot of a Job — what the engine reasons over. Ids are opaque strings. */
20
+ export interface JobSnapshot {
21
+ jobId: string;
22
+ tenantId: string;
23
+ employeeId: string;
24
+ missionId: string | null;
25
+ status: JobStatus;
26
+ workflowType: string;
27
+ workflowVersion: string;
28
+ currentStep: number;
29
+ state: Record<string, unknown>;
30
+ attempt: number;
31
+ resumeAt: Date | null;
32
+ waitEvent: JobWaitEvent | null;
33
+ correlationId: string;
34
+ budget: JobBudget;
35
+ version: number;
36
+ totalStepsExecuted: number;
37
+ totalCostUsd: number;
38
+ startedAt: Date | null;
39
+ completedAt: Date | null;
40
+ }
41
+ export interface JobStepToolCall {
42
+ toolName: string;
43
+ success: boolean;
44
+ durationMs?: number;
45
+ costUsd?: number;
46
+ errorCode?: string;
47
+ }
48
+ /** An append-only step record (one workflow transition). */
49
+ export interface JobStepRecord {
50
+ stepId: string;
51
+ jobId: string;
52
+ tenantId: string;
53
+ stepNumber: number;
54
+ stepType: JobStepType;
55
+ status: JobStepStatus;
56
+ triggeredBy: JobStepTriggeredBy;
57
+ startedAt: Date;
58
+ endedAt: Date | null;
59
+ durationMs: number;
60
+ toolCalls: JobStepToolCall[];
61
+ output: Record<string, unknown> | null;
62
+ error: {
63
+ code?: string;
64
+ message?: string;
65
+ } | null;
66
+ nextStep: string | null;
67
+ transitionReason: string | null;
68
+ idempotencyKey: string;
69
+ }
70
+ /** Inputs to create a new durable job. */
71
+ export interface CreateJobInput {
72
+ tenantId: string;
73
+ employeeId: string;
74
+ workflowType: string;
75
+ workflowVersion: string;
76
+ missionId?: string | null;
77
+ correlationId?: string;
78
+ state?: Record<string, unknown>;
79
+ budget?: Partial<JobBudget>;
80
+ }
81
+ /** Operational ledger event the engine emits through JobOpsPort (fire-and-forget). */
82
+ export interface JobOpsEventInput {
83
+ jobId: string;
84
+ tenantId: string;
85
+ missionId?: string | null;
86
+ source: string;
87
+ eventType: string;
88
+ status?: string | null;
89
+ queueName?: string | null;
90
+ employeeId?: string | null;
91
+ correlationId?: string | null;
92
+ metadata?: Record<string, unknown>;
93
+ }
94
+ export interface WorkflowResolution {
95
+ stepType: JobStepType;
96
+ triggeredBy: JobStepTriggeredBy;
97
+ transitionReason: string;
98
+ nextStatus: JobStatus;
99
+ resumeDelayMs?: number;
100
+ waitEventName?: string;
101
+ approvalPolicy?: JobApprovalPolicy;
102
+ approvalTimeoutMs?: number;
103
+ approvalTimeoutOutcome?: JobApprovalTimeoutOutcome | string;
104
+ statePatch?: Record<string, unknown>;
105
+ output?: Record<string, unknown>;
106
+ }
107
+ export interface WorkflowContext {
108
+ workflowType: string;
109
+ workflowVersion: string;
110
+ budget: JobBudget;
111
+ state: Record<string, unknown>;
112
+ stepNumber: number;
113
+ reason: string;
114
+ now: Date;
115
+ }
116
+ /** One step in a declarative workflow definition (fed from the manifest in platform-ai). */
117
+ export interface WorkflowSequenceStep {
118
+ stepType: JobStepType;
119
+ triggeredBy?: JobStepTriggeredBy;
120
+ waitTimeMs?: number;
121
+ waitEventName?: string;
122
+ requiresApproval?: boolean;
123
+ approvalPolicy?: JobApprovalPolicy;
124
+ approvalTimeoutMs?: number;
125
+ terminalStatus?: Extract<JobStatus, "completed" | "failed" | "cancelled">;
126
+ transitionReason?: string;
127
+ output?: Record<string, unknown>;
128
+ }
129
+ export interface WorkflowDefinition {
130
+ workflowType: string;
131
+ workflowVersion: string;
132
+ sequence: WorkflowSequenceStep[];
133
+ }
134
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ // @msm-core/jobs — the pure data shapes. These are storage-agnostic: the Mongo
2
+ // adapter maps its documents onto these, the engine only ever sees these.
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,0EAA0E"}
@@ -0,0 +1,10 @@
1
+ import type { WorkflowRegistry } from "../port.js";
2
+ import type { WorkflowDefinition } from "../types.js";
3
+ /** A minimal built-in workflow so a fresh deploy can run a job before any are declared. */
4
+ export declare const BUILTIN_JOB_BASIC: WorkflowDefinition;
5
+ export interface CreateWorkflowRegistryOptions {
6
+ /** Wait used when a step resolves to waiting_time without an explicit waitTimeMs. */
7
+ defaultWaitMs?: number;
8
+ }
9
+ export declare function createWorkflowRegistry(definitions: WorkflowDefinition[], options?: CreateWorkflowRegistryOptions): WorkflowRegistry;
10
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/workflow/registry.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAEV,kBAAkB,EAGnB,MAAM,aAAa,CAAC;AAIrB,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,EAAE,kBAiB/B,CAAC;AAMF,MAAM,WAAW,6BAA6B;IAC5C,qFAAqF;IACrF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,kBAAkB,EAAE,EACjC,OAAO,GAAE,6BAAkC,GAC1C,gBAAgB,CA6FlB"}
@@ -0,0 +1,114 @@
1
+ // Default, data-driven workflow registry. Ported from kader's workflow-registry.ts,
2
+ // but the workflow definitions are INJECTED (in platform-ai they come from the client
3
+ // manifest's `missions[].sequence`) instead of being hardcoded. The resolution logic
4
+ // is identical to kader's so the state-machine semantics are preserved exactly.
5
+ import { DEFAULT_JOB_APPROVAL_POLICY, resolveJobApprovalTimeoutOutcome, } from "../approval/policy.js";
6
+ const DEFAULT_WAIT_MS = 60_000;
7
+ /** A minimal built-in workflow so a fresh deploy can run a job before any are declared. */
8
+ export const BUILTIN_JOB_BASIC = {
9
+ workflowType: "job.basic",
10
+ workflowVersion: "1.0.0",
11
+ sequence: [
12
+ {
13
+ stepType: "run_interaction_task",
14
+ triggeredBy: "task",
15
+ waitTimeMs: DEFAULT_WAIT_MS,
16
+ transitionReason: "initial_iteration",
17
+ },
18
+ {
19
+ stepType: "run_interaction_task",
20
+ triggeredBy: "time",
21
+ terminalStatus: "completed",
22
+ transitionReason: "second_iteration_completed",
23
+ },
24
+ ],
25
+ };
26
+ function normalizeWorkflowType(workflowType) {
27
+ return workflowType.trim().toLowerCase();
28
+ }
29
+ export function createWorkflowRegistry(definitions, options = {}) {
30
+ const defaultWaitMs = options.defaultWaitMs ?? DEFAULT_WAIT_MS;
31
+ // Always provide a fallback so resolution never throws on an unknown type.
32
+ const defs = definitions.some((d) => normalizeWorkflowType(d.workflowType) === "job.basic")
33
+ ? definitions
34
+ : [...definitions, BUILTIN_JOB_BASIC];
35
+ function getDefinition(workflowType, workflowVersion) {
36
+ const normalizedType = normalizeWorkflowType(workflowType);
37
+ const exact = defs.find((d) => normalizeWorkflowType(d.workflowType) === normalizedType &&
38
+ d.workflowVersion === workflowVersion);
39
+ if (exact)
40
+ return exact;
41
+ const latestForType = defs.find((d) => normalizeWorkflowType(d.workflowType) === normalizedType);
42
+ if (latestForType)
43
+ return latestForType;
44
+ return defs.find((d) => normalizeWorkflowType(d.workflowType) === "job.basic");
45
+ }
46
+ function resolveStep(context) {
47
+ const definition = getDefinition(context.workflowType, context.workflowVersion);
48
+ const sequenceIndex = Math.max(0, context.stepNumber - 1);
49
+ const stepDefinition = definition.sequence[Math.min(sequenceIndex, definition.sequence.length - 1)];
50
+ const reachedBudgetMaxSteps = context.stepNumber >= context.budget.totalJob.maxSteps;
51
+ let nextStatus;
52
+ if (reachedBudgetMaxSteps) {
53
+ nextStatus = "completed";
54
+ }
55
+ else if (stepDefinition.terminalStatus) {
56
+ nextStatus = stepDefinition.terminalStatus;
57
+ }
58
+ else if (stepDefinition.requiresApproval) {
59
+ nextStatus = "awaiting_approval";
60
+ }
61
+ else if (stepDefinition.waitEventName) {
62
+ nextStatus = "waiting_event";
63
+ }
64
+ else {
65
+ nextStatus = "waiting_time";
66
+ }
67
+ const approvalPolicy = stepDefinition.approvalPolicy || DEFAULT_JOB_APPROVAL_POLICY;
68
+ const approvalTimeoutOutcome = resolveJobApprovalTimeoutOutcome(approvalPolicy);
69
+ const transitionReason = reachedBudgetMaxSteps
70
+ ? "budget_max_steps_reached"
71
+ : stepDefinition.transitionReason || "workflow_transition";
72
+ const resolution = {
73
+ stepType: stepDefinition.stepType,
74
+ triggeredBy: stepDefinition.triggeredBy || "task",
75
+ transitionReason,
76
+ nextStatus,
77
+ statePatch: {
78
+ workflow: {
79
+ type: context.workflowType,
80
+ version: context.workflowVersion,
81
+ sequenceIndex,
82
+ lastTransitionReason: transitionReason,
83
+ },
84
+ pendingApproval: nextStatus === "awaiting_approval"
85
+ ? { policy: approvalPolicy, timeoutOutcome: approvalTimeoutOutcome }
86
+ : null,
87
+ lastOrchestrationReason: context.reason,
88
+ lastOrchestratedAt: context.now.toISOString(),
89
+ },
90
+ output: {
91
+ workflowType: context.workflowType,
92
+ workflowVersion: context.workflowVersion,
93
+ transitionReason,
94
+ ...(stepDefinition.output || {}),
95
+ },
96
+ };
97
+ if (nextStatus === "waiting_time") {
98
+ resolution.resumeDelayMs = stepDefinition.waitTimeMs ?? defaultWaitMs;
99
+ }
100
+ if (nextStatus === "waiting_event" && stepDefinition.waitEventName !== undefined) {
101
+ resolution.waitEventName = stepDefinition.waitEventName;
102
+ }
103
+ if (nextStatus === "awaiting_approval") {
104
+ resolution.approvalPolicy = approvalPolicy;
105
+ resolution.approvalTimeoutOutcome = approvalTimeoutOutcome;
106
+ if (stepDefinition.approvalTimeoutMs !== undefined) {
107
+ resolution.approvalTimeoutMs = stepDefinition.approvalTimeoutMs;
108
+ }
109
+ }
110
+ return resolution;
111
+ }
112
+ return { resolveStep };
113
+ }
114
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/workflow/registry.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@msm-core/jobs",
3
+ "version": "0.1.0",
4
+ "description": "Durable job/mission orchestration engine (CAS + idempotency state machine, cron missions, HITL approval). Pure decision logic; storage, queue, lock, clock injected via ports (no mongoose/bullmq/ioredis dependency).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./port": {
14
+ "types": "./dist/port.d.ts",
15
+ "import": "./dist/port.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/msm-core/sdk.git",
28
+ "directory": "jobs"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "test": "vitest run --passWithNoTests",
33
+ "clean": "rm -rf dist",
34
+ "prepublishOnly": "npm run build && npm test"
35
+ },
36
+ "dependencies": {
37
+ "jsonwebtoken": "^9.0.2"
38
+ },
39
+ "devDependencies": {
40
+ "@types/jsonwebtoken": "^9.0.6",
41
+ "@types/node": "^20.0.0",
42
+ "typescript": "^5.4.0",
43
+ "vitest": "^1.6.0"
44
+ },
45
+ "license": "UNLICENSED",
46
+ "author": "Emad Jumaah"
47
+ }