@you-agent-factory/factory-emulator 0.0.0 → 0.0.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.
Files changed (69) hide show
  1. package/LICENSE.md +19 -1
  2. package/README.md +305 -286
  3. package/dist/compatibility.d.ts +19 -0
  4. package/dist/compatibility.js +156 -0
  5. package/dist/event-sink.d.ts +29 -0
  6. package/dist/event-sink.js +58 -0
  7. package/dist/generated/scenario-schema.d.ts +268 -0
  8. package/dist/generated/scenario-schema.js +319 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +8 -0
  11. package/dist/logical-move.d.ts +10 -0
  12. package/dist/logical-move.js +18 -0
  13. package/dist/recording-sink.d.ts +22 -0
  14. package/dist/recording-sink.js +122 -0
  15. package/dist/runtime-reference-conformance.d.ts +20 -0
  16. package/dist/runtime-reference-conformance.js +138 -0
  17. package/dist/runtime-reference-evidence.d.ts +9 -0
  18. package/dist/runtime-reference-evidence.js +193 -0
  19. package/dist/runtime-reference-fixtures.d.ts +7 -0
  20. package/dist/runtime-reference-fixtures.js +308 -0
  21. package/dist/runtime-reference.d.ts +48 -0
  22. package/dist/runtime-reference.js +143 -0
  23. package/dist/scenario-contracts.d.ts +81 -0
  24. package/dist/scenario-contracts.js +11 -0
  25. package/dist/scenario.d.ts +271 -0
  26. package/dist/scenario.js +333 -0
  27. package/dist/scheduler.d.ts +24 -0
  28. package/dist/scheduler.js +104 -0
  29. package/dist/session-contracts.d.ts +262 -0
  30. package/dist/session-contracts.js +74 -0
  31. package/dist/session.d.ts +4 -0
  32. package/dist/session.js +1490 -0
  33. package/dist/submission-replay.d.ts +14 -0
  34. package/dist/submission-replay.js +96 -0
  35. package/dist/submission-validation.d.ts +3 -0
  36. package/dist/submission-validation.js +113 -0
  37. package/examples/customer-support.scenario.v1.json +45 -0
  38. package/package.json +44 -22
  39. package/schema/scenario.schema.json +171 -0
  40. package/generated/factory-emulator-scenario.schema.json +0 -191
  41. package/generated/factory.schema.json +0 -2606
  42. package/src/contracts.ts +0 -41
  43. package/src/data-error.js +0 -21
  44. package/src/data-only.js +0 -208
  45. package/src/emulator.js +0 -195
  46. package/src/emulator.ts +0 -86
  47. package/src/examples.js +0 -86
  48. package/src/examples.ts +0 -11
  49. package/src/generated/factory-schema.d.ts +0 -3
  50. package/src/generated/factory-schema.js +0 -5
  51. package/src/generated/scenario-schema.d.ts +0 -4
  52. package/src/generated/scenario-schema.js +0 -6
  53. package/src/generated/scenario.ts +0 -103
  54. package/src/identity.js +0 -55
  55. package/src/index.js +0 -34
  56. package/src/index.ts +0 -102
  57. package/src/limits.js +0 -125
  58. package/src/parser.js +0 -113
  59. package/src/parser.ts +0 -56
  60. package/src/scheduler.js +0 -374
  61. package/src/semantics.js +0 -354
  62. package/src/semantics.ts +0 -38
  63. package/src/session.js +0 -1201
  64. package/src/session.ts +0 -324
  65. package/src/sinks.js +0 -118
  66. package/src/sinks.ts +0 -56
  67. package/src/support.js +0 -334
  68. package/src/support.ts +0 -15
  69. package/src/virtual-time.js +0 -36
@@ -0,0 +1,14 @@
1
+ import type { FactoryEvent } from "@you-agent-factory/client";
2
+ import type { FactoryEmulatorNormalizedRelation } from "./session-contracts.js";
3
+ export interface FactoryEmulatorReplayedWork {
4
+ readonly submissionId: string;
5
+ readonly workId: string;
6
+ readonly requestId?: string;
7
+ readonly traceId?: string;
8
+ readonly workType?: string;
9
+ readonly state?: string;
10
+ readonly input?: unknown;
11
+ readonly relations?: readonly FactoryEmulatorNormalizedRelation[];
12
+ }
13
+ /** Reapply canonical submission events into detached Work and relationship state. */
14
+ export declare function replayFactoryEmulatorSubmissions(events: readonly FactoryEvent[]): readonly FactoryEmulatorReplayedWork[];
@@ -0,0 +1,96 @@
1
+ function record(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value)
3
+ ? value
4
+ : undefined;
5
+ }
6
+ function normalizedRelation(value, worksByName) {
7
+ const relation = record(value);
8
+ if (relation?.type !== "DEPENDS_ON" ||
9
+ typeof relation.sourceWorkName !== "string" ||
10
+ typeof relation.targetWorkName !== "string") {
11
+ return undefined;
12
+ }
13
+ const targetWorkId = typeof relation.targetWorkId === "string"
14
+ ? relation.targetWorkId
15
+ : worksByName.get(relation.targetWorkName)?.workId;
16
+ if (targetWorkId === undefined)
17
+ return undefined;
18
+ return {
19
+ type: "DEPENDS_ON",
20
+ sourceWorkName: relation.sourceWorkName,
21
+ targetWorkName: relation.targetWorkName,
22
+ targetWorkId,
23
+ requiredState: typeof relation.requiredState === "string"
24
+ ? relation.requiredState
25
+ : "complete",
26
+ };
27
+ }
28
+ function applyRelation(value, worksByName) {
29
+ const relation = normalizedRelation(value, worksByName);
30
+ if (relation === undefined)
31
+ return;
32
+ const source = worksByName.get(relation.sourceWorkName);
33
+ if (source === undefined)
34
+ return;
35
+ const key = (candidate) => `${candidate.type}\u0000${candidate.sourceWorkName}\u0000${candidate.targetWorkId}\u0000${candidate.requiredState}`;
36
+ const existing = source.relations.findIndex((candidate) => key(candidate) === key(relation));
37
+ if (existing === -1)
38
+ source.relations.push(relation);
39
+ else
40
+ source.relations[existing] = relation;
41
+ }
42
+ /** Reapply canonical submission events into detached Work and relationship state. */
43
+ export function replayFactoryEmulatorSubmissions(events) {
44
+ const works = [];
45
+ const worksByName = new Map();
46
+ for (const event of events) {
47
+ const payload = record(event.payload);
48
+ if (event.type === "WORK_REQUEST") {
49
+ const eventWorks = Array.isArray(payload?.works) ? payload.works : [];
50
+ for (const [index, value] of eventWorks.entries()) {
51
+ const work = record(value);
52
+ if (typeof work?.name !== "string" || typeof work.workId !== "string") {
53
+ continue;
54
+ }
55
+ const state = record(work.state);
56
+ const replayed = {
57
+ submissionId: work.name,
58
+ workId: work.workId,
59
+ ...(typeof work.requestId === "string"
60
+ ? { requestId: work.requestId }
61
+ : event.context.requestId
62
+ ? { requestId: event.context.requestId }
63
+ : {}),
64
+ ...(typeof work.traceId === "string"
65
+ ? { traceId: work.traceId }
66
+ : event.context.traceIds?.[index]
67
+ ? { traceId: event.context.traceIds[index] }
68
+ : {}),
69
+ ...(typeof work.workTypeName === "string"
70
+ ? { workType: work.workTypeName }
71
+ : {}),
72
+ ...(typeof state?.name === "string" ? { state: state.name } : {}),
73
+ ...("payload" in work ? { input: work.payload } : {}),
74
+ relations: [],
75
+ };
76
+ works.push(replayed);
77
+ worksByName.set(replayed.submissionId, replayed);
78
+ }
79
+ for (const relation of Array.isArray(payload?.relations)
80
+ ? payload.relations
81
+ : []) {
82
+ applyRelation(relation, worksByName);
83
+ }
84
+ continue;
85
+ }
86
+ if (event.type === "RELATIONSHIP_CHANGE_REQUEST") {
87
+ applyRelation(record(payload?.relation), worksByName);
88
+ }
89
+ }
90
+ return works.map(({ relations, ...work }) => ({
91
+ ...work,
92
+ ...(relations.length === 0
93
+ ? {}
94
+ : { relations: relations.map((relation) => ({ ...relation })) }),
95
+ }));
96
+ }
@@ -0,0 +1,3 @@
1
+ import type { FactoryDefinition } from "@you-agent-factory/client";
2
+ import type { FactoryEmulatorScenarioIssue, FactoryEmulatorSubmissionBatch } from "./scenario-contracts.js";
3
+ export declare function validateDependencyRelationships(initial: FactoryEmulatorSubmissionBatch, factory: FactoryDefinition, issues: FactoryEmulatorScenarioIssue[]): void;
@@ -0,0 +1,113 @@
1
+ export function validateDependencyRelationships(initial, factory, issues) {
2
+ const relations = initial.relations ?? [];
3
+ const workByName = new Map(initial.works.map((work) => [work.name, work]));
4
+ const targetsBySource = new Map();
5
+ const relationKeys = new Map();
6
+ for (const [index, relation] of relations.entries()) {
7
+ const path = ["initialSubmissions", "relations", index];
8
+ if (relation.type !== "DEPENDS_ON") {
9
+ issues.push({
10
+ category: "semantic",
11
+ code: "invalid_initial_submission_relationship",
12
+ path: [...path, "type"],
13
+ message: `Relationship type ${relation.type} is unsupported; the emulator accepts only DEPENDS_ON.`,
14
+ });
15
+ }
16
+ const source = workByName.get(relation.sourceWorkName);
17
+ const target = workByName.get(relation.targetWorkName);
18
+ validateEndpoints(relation, source, target, path, issues);
19
+ validateRequiredState(relation, target, factory, path, issues);
20
+ const key = `${relation.type}\u0000${relation.sourceWorkName}\u0000${relation.targetWorkName}`;
21
+ const indexes = relationKeys.get(key) ?? [];
22
+ indexes.push(index);
23
+ relationKeys.set(key, indexes);
24
+ if (relation.type === "DEPENDS_ON" &&
25
+ source !== undefined &&
26
+ target !== undefined &&
27
+ source !== target) {
28
+ const targets = targetsBySource.get(source.name) ?? [];
29
+ targets.push(target.name);
30
+ targetsBySource.set(source.name, targets);
31
+ }
32
+ }
33
+ addDuplicateRelationIssues(relationKeys, issues);
34
+ addCycleIssues(relations, targetsBySource, issues);
35
+ }
36
+ function validateEndpoints(relation, source, target, path, issues) {
37
+ if (source === undefined) {
38
+ issues.push({
39
+ category: "semantic",
40
+ code: "invalid_initial_submission_relationship",
41
+ path: [...path, "sourceWorkName"],
42
+ message: `Source Work ${relation.sourceWorkName} is not in this submission batch.`,
43
+ });
44
+ }
45
+ if (target === undefined) {
46
+ issues.push({
47
+ category: "semantic",
48
+ code: "invalid_initial_submission_relationship",
49
+ path: [...path, "targetWorkName"],
50
+ message: `Target Work ${relation.targetWorkName} is not in this submission batch.`,
51
+ });
52
+ }
53
+ if (relation.sourceWorkName === relation.targetWorkName) {
54
+ issues.push({
55
+ category: "semantic",
56
+ code: "invalid_initial_submission_relationship",
57
+ path,
58
+ message: "A Work item cannot depend on itself.",
59
+ });
60
+ }
61
+ }
62
+ function validateRequiredState(relation, target, factory, path, issues) {
63
+ if (target === undefined)
64
+ return;
65
+ const requiredState = relation.requiredState ?? "complete";
66
+ const targetStates = factory.workTypes
67
+ ?.find(({ name }) => name === target.workType)
68
+ ?.states.map(({ name }) => name);
69
+ if (!targetStates?.includes(requiredState)) {
70
+ issues.push({
71
+ category: "semantic",
72
+ code: "invalid_initial_submission_relationship",
73
+ path: [...path, "requiredState"],
74
+ message: `Required state ${requiredState} is not declared by target Work type ${target.workType}.`,
75
+ });
76
+ }
77
+ }
78
+ function addDuplicateRelationIssues(relationKeys, issues) {
79
+ for (const indexes of relationKeys.values()) {
80
+ if (indexes.length < 2)
81
+ continue;
82
+ for (const index of indexes) {
83
+ issues.push({
84
+ category: "semantic",
85
+ code: "duplicate_identity",
86
+ path: ["initialSubmissions", "relations", index],
87
+ message: `Submission relationship is duplicated at indexes ${indexes.join(", ")}.`,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ function addCycleIssues(relations, targetsBySource, issues) {
93
+ const reaches = (current, goal, visited) => {
94
+ if (current === goal)
95
+ return true;
96
+ if (visited.has(current))
97
+ return false;
98
+ visited.add(current);
99
+ return (targetsBySource.get(current) ?? []).some((target) => reaches(target, goal, visited));
100
+ };
101
+ relations.forEach((relation, index) => {
102
+ if (relation.type === "DEPENDS_ON" &&
103
+ relation.sourceWorkName !== relation.targetWorkName &&
104
+ reaches(relation.targetWorkName, relation.sourceWorkName, new Set())) {
105
+ issues.push({
106
+ category: "semantic",
107
+ code: "invalid_initial_submission_relationship",
108
+ path: ["initialSubmissions", "relations", index],
109
+ message: "DEPENDS_ON relationships must be acyclic.",
110
+ });
111
+ }
112
+ });
113
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "schemaVersion": "factory-emulator-scenario/v1",
3
+ "id": "customer-support-happy-path",
4
+ "factory": {
5
+ "name": "customer-support"
6
+ },
7
+ "seed": "customer-support-seed-001",
8
+ "startAt": "2026-07-18T16:00:00.000Z",
9
+ "rules": [
10
+ {
11
+ "id": "triage-new-ticket",
12
+ "selector": {
13
+ "workstation": "triage",
14
+ "worker": "support-agent",
15
+ "input": {
16
+ "workType": "ticket",
17
+ "state": "new"
18
+ }
19
+ },
20
+ "cursor": {
21
+ "scope": "lineage",
22
+ "input": "rootWorkId"
23
+ },
24
+ "outcomes": [
25
+ {
26
+ "result": "accepted",
27
+ "durationMs": 25,
28
+ "output": "Ticket classified"
29
+ }
30
+ ],
31
+ "exhaustion": "repeat-last"
32
+ }
33
+ ],
34
+ "unmatched": {
35
+ "behavior": "error"
36
+ },
37
+ "initialSubmissions": [
38
+ {
39
+ "name": "ticket-001",
40
+ "workType": "ticket",
41
+ "state": "new",
42
+ "input": "Customer cannot sign in"
43
+ }
44
+ ]
45
+ }
package/package.json CHANGED
@@ -1,44 +1,66 @@
1
1
  {
2
2
  "name": "@you-agent-factory/factory-emulator",
3
- "version": "0.0.0",
4
- "description": "Deterministic scenario and event sink contracts for Factory emulator hosts.",
3
+ "version": "0.0.1",
4
+ "description": "Deterministic Factory emulator contracts and caller-owned event sinks.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/portpowered/you-agent-factory.git"
8
+ "url": "git+https://github.com/portpowered/you-agent-factory.git",
9
+ "directory": "ui/packages/factory-emulator"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
9
13
  },
10
- "type": "module",
11
14
  "files": [
12
- "README.md",
15
+ "dist",
16
+ "examples",
17
+ "schema",
13
18
  "LICENSE.md",
14
- "generated",
15
- "src"
19
+ "README.md"
16
20
  ],
17
- "types": "./src/index.ts",
21
+ "type": "module",
22
+ "sideEffects": false,
18
23
  "exports": {
19
24
  ".": {
20
- "types": "./src/index.ts",
21
- "import": "./src/index.js"
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "default": "./dist/index.js"
22
28
  },
23
- "./schema": "./generated/factory-emulator-scenario.schema.json",
24
- "./generated/types": {
25
- "types": "./src/generated/scenario.ts"
26
- }
29
+ "./schema": "./schema/scenario.schema.json",
30
+ "./examples/customer-support.scenario.v1.json": "./examples/customer-support.scenario.v1.json"
27
31
  },
28
- "peerDependencies": {
29
- "@you-agent-factory/client": "*"
32
+ "scripts": {
33
+ "build": "node scripts/build-package.mjs",
34
+ "check:changed-paths": "node scripts/check-changed-paths.mjs",
35
+ "check:generated": "node scripts/generate-scenario-schema.mjs --check",
36
+ "check:installed-consumer": "node scripts/verify-installed-consumer.mjs",
37
+ "check:pack": "node scripts/verify-package-pack.mjs",
38
+ "check:package-boundary": "node scripts/check-package-boundary.mjs",
39
+ "check:runtime-reference-conformance": "node scripts/check-runtime-reference-conformance.mjs",
40
+ "generate": "node scripts/generate-scenario-schema.mjs",
41
+ "lint": "biome check src scripts vitest.config.ts",
42
+ "prepack": "bun run build",
43
+ "test": "vitest run --config vitest.config.ts",
44
+ "typecheck": "tsc -p tsconfig.json --noEmit --pretty false",
45
+ "verify": "bun run verify:release && bun run check:changed-paths",
46
+ "verify:release": "bun run check:generated && bun run typecheck && bun run lint && bun run test && bun run build && bun run check:runtime-reference-conformance && bun run check:package-boundary && bun run check:pack && bun run check:installed-consumer"
30
47
  },
31
48
  "dependencies": {
32
49
  "ajv": "^8.20.0",
33
50
  "ajv-formats": "^3.0.1"
34
51
  },
52
+ "peerDependencies": {
53
+ "@you-agent-factory/client": "0.0.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "@you-agent-factory/client": {
57
+ "optional": true
58
+ }
59
+ },
35
60
  "devDependencies": {
61
+ "@biomejs/biome": "^2.5.4",
36
62
  "@you-agent-factory/client": "file:../client",
37
- "typescript": "~5.9.3"
38
- },
39
- "scripts": {
40
- "generate": "node ../../scripts/generate-emulator.mjs",
41
- "test": "node --test --test-concurrency=1 test/*.test.mjs",
42
- "typecheck": "tsc --project tsconfig.json"
63
+ "typescript": "~5.9.3",
64
+ "vitest": "4.1.3"
43
65
  }
44
66
  }
@@ -0,0 +1,171 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://you-agent-factory.dev/schemas/ui/factory-emulator/scenario/v1",
4
+ "title": "Factory Emulator Scenario v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schemaVersion",
9
+ "id",
10
+ "factory",
11
+ "seed",
12
+ "startAt",
13
+ "rules",
14
+ "unmatched"
15
+ ],
16
+ "properties": {
17
+ "schemaVersion": { "const": "factory-emulator-scenario/v1" },
18
+ "id": { "$ref": "#/$defs/stableIdentity" },
19
+ "factory": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "required": ["name"],
23
+ "properties": { "name": { "$ref": "#/$defs/nonEmptyText128" } }
24
+ },
25
+ "seed": { "type": "string", "minLength": 1, "maxLength": 256 },
26
+ "startAt": {
27
+ "type": "string",
28
+ "format": "date-time",
29
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$"
30
+ },
31
+ "rules": {
32
+ "type": "array",
33
+ "items": { "$ref": "#/$defs/rule" }
34
+ },
35
+ "unmatched": { "$ref": "#/$defs/unmatched" },
36
+ "initialSubmissions": {
37
+ "oneOf": [
38
+ {
39
+ "type": "array",
40
+ "items": { "$ref": "#/$defs/initialSubmission" }
41
+ },
42
+ { "$ref": "#/$defs/submissionBatch" }
43
+ ]
44
+ }
45
+ },
46
+ "$defs": {
47
+ "stableIdentity": {
48
+ "type": "string",
49
+ "minLength": 1,
50
+ "maxLength": 128,
51
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
52
+ },
53
+ "nonEmptyText128": { "type": "string", "minLength": 1, "maxLength": 128 },
54
+ "plainText": { "type": "string", "maxLength": 65536 },
55
+ "feedbackText": { "type": "string", "maxLength": 4096 },
56
+ "activityLabel": { "type": "string", "minLength": 1, "maxLength": 120 },
57
+ "inputSelector": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "properties": {
61
+ "workType": { "$ref": "#/$defs/nonEmptyText128" },
62
+ "state": { "$ref": "#/$defs/nonEmptyText128" },
63
+ "name": { "$ref": "#/$defs/nonEmptyText128" }
64
+ }
65
+ },
66
+ "selector": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "properties": {
70
+ "workstation": { "$ref": "#/$defs/nonEmptyText128" },
71
+ "worker": { "$ref": "#/$defs/nonEmptyText128" },
72
+ "input": { "$ref": "#/$defs/inputSelector" }
73
+ }
74
+ },
75
+ "cursor": {
76
+ "type": "object",
77
+ "additionalProperties": false,
78
+ "required": ["scope", "input"],
79
+ "properties": {
80
+ "scope": { "const": "lineage" },
81
+ "input": { "const": "rootWorkId" }
82
+ }
83
+ },
84
+ "outcome": {
85
+ "type": "object",
86
+ "additionalProperties": false,
87
+ "required": ["result", "durationMs"],
88
+ "properties": {
89
+ "result": { "enum": ["accepted", "continued", "rejected", "failed"] },
90
+ "durationMs": { "type": "number", "minimum": 0 },
91
+ "output": { "$ref": "#/$defs/plainText" },
92
+ "feedback": { "$ref": "#/$defs/feedbackText" },
93
+ "activityLabel": { "$ref": "#/$defs/activityLabel" },
94
+ "error": { "$ref": "#/$defs/feedbackText" }
95
+ }
96
+ },
97
+ "rule": {
98
+ "type": "object",
99
+ "additionalProperties": false,
100
+ "required": ["id", "selector", "cursor", "outcomes", "exhaustion"],
101
+ "properties": {
102
+ "id": { "$ref": "#/$defs/stableIdentity" },
103
+ "selector": { "$ref": "#/$defs/selector" },
104
+ "cursor": { "$ref": "#/$defs/cursor" },
105
+ "outcomes": {
106
+ "type": "array",
107
+ "minItems": 1,
108
+ "items": { "$ref": "#/$defs/outcome" }
109
+ },
110
+ "exhaustion": { "enum": ["repeat-last", "fail"] }
111
+ }
112
+ },
113
+ "unmatched": {
114
+ "oneOf": [
115
+ {
116
+ "type": "object",
117
+ "additionalProperties": false,
118
+ "required": ["behavior"],
119
+ "properties": { "behavior": { "const": "error" } }
120
+ },
121
+ {
122
+ "type": "object",
123
+ "additionalProperties": false,
124
+ "required": ["behavior", "outcome"],
125
+ "properties": {
126
+ "behavior": { "const": "outcome" },
127
+ "outcome": { "$ref": "#/$defs/outcome" }
128
+ }
129
+ }
130
+ ]
131
+ },
132
+ "initialSubmission": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "required": ["name", "workType", "state"],
136
+ "properties": {
137
+ "name": { "$ref": "#/$defs/stableIdentity" },
138
+ "workType": { "$ref": "#/$defs/nonEmptyText128" },
139
+ "state": { "$ref": "#/$defs/nonEmptyText128" },
140
+ "input": { "$ref": "#/$defs/plainText" },
141
+ "parent": { "$ref": "#/$defs/stableIdentity" }
142
+ }
143
+ },
144
+ "submissionRelation": {
145
+ "type": "object",
146
+ "additionalProperties": false,
147
+ "required": ["type", "sourceWorkName", "targetWorkName"],
148
+ "properties": {
149
+ "type": { "enum": ["DEPENDS_ON", "PARENT_CHILD", "SPAWNED_BY"] },
150
+ "sourceWorkName": { "$ref": "#/$defs/stableIdentity" },
151
+ "targetWorkName": { "$ref": "#/$defs/stableIdentity" },
152
+ "requiredState": { "$ref": "#/$defs/nonEmptyText128" }
153
+ }
154
+ },
155
+ "submissionBatch": {
156
+ "type": "object",
157
+ "additionalProperties": false,
158
+ "required": ["works"],
159
+ "properties": {
160
+ "works": {
161
+ "type": "array",
162
+ "items": { "$ref": "#/$defs/initialSubmission" }
163
+ },
164
+ "relations": {
165
+ "type": "array",
166
+ "items": { "$ref": "#/$defs/submissionRelation" }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }