@nt-ai-lab/deterministic-agent-workflow-engine 0.4.0 → 0.4.1

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/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export type { BaseWorkflowState } from './platform/domain/workflow-state';
2
2
  export { WorkflowStateError } from './platform/domain/workflow-state';
3
3
  export type { BaseEvent } from './platform/domain/base-event';
4
4
  export { baseEventSchema } from './platform/domain/base-event';
5
+ export { nonEmptyStringSchema, requireNonEmptyString, } from './platform/domain/non-empty-string';
5
6
  export type { EventEnvelope, StoredEvent, } from './platform/domain/stored-event';
6
7
  export { flattenStoredEvent, stripEnvelopeKeys, toPayload, } from './platform/domain/stored-event';
7
8
  export { reflectionCategorySchema, reflectionConfidenceSchema, reflectionEvidenceSchema, reflectionFindingSchema, reflectionPayloadSchema, recordReflectionInputSchema, storedReflectionSchema, } from './platform/domain/reflection-types';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { WorkflowStateError } from './platform/domain/workflow-state.js';
2
2
  export { baseEventSchema } from './platform/domain/base-event.js';
3
+ export { nonEmptyStringSchema, requireNonEmptyString, } from './platform/domain/non-empty-string.js';
3
4
  export { flattenStoredEvent, stripEnvelopeKeys, toPayload, } from './platform/domain/stored-event.js';
4
5
  export { reflectionCategorySchema, reflectionConfidenceSchema, reflectionEvidenceSchema, reflectionFindingSchema, reflectionPayloadSchema, recordReflectionInputSchema, storedReflectionSchema, } from './platform/domain/reflection-types.js';
5
6
  export { reviewTypeSchema, reviewVerdictSchema, reviewFindingSeveritySchema, reviewFindingStatusSchema, reviewFindingSchema, reviewPayloadSchema, recordReviewInputSchema, storedReviewSchema, listedReviewSchema, reviewFiltersSchema, } from './platform/domain/review-types.js';
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
+ import { nonEmptyStringSchema } from './non-empty-string.js';
2
3
  export const baseEventSchema = z.object({
3
- type: z.string(),
4
- at: z.string(),
4
+ type: nonEmptyStringSchema,
5
+ at: nonEmptyStringSchema,
5
6
  }).passthrough();
@@ -2,24 +2,24 @@ import { z } from 'zod';
2
2
  declare const sessionStartedSchema: z.ZodObject<{
3
3
  type: z.ZodLiteral<"session-started">;
4
4
  at: z.ZodString;
5
- transcriptPath: z.ZodOptional<z.ZodString>;
6
- repository: z.ZodOptional<z.ZodString>;
7
- currentState: z.ZodOptional<z.ZodString>;
8
- states: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
5
+ transcriptPath: z.ZodString;
6
+ repository: z.ZodString;
7
+ currentState: z.ZodString;
8
+ states: z.ZodArray<z.ZodString, "many">;
9
9
  }, "strip", z.ZodTypeAny, {
10
10
  type: "session-started";
11
11
  at: string;
12
- repository?: string | undefined;
13
- transcriptPath?: string | undefined;
14
- currentState?: string | undefined;
15
- states?: string[] | undefined;
12
+ repository: string;
13
+ transcriptPath: string;
14
+ currentState: string;
15
+ states: string[];
16
16
  }, {
17
17
  type: "session-started";
18
18
  at: string;
19
- repository?: string | undefined;
20
- transcriptPath?: string | undefined;
21
- currentState?: string | undefined;
22
- states?: string[] | undefined;
19
+ repository: string;
20
+ transcriptPath: string;
21
+ currentState: string;
22
+ states: string[];
23
23
  }>;
24
24
  declare const transitionedSchema: z.ZodObject<{
25
25
  type: z.ZodLiteral<"transitioned">;
@@ -230,24 +230,24 @@ export declare const reviewRecordedEventSchema: z.ZodObject<{
230
230
  export declare const engineEventSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
231
231
  type: z.ZodLiteral<"session-started">;
232
232
  at: z.ZodString;
233
- transcriptPath: z.ZodOptional<z.ZodString>;
234
- repository: z.ZodOptional<z.ZodString>;
235
- currentState: z.ZodOptional<z.ZodString>;
236
- states: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
233
+ transcriptPath: z.ZodString;
234
+ repository: z.ZodString;
235
+ currentState: z.ZodString;
236
+ states: z.ZodArray<z.ZodString, "many">;
237
237
  }, "strip", z.ZodTypeAny, {
238
238
  type: "session-started";
239
239
  at: string;
240
- repository?: string | undefined;
241
- transcriptPath?: string | undefined;
242
- currentState?: string | undefined;
243
- states?: string[] | undefined;
240
+ repository: string;
241
+ transcriptPath: string;
242
+ currentState: string;
243
+ states: string[];
244
244
  }, {
245
245
  type: "session-started";
246
246
  at: string;
247
- repository?: string | undefined;
248
- transcriptPath?: string | undefined;
249
- currentState?: string | undefined;
250
- states?: string[] | undefined;
247
+ repository: string;
248
+ transcriptPath: string;
249
+ currentState: string;
250
+ states: string[];
251
251
  }>, z.ZodObject<{
252
252
  type: z.ZodLiteral<"transitioned">;
253
253
  at: z.ZodString;
@@ -1,86 +1,87 @@
1
1
  import { z } from 'zod';
2
+ import { nonEmptyStringSchema } from './non-empty-string.js';
2
3
  const sessionStartedSchema = z.object({
3
4
  type: z.literal('session-started'),
4
- at: z.string(),
5
- transcriptPath: z.string().optional(),
6
- repository: z.string().optional(),
7
- currentState: z.string().optional(),
8
- states: z.array(z.string()).optional(),
5
+ at: nonEmptyStringSchema,
6
+ transcriptPath: nonEmptyStringSchema,
7
+ repository: nonEmptyStringSchema,
8
+ currentState: nonEmptyStringSchema,
9
+ states: z.array(nonEmptyStringSchema),
9
10
  });
10
11
  const transitionedSchema = z.object({
11
12
  type: z.literal('transitioned'),
12
- at: z.string(),
13
- from: z.string(),
14
- to: z.string(),
15
- preBlockedState: z.string().optional(),
13
+ at: nonEmptyStringSchema,
14
+ from: nonEmptyStringSchema,
15
+ to: nonEmptyStringSchema,
16
+ preBlockedState: nonEmptyStringSchema.optional(),
16
17
  iteration: z.number().optional(),
17
- developingHeadCommit: z.string().optional(),
18
+ developingHeadCommit: nonEmptyStringSchema.optional(),
18
19
  developerDone: z.boolean().optional(),
19
20
  });
20
21
  const agentRegisteredSchema = z.object({
21
22
  type: z.literal('agent-registered'),
22
- at: z.string(),
23
- agentType: z.string(),
24
- agentId: z.string(),
23
+ at: nonEmptyStringSchema,
24
+ agentType: nonEmptyStringSchema,
25
+ agentId: nonEmptyStringSchema,
25
26
  });
26
27
  const agentShutDownSchema = z.object({
27
28
  type: z.literal('agent-shut-down'),
28
- at: z.string(),
29
- agentName: z.string(),
29
+ at: nonEmptyStringSchema,
30
+ agentName: nonEmptyStringSchema,
30
31
  });
31
32
  const journalEntrySchema = z.object({
32
33
  type: z.literal('journal-entry'),
33
- at: z.string(),
34
- agentName: z.string(),
35
- content: z.string(),
34
+ at: nonEmptyStringSchema,
35
+ agentName: nonEmptyStringSchema,
36
+ content: nonEmptyStringSchema,
36
37
  });
37
38
  const writeCheckedSchema = z.object({
38
39
  type: z.literal('write-checked'),
39
- at: z.string(),
40
- tool: z.string(),
41
- filePath: z.string(),
40
+ at: nonEmptyStringSchema,
41
+ tool: nonEmptyStringSchema,
42
+ filePath: nonEmptyStringSchema,
42
43
  allowed: z.boolean(),
43
- reason: z.string().optional(),
44
+ reason: nonEmptyStringSchema.optional(),
44
45
  });
45
46
  const bashCheckedSchema = z.object({
46
47
  type: z.literal('bash-checked'),
47
- at: z.string(),
48
- tool: z.string(),
49
- command: z.string(),
48
+ at: nonEmptyStringSchema,
49
+ tool: nonEmptyStringSchema,
50
+ command: nonEmptyStringSchema,
50
51
  allowed: z.boolean(),
51
- reason: z.string().optional(),
52
+ reason: nonEmptyStringSchema.optional(),
52
53
  });
53
54
  const pluginReadCheckedSchema = z.object({
54
55
  type: z.literal('plugin-read-checked'),
55
- at: z.string(),
56
- tool: z.string(),
57
- path: z.string(),
56
+ at: nonEmptyStringSchema,
57
+ tool: nonEmptyStringSchema,
58
+ path: nonEmptyStringSchema,
58
59
  allowed: z.boolean(),
59
- reason: z.string().optional(),
60
+ reason: nonEmptyStringSchema.optional(),
60
61
  });
61
62
  const idleCheckedSchema = z.object({
62
63
  type: z.literal('idle-checked'),
63
- at: z.string(),
64
- agentName: z.string(),
64
+ at: nonEmptyStringSchema,
65
+ agentName: nonEmptyStringSchema,
65
66
  allowed: z.boolean(),
66
- reason: z.string().optional(),
67
+ reason: nonEmptyStringSchema.optional(),
67
68
  });
68
69
  const identityVerifiedSchema = z.object({
69
70
  type: z.literal('identity-verified'),
70
- at: z.string(),
71
- status: z.string(),
72
- transcriptPath: z.string(),
71
+ at: nonEmptyStringSchema,
72
+ status: nonEmptyStringSchema,
73
+ transcriptPath: nonEmptyStringSchema,
73
74
  });
74
75
  const contextRequestedSchema = z.object({
75
76
  type: z.literal('context-requested'),
76
- at: z.string(),
77
- agentName: z.string(),
77
+ at: nonEmptyStringSchema,
78
+ agentName: nonEmptyStringSchema,
78
79
  });
79
80
  export const reviewRecordedEventSchema = z.object({
80
81
  type: z.literal('review-recorded'),
81
- at: z.string(),
82
+ at: nonEmptyStringSchema,
82
83
  reviewId: z.number().int().positive(),
83
- reviewType: z.string(),
84
+ reviewType: nonEmptyStringSchema,
84
85
  verdict: z.enum(['PASS', 'FAIL']),
85
86
  });
86
87
  export const engineEventSchema = z.discriminatedUnion('type', [
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ /** @riviere-role value-object */
3
+ export declare const nonEmptyStringSchema: z.ZodString;
4
+ /** @riviere-role domain-service */
5
+ export declare function requireNonEmptyString(value: string, name: string): string;
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ /** @riviere-role value-object */
3
+ export const nonEmptyStringSchema = z.string().trim().min(1);
4
+ /** @riviere-role domain-service */
5
+ export function requireNonEmptyString(value, name) {
6
+ const trimmed = value.trim();
7
+ if (trimmed.length === 0)
8
+ throw new TypeError(`${name} must be a non-empty string.`);
9
+ return trimmed;
10
+ }
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { nonEmptyStringSchema } from './non-empty-string.js';
2
3
  export const reflectionCategorySchema = z.enum([
3
4
  'state-efficiency',
4
5
  'review-rework',
@@ -7,13 +8,13 @@ export const reflectionCategorySchema = z.enum([
7
8
  'workflow-design',
8
9
  ]);
9
10
  export const reflectionConfidenceSchema = z.enum(['low', 'medium', 'high']);
10
- const evidenceBaseSchema = z.object({ label: z.string().min(1).optional(), });
11
+ const evidenceBaseSchema = z.object({ label: nonEmptyStringSchema.optional(), });
11
12
  export const reflectionEvidenceSchema = z.discriminatedUnion('kind', [
12
13
  evidenceBaseSchema.extend({
13
14
  kind: z.literal('state-period'),
14
- state: z.string().min(1),
15
- startedAt: z.string().min(1).optional(),
16
- endedAt: z.string().min(1).optional(),
15
+ state: nonEmptyStringSchema,
16
+ startedAt: nonEmptyStringSchema.optional(),
17
+ endedAt: nonEmptyStringSchema.optional(),
17
18
  }),
18
19
  evidenceBaseSchema.extend({
19
20
  kind: z.literal('event'),
@@ -26,8 +27,8 @@ export const reflectionEvidenceSchema = z.discriminatedUnion('kind', [
26
27
  }),
27
28
  evidenceBaseSchema.extend({
28
29
  kind: z.literal('journal-entry'),
29
- at: z.string().min(1),
30
- agentName: z.string().min(1).optional(),
30
+ at: nonEmptyStringSchema,
31
+ agentName: nonEmptyStringSchema.optional(),
31
32
  }),
32
33
  evidenceBaseSchema.extend({
33
34
  kind: z.literal('transcript-range'),
@@ -36,37 +37,37 @@ export const reflectionEvidenceSchema = z.discriminatedUnion('kind', [
36
37
  }),
37
38
  evidenceBaseSchema.extend({
38
39
  kind: z.literal('tool-activity'),
39
- state: z.string().min(1).optional(),
40
- toolName: z.string().min(1).optional(),
41
- metric: z.string().min(1).optional(),
40
+ state: nonEmptyStringSchema.optional(),
41
+ toolName: nonEmptyStringSchema.optional(),
42
+ metric: nonEmptyStringSchema.optional(),
42
43
  }),
43
44
  ]);
44
45
  export const reflectionFindingSchema = z.object({
45
- title: z.string().min(1),
46
+ title: nonEmptyStringSchema,
46
47
  category: reflectionCategorySchema,
47
- opportunity: z.string().min(1),
48
- likelyCause: z.string().min(1),
49
- suggestedChange: z.string().min(1),
50
- expectedImpact: z.string().min(1),
48
+ opportunity: nonEmptyStringSchema,
49
+ likelyCause: nonEmptyStringSchema,
50
+ suggestedChange: nonEmptyStringSchema,
51
+ expectedImpact: nonEmptyStringSchema,
51
52
  confidence: reflectionConfidenceSchema.optional(),
52
53
  evidence: z.array(reflectionEvidenceSchema).min(1),
53
54
  });
54
55
  export const reflectionPayloadSchema = z.object({
55
- summary: z.string().min(1).optional(),
56
+ summary: nonEmptyStringSchema.optional(),
56
57
  findings: z.array(reflectionFindingSchema).max(10),
57
58
  }).strict();
58
59
  export const recordReflectionInputSchema = z.object({
59
- label: z.string().min(1).optional(),
60
- agentName: z.string().min(1).optional(),
61
- sourceState: z.string().min(1).optional(),
60
+ label: nonEmptyStringSchema.optional(),
61
+ agentName: nonEmptyStringSchema.optional(),
62
+ sourceState: nonEmptyStringSchema.optional(),
62
63
  reflection: reflectionPayloadSchema,
63
64
  }).strict();
64
65
  export const storedReflectionSchema = z.object({
65
66
  id: z.number().int().positive(),
66
- sessionId: z.string().min(1),
67
- createdAt: z.string().min(1),
68
- label: z.string().min(1).optional(),
69
- agentName: z.string().min(1).optional(),
70
- sourceState: z.string().min(1).optional(),
67
+ sessionId: nonEmptyStringSchema,
68
+ createdAt: nonEmptyStringSchema,
69
+ label: nonEmptyStringSchema.optional(),
70
+ agentName: nonEmptyStringSchema.optional(),
71
+ sourceState: nonEmptyStringSchema.optional(),
71
72
  reflection: reflectionPayloadSchema,
72
73
  }).strict();
@@ -1,17 +1,18 @@
1
1
  import { z } from 'zod';
2
+ import { nonEmptyStringSchema } from './non-empty-string.js';
2
3
  const issueRecordedSchema = z.object({
3
4
  type: z.literal('issue-recorded'),
4
- at: z.string(),
5
+ at: nonEmptyStringSchema,
5
6
  issueNumber: z.number(),
6
7
  });
7
8
  const branchRecordedSchema = z.object({
8
9
  type: z.literal('branch-recorded'),
9
- at: z.string(),
10
- branch: z.string(),
10
+ at: nonEmptyStringSchema,
11
+ branch: nonEmptyStringSchema,
11
12
  });
12
13
  const prRecordedSchema = z.object({
13
14
  type: z.literal('pr-recorded'),
14
- at: z.string(),
15
+ at: nonEmptyStringSchema,
15
16
  prNumber: z.number(),
16
17
  });
17
18
  export const repositoryMetadataEventSchema = z.discriminatedUnion('type', [
@@ -1,18 +1,19 @@
1
1
  import { z } from 'zod';
2
- export const reviewTypeSchema = z.string().min(1);
2
+ import { nonEmptyStringSchema } from './non-empty-string.js';
3
+ export const reviewTypeSchema = nonEmptyStringSchema;
3
4
  export const reviewVerdictSchema = z.enum(['PASS', 'FAIL']);
4
5
  export const reviewFindingSeveritySchema = z.enum(['minor', 'major', 'critical']);
5
6
  export const reviewFindingStatusSchema = z.enum(['blocking', 'non-blocking', 'accepted-risk']);
6
7
  export const reviewFindingSchema = z.object({
7
- title: z.string().min(1).optional(),
8
+ title: nonEmptyStringSchema.optional(),
8
9
  severity: reviewFindingSeveritySchema.optional(),
9
10
  status: reviewFindingStatusSchema.optional(),
10
- rule: z.string().min(1).optional(),
11
- file: z.string().min(1).optional(),
11
+ rule: nonEmptyStringSchema.optional(),
12
+ file: nonEmptyStringSchema.optional(),
12
13
  startLine: z.number().int().positive().optional(),
13
14
  endLine: z.number().int().positive().optional(),
14
- details: z.string().min(1).optional(),
15
- recommendation: z.string().min(1).optional(),
15
+ details: nonEmptyStringSchema.optional(),
16
+ recommendation: nonEmptyStringSchema.optional(),
16
17
  }).strict().superRefine((finding, context) => {
17
18
  if (finding.title === undefined && finding.details === undefined && finding.rule === undefined) {
18
19
  context.addIssue({
@@ -38,24 +39,24 @@ export const reviewFindingSchema = z.object({
38
39
  });
39
40
  export const reviewPayloadSchema = z.object({
40
41
  verdict: reviewVerdictSchema,
41
- summary: z.string().min(1).optional(),
42
- branch: z.string().min(1).optional(),
42
+ summary: nonEmptyStringSchema.optional(),
43
+ branch: nonEmptyStringSchema.optional(),
43
44
  pullRequestNumber: z.number().int().positive().optional(),
44
45
  findings: z.array(reviewFindingSchema),
45
46
  }).strict();
46
47
  export const recordReviewInputSchema = reviewPayloadSchema.extend({
47
48
  reviewType: reviewTypeSchema,
48
- sourceState: z.string().min(1).optional(),
49
+ sourceState: nonEmptyStringSchema.optional(),
49
50
  }).strict();
50
51
  export const storedReviewSchema = recordReviewInputSchema.extend({
51
52
  id: z.number().int().positive(),
52
- sessionId: z.string().min(1),
53
- createdAt: z.string().min(1),
53
+ sessionId: nonEmptyStringSchema,
54
+ createdAt: nonEmptyStringSchema,
54
55
  }).strict();
55
- export const listedReviewSchema = storedReviewSchema.extend({ repository: z.string().min(1).optional() }).strict();
56
+ export const listedReviewSchema = storedReviewSchema.extend({ repository: nonEmptyStringSchema.optional() }).strict();
56
57
  export const reviewFiltersSchema = z.object({
57
- repository: z.string().min(1).optional(),
58
- branch: z.string().min(1).optional(),
58
+ repository: nonEmptyStringSchema.optional(),
59
+ branch: nonEmptyStringSchema.optional(),
59
60
  pullRequestNumber: z.number().int().positive().optional(),
60
61
  reviewType: reviewTypeSchema.optional(),
61
62
  verdict: reviewVerdictSchema.optional(),
@@ -10,4 +10,4 @@ export declare function buildProcedurePath<TStateName extends string>(engineDeps
10
10
  /** @riviere-role domain-service */
11
11
  export declare function readProcedure<TStateName extends string>(engineDeps: WorkflowEngineDeps, stateName: TStateName): string;
12
12
  /** @riviere-role domain-service */
13
- export declare function enrichSessionStartedEvents<TStateName extends string>(engineDeps: WorkflowEngineDeps, events: readonly BaseEvent[], transcriptPath: string, repository: string | undefined, currentState: TStateName, states: readonly string[]): readonly BaseEvent[];
13
+ export declare function enrichSessionStartedEvents<TStateName extends string>(engineDeps: WorkflowEngineDeps, events: readonly BaseEvent[], transcriptPath: string, repository: string, currentState: TStateName, states: readonly string[]): readonly BaseEvent[];
@@ -1,3 +1,4 @@
1
+ import { requireNonEmptyString } from './non-empty-string.js';
1
2
  /** @riviere-role domain-service */
2
3
  export function buildPrefixPattern(registry) {
3
4
  const prefixes = [];
@@ -21,14 +22,16 @@ export function readProcedure(engineDeps, stateName) {
21
22
  }
22
23
  /** @riviere-role domain-service */
23
24
  export function enrichSessionStartedEvents(engineDeps, events, transcriptPath, repository, currentState, states) {
25
+ const validRepository = requireNonEmptyString(repository, 'repository');
26
+ const validTranscriptPath = requireNonEmptyString(transcriptPath, 'transcriptPath');
24
27
  const enriched = events.map((event) => {
25
28
  if (event.type !== 'session-started') {
26
29
  return event;
27
30
  }
28
31
  return {
29
32
  ...event,
30
- transcriptPath,
31
- ...(repository === undefined ? {} : { repository }),
33
+ transcriptPath: validTranscriptPath,
34
+ repository: validRepository,
32
35
  currentState,
33
36
  states: [...states],
34
37
  };
@@ -39,8 +42,8 @@ export function enrichSessionStartedEvents(engineDeps, events, transcriptPath, r
39
42
  return [{
40
43
  type: 'session-started',
41
44
  at: engineDeps.now(),
42
- transcriptPath,
43
- ...(repository === undefined ? {} : { repository }),
45
+ transcriptPath: validTranscriptPath,
46
+ repository: validRepository,
44
47
  currentState,
45
48
  states: [...states],
46
49
  }, ...enriched];
@@ -23,7 +23,7 @@ export interface RehydratableWorkflow<TState extends BaseWorkflowState> {
23
23
  getState(): TState;
24
24
  appendEvent(event: BaseEvent): void;
25
25
  getPendingEvents(): readonly BaseEvent[];
26
- startSession(transcriptPath: string, repository: string | undefined): void;
26
+ startSession(transcriptPath: string, repository: string): void;
27
27
  getTranscriptPath(): string;
28
28
  registerAgent(agentType: string, agentId: string): PreconditionResult;
29
29
  handleTeammateIdle(agentName: string): PreconditionResult;
@@ -8,7 +8,7 @@ export declare class WorkflowEngine<TWorkflow extends RehydratableWorkflow<TStat
8
8
  private readonly engineDeps;
9
9
  private readonly workflowDeps;
10
10
  constructor(factory: WorkflowDefinition<TWorkflow, TState, TDeps, TStateName, TOperation>, engineDeps: WorkflowEngineDeps, workflowDeps: TDeps);
11
- startSession(sessionId: string, transcriptPath: string, repository?: string): EngineResult;
11
+ startSession(sessionId: string, transcriptPath: string, repository: string): EngineResult;
12
12
  transaction(sessionId: string, op: string, fn: (workflow: TWorkflow) => PreconditionResult): EngineResult;
13
13
  writeJournal(sessionId: string, agentName: string, content: string): EngineResult;
14
14
  transition(sessionId: string, target: TStateName): EngineResult;
@@ -7,6 +7,7 @@ import { engineEventSchema } from './engine-events.js';
7
7
  import { WorkflowStateError, } from './workflow-state.js';
8
8
  import { reduceWorkflowStateFromStoredEvents } from './workflow-state-reducer.js';
9
9
  import { serializeWorkflowState } from './workflow-state-serialization.js';
10
+ import { requireNonEmptyString } from './non-empty-string.js';
10
11
  /** @riviere-role domain-service */
11
12
  export class WorkflowEngine {
12
13
  factory;
@@ -18,7 +19,10 @@ export class WorkflowEngine {
18
19
  this.workflowDeps = workflowDeps;
19
20
  }
20
21
  startSession(sessionId, transcriptPath, repository) {
21
- if (this.engineDeps.store.hasSessionStarted(sessionId)) {
22
+ const validSessionId = requireNonEmptyString(sessionId, 'sessionId');
23
+ const validTranscriptPath = requireNonEmptyString(transcriptPath, 'transcriptPath');
24
+ const validRepository = requireNonEmptyString(repository, 'repository');
25
+ if (this.engineDeps.store.hasSessionStarted(validSessionId)) {
22
26
  return {
23
27
  type: 'success',
24
28
  output: ''
@@ -26,12 +30,11 @@ export class WorkflowEngine {
26
30
  }
27
31
  const initialState = this.factory.initialState();
28
32
  const workflow = this.factory.buildWorkflow(initialState, this.workflowDeps);
29
- const resolvedRepository = repository ?? this.engineDeps.getRepositoryName?.();
30
- workflow.startSession(transcriptPath, resolvedRepository);
33
+ workflow.startSession(validTranscriptPath, validRepository);
31
34
  const registry = this.factory.getRegistry();
32
35
  const stateNames = Object.keys(registry);
33
- const pendingEvents = enrichSessionStartedEvents(this.engineDeps, workflow.getPendingEvents(), transcriptPath, resolvedRepository, initialState.currentStateMachineState, stateNames);
34
- this.engineDeps.store.appendEvents(sessionId, this.wrapEvents(pendingEvents, initialState));
36
+ const pendingEvents = enrichSessionStartedEvents(this.engineDeps, workflow.getPendingEvents(), validTranscriptPath, validRepository, initialState.currentStateMachineState, stateNames);
37
+ this.engineDeps.store.appendEvents(validSessionId, this.wrapEvents(pendingEvents, initialState));
35
38
  const procedureContent = this.engineDeps.readFile(buildProcedurePath(this.engineDeps, initialState.currentStateMachineState));
36
39
  const expectedPrefix = getExpectedPrefix(initialState.currentStateMachineState, registry);
37
40
  return {
@@ -256,11 +256,26 @@ function createEngine() {
256
256
  };
257
257
  }
258
258
  describe('WorkflowEngine platform-owned events', () => {
259
+ it.each([
260
+ ['repository', ''],
261
+ ['repository', ' '],
262
+ ['transcriptPath', ''],
263
+ ['transcriptPath', ' '],
264
+ ['sessionId', ''],
265
+ ['sessionId', ' '],
266
+ ])('rejects empty %s before persisting a session', (field, value) => {
267
+ const { engine, store, } = createEngine();
268
+ const sessionId = field === 'sessionId' ? value : 'session-1';
269
+ const transcriptPath = field === 'transcriptPath' ? value : '/transcripts/session-1.jsonl';
270
+ const repository = field === 'repository' ? value : 'test/repo';
271
+ expect(() => engine.startSession(sessionId, transcriptPath, repository)).toThrow(`${field} must be a non-empty string.`);
272
+ expect(store.readEvents('session-1')).toStrictEqual([]);
273
+ });
259
274
  it('persists journal and write-check events without routing them through the consumer workflow', () => {
260
275
  const { engine, store, } = createEngine();
261
- engine.startSession('session-1', '/transcripts/session-1.jsonl');
276
+ engine.startSession('session-1', '/transcripts/session-1.jsonl', 'test/repo');
262
277
  const journalResult = engine.writeJournal('session-1', 'gpt-5.4', 'Captured planning context.');
263
- const writeCheckResult = engine.checkWrite('session-1', 'Read', '', () => true);
278
+ const writeCheckResult = engine.checkWrite('session-1', 'Read', '/src/example.ts', () => true);
264
279
  const stateResult = engine.getState('session-1');
265
280
  expect(journalResult.type).toBe('success');
266
281
  expect(writeCheckResult).toStrictEqual({
@@ -181,7 +181,7 @@ const engineDeps = {
181
181
  it('reinserts the current procedure when identity verification fails', () => {
182
182
  const emptyWorkflowDeps = {};
183
183
  const engine = new WorkflowEngine(workflowDefinition, engineDeps, emptyWorkflowDeps);
184
- engine.startSession('session-1', '/transcripts/session-1.jsonl');
184
+ engine.startSession('session-1', '/transcripts/session-1.jsonl', 'test/repo');
185
185
  const result = engine.checkWrite('session-1', 'Write', '/workspace/note.md', () => true);
186
186
  const expectedOutput = [
187
187
  'Cannot write-check',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nt-ai-lab/deterministic-agent-workflow-engine",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {