@you-agent-factory/factory-replay 0.0.0 → 0.0.2

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/replay.js ADDED
@@ -0,0 +1,144 @@
1
+ function cloneFactoryEvent(event) {
2
+ return JSON.parse(JSON.stringify(event));
3
+ }
4
+ function canonicalValue(value) {
5
+ if (Array.isArray(value)) {
6
+ return `[${value.map(canonicalValue).join(",")}]`;
7
+ }
8
+ if (value !== null && typeof value === "object") {
9
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
10
+ return `{${entries
11
+ .map(([key, item]) => `${JSON.stringify(key)}:${canonicalValue(item)}`)
12
+ .join(",")}}`;
13
+ }
14
+ return JSON.stringify(value) ?? "undefined";
15
+ }
16
+ function compareAcceptedEvents(left, right) {
17
+ const tickDifference = left.context.tick - right.context.tick;
18
+ if (tickDifference !== 0)
19
+ return tickDifference;
20
+ const effectiveSequenceDifference = (left.context.sessionSequence ?? left.context.sequence) -
21
+ (right.context.sessionSequence ?? right.context.sequence);
22
+ if (effectiveSequenceDifference !== 0)
23
+ return effectiveSequenceDifference;
24
+ const eventLogSequenceDifference = left.context.sequence - right.context.sequence;
25
+ if (eventLogSequenceDifference !== 0)
26
+ return eventLogSequenceDifference;
27
+ const timeDifference = left.context.eventTime.localeCompare(right.context.eventTime);
28
+ if (timeDifference !== 0)
29
+ return timeDifference;
30
+ const idDifference = left.id.localeCompare(right.id);
31
+ if (idDifference !== 0)
32
+ return idDifference;
33
+ return canonicalValue(left).localeCompare(canonicalValue(right));
34
+ }
35
+ /**
36
+ * Return one immutable, canonically ordered accepted history. When an ID is
37
+ * repeated, the canonically earliest event owns that ID.
38
+ */
39
+ export function canonicalizeFactoryEvents(events) {
40
+ const acceptedEventIds = new Set();
41
+ return [...events].sort(compareAcceptedEvents).filter((event) => {
42
+ if (acceptedEventIds.has(event.id))
43
+ return false;
44
+ acceptedEventIds.add(event.id);
45
+ return true;
46
+ });
47
+ }
48
+ /** Reconstruct reducer-owned state for a current or fixed logical tick. */
49
+ export function initializeFactoryReplay(input) {
50
+ const events = canonicalizeFactoryEvents(input.events);
51
+ const latestTick = events.reduce((latest, event) => Math.max(latest, event.context.tick), 0);
52
+ const selectedTick = input.selection.mode === "current" ? latestTick : input.selection.tick;
53
+ const appliedEvents = events.filter((event) => event.context.tick <= selectedTick);
54
+ let state = input.reducer.createState(selectedTick);
55
+ for (const event of appliedEvents) {
56
+ state = input.reducer.applyEvent(state, event);
57
+ }
58
+ return {
59
+ acceptedEventIds: new Set(appliedEvents.map(({ id }) => id)),
60
+ appliedEvents,
61
+ events,
62
+ latestTick,
63
+ selectedTick,
64
+ selection: input.selection,
65
+ state,
66
+ };
67
+ }
68
+ /** Advance an immutable checkpoint with unseen accepted events through a tick. */
69
+ export function advanceFactoryReplayCheckpoint(input) {
70
+ const checkpointEventIds = new Set(input.checkpoint.acceptedEventIds);
71
+ const eligibleTail = canonicalizeFactoryEvents(input.tail).filter((event) => event.context.tick <= input.tick && !checkpointEventIds.has(event.id));
72
+ const events = canonicalizeFactoryEvents([
73
+ ...input.checkpoint.appliedEvents.map(cloneFactoryEvent),
74
+ ...eligibleTail.map(cloneFactoryEvent),
75
+ ]).filter((event) => event.context.tick <= input.tick);
76
+ let state = input.reducer.createState(input.tick);
77
+ for (const event of events) {
78
+ state = input.reducer.applyEvent(state, event);
79
+ }
80
+ const acceptedEventIds = new Set(events.map(({ id }) => id));
81
+ return {
82
+ acceptedEventIds,
83
+ appliedEvents: [...events],
84
+ events,
85
+ latestTick: events.reduce((latest, event) => Math.max(latest, event.context.tick), 0),
86
+ selectedTick: input.tick,
87
+ selection: { mode: "fixed", tick: input.tick },
88
+ state,
89
+ };
90
+ }
91
+ /** Reconstruct reducer-owned state at one explicit logical tick. */
92
+ export function projectFactoryStateAtTick(input) {
93
+ return initializeFactoryReplay({
94
+ events: input.events,
95
+ reducer: input.reducer,
96
+ selection: { mode: "fixed", tick: input.tick },
97
+ });
98
+ }
99
+ /** Create an independent compact checkpoint from a selected-tick projection. */
100
+ export function createFactoryReplayWorldCheckpoint(result, cloneState) {
101
+ return {
102
+ acceptedEventIDs: result.appliedEvents.map((event) => event.id),
103
+ selectedTick: result.selectedTick,
104
+ state: cloneState(result.state),
105
+ };
106
+ }
107
+ /**
108
+ * Advance a compact checkpoint with an accepted tail without retaining the
109
+ * checkpoint's historical events. The explicit clone and tick adapters keep
110
+ * domain-state ownership with the consumer.
111
+ */
112
+ export function advanceFactoryReplay(input) {
113
+ const acceptedEventIDs = new Set(input.checkpoint.acceptedEventIDs);
114
+ const appliedEvents = canonicalizeFactoryEvents(input.events).filter((event) => {
115
+ if (acceptedEventIDs.has(event.id) ||
116
+ event.context.tick < input.checkpoint.selectedTick ||
117
+ event.context.tick > input.tick) {
118
+ return false;
119
+ }
120
+ acceptedEventIDs.add(event.id);
121
+ return true;
122
+ });
123
+ let state = input.setSelectedTick(input.cloneState(input.checkpoint.state), input.tick);
124
+ for (const event of appliedEvents) {
125
+ state = input.reducer.applyEvent(state, event);
126
+ }
127
+ return {
128
+ appliedEvents,
129
+ checkpoint: {
130
+ acceptedEventIDs: [...acceptedEventIDs],
131
+ selectedTick: input.tick,
132
+ state: input.cloneState(state),
133
+ },
134
+ latestTick: appliedEvents.reduce((latest, event) => Math.max(latest, event.context.tick), input.checkpoint.selectedTick),
135
+ selectedTick: input.tick,
136
+ state,
137
+ world: input.reducer.projectWorld(state),
138
+ };
139
+ }
140
+ /** Reconstruct a consumer-owned world at one explicit logical tick. */
141
+ export function projectFactoryWorldAtTick(input) {
142
+ const result = projectFactoryStateAtTick(input);
143
+ return { ...result, world: input.reducer.projectWorld(result.state) };
144
+ }
@@ -0,0 +1,3 @@
1
+ import type { FactoryTopologyConnectionCandidate, FactoryTopologyConnectionResult, FactoryTopologyNode } from "./topology-contract.js";
2
+ /** Validate and project one relationship using the public semantic vocabulary. */
3
+ export declare function projectFactoryTopologyConnection(nodes: readonly FactoryTopologyNode[], candidate: FactoryTopologyConnectionCandidate): FactoryTopologyConnectionResult;
@@ -0,0 +1,80 @@
1
+ import { FACTORY_TOPOLOGY_RELATIONSHIPS } from "./topology-contract.js";
2
+ function invalidEndpointIssue(candidate, endpoint, reason, expectedNodeKind, handleId) {
3
+ const connectionId = `${candidate.kind}:${candidate.sourceNodeId ?? candidate.sourceReference}->${candidate.targetNodeId ?? candidate.targetReference}`;
4
+ const nodeId = endpoint === "source" ? candidate.sourceNodeId : candidate.targetNodeId;
5
+ const id = `invalid-endpoint:${connectionId}:${endpoint}:${reason}`;
6
+ return {
7
+ code: "INVALID_CONNECTION_ENDPOINT",
8
+ connectionId,
9
+ connectionKind: candidate.kind,
10
+ endpoint,
11
+ endpointReason: reason,
12
+ expectedNodeKind,
13
+ handleId,
14
+ id,
15
+ message: `Invalid ${endpoint} endpoint for ${candidate.kind} connection ${connectionId}: ${reason}.`,
16
+ nodeId,
17
+ sourceReference: candidate.sourceReference,
18
+ targetReference: candidate.targetReference,
19
+ };
20
+ }
21
+ /** Validate and project one relationship using the public semantic vocabulary. */
22
+ export function projectFactoryTopologyConnection(nodes, candidate) {
23
+ if (!Object.hasOwn(FACTORY_TOPOLOGY_RELATIONSHIPS, candidate.kind)) {
24
+ const connectionId = `${candidate.kind}:${candidate.sourceNodeId ?? candidate.sourceReference}->${candidate.targetNodeId ?? candidate.targetReference}`;
25
+ return {
26
+ issue: {
27
+ code: "UNSUPPORTED_CONNECTION_KIND",
28
+ connectionId,
29
+ connectionKind: candidate.kind,
30
+ id: `unsupported-connection:${connectionId}`,
31
+ message: `Unsupported Factory topology connection kind ${candidate.kind}.`,
32
+ sourceReference: candidate.sourceReference,
33
+ targetReference: candidate.targetReference,
34
+ },
35
+ ok: false,
36
+ };
37
+ }
38
+ const kind = candidate.kind;
39
+ const relationship = FACTORY_TOPOLOGY_RELATIONSHIPS[kind];
40
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
41
+ for (const endpoint of ["source", "target"]) {
42
+ const endpointContract = relationship[endpoint];
43
+ const endpointNodeId = endpoint === "source" ? candidate.sourceNodeId : candidate.targetNodeId;
44
+ const node = endpointNodeId ? nodesById.get(endpointNodeId) : undefined;
45
+ if (!node) {
46
+ return {
47
+ issue: invalidEndpointIssue(candidate, endpoint, "MISSING_NODE", endpointContract.nodeKind, endpointContract.handleId),
48
+ ok: false,
49
+ };
50
+ }
51
+ if (node.kind !== endpointContract.nodeKind) {
52
+ return {
53
+ issue: invalidEndpointIssue(candidate, endpoint, "NODE_KIND_MISMATCH", endpointContract.nodeKind, endpointContract.handleId),
54
+ ok: false,
55
+ };
56
+ }
57
+ if (!node.handles.some((handle) => handle.id === endpointContract.handleId && handle.role === endpoint)) {
58
+ return {
59
+ issue: invalidEndpointIssue(candidate, endpoint, "MISSING_HANDLE", endpointContract.nodeKind, endpointContract.handleId),
60
+ ok: false,
61
+ };
62
+ }
63
+ }
64
+ const id = `${kind}:${candidate.sourceNodeId}->${candidate.targetNodeId}`;
65
+ return {
66
+ connection: {
67
+ id,
68
+ kind,
69
+ source: {
70
+ handleId: relationship.source.handleId,
71
+ nodeId: candidate.sourceNodeId,
72
+ },
73
+ target: {
74
+ handleId: relationship.target.handleId,
75
+ nodeId: candidate.targetNodeId,
76
+ },
77
+ },
78
+ ok: true,
79
+ };
80
+ }
@@ -0,0 +1,187 @@
1
+ import type { FactoryDefinition, FactoryEvent } from "@you-agent-factory/client";
2
+ export type FactoryTopologyNodeKind = "resource" | "worker" | "work-state" | "work-type" | "workstation";
3
+ /**
4
+ * The canonical relationship vocabulary used to declare node handles and
5
+ * project connection endpoints. Renderers can use this same value to render
6
+ * handles without maintaining a parallel mapping.
7
+ */
8
+ export declare const FACTORY_TOPOLOGY_RELATIONSHIPS: {
9
+ readonly "worker-assignment": {
10
+ readonly source: {
11
+ readonly handleId: "worker-assignment-source";
12
+ readonly nodeKind: "worker";
13
+ };
14
+ readonly target: {
15
+ readonly handleId: "worker-assignment-target";
16
+ readonly nodeKind: "workstation";
17
+ };
18
+ };
19
+ readonly "worker-resource": {
20
+ readonly source: {
21
+ readonly handleId: "worker-resource-source";
22
+ readonly nodeKind: "resource";
23
+ };
24
+ readonly target: {
25
+ readonly handleId: "worker-input-target";
26
+ readonly nodeKind: "worker";
27
+ };
28
+ };
29
+ readonly "workstation-input": {
30
+ readonly source: {
31
+ readonly handleId: "workstation-input-source";
32
+ readonly nodeKind: "work-state";
33
+ };
34
+ readonly target: {
35
+ readonly handleId: "workstation-input-target";
36
+ readonly nodeKind: "workstation";
37
+ };
38
+ };
39
+ readonly "workstation-on-continue": {
40
+ readonly source: {
41
+ readonly handleId: "workstation-on-continue-source";
42
+ readonly nodeKind: "workstation";
43
+ };
44
+ readonly target: {
45
+ readonly handleId: "work-state-input-target";
46
+ readonly nodeKind: "work-state";
47
+ };
48
+ };
49
+ readonly "workstation-on-failure": {
50
+ readonly source: {
51
+ readonly handleId: "workstation-on-failure-source";
52
+ readonly nodeKind: "workstation";
53
+ };
54
+ readonly target: {
55
+ readonly handleId: "work-state-input-target";
56
+ readonly nodeKind: "work-state";
57
+ };
58
+ };
59
+ readonly "workstation-on-rejection": {
60
+ readonly source: {
61
+ readonly handleId: "workstation-on-rejection-source";
62
+ readonly nodeKind: "workstation";
63
+ };
64
+ readonly target: {
65
+ readonly handleId: "work-state-input-target";
66
+ readonly nodeKind: "work-state";
67
+ };
68
+ };
69
+ readonly "workstation-output": {
70
+ readonly source: {
71
+ readonly handleId: "workstation-output-source";
72
+ readonly nodeKind: "workstation";
73
+ };
74
+ readonly target: {
75
+ readonly handleId: "work-state-input-target";
76
+ readonly nodeKind: "work-state";
77
+ };
78
+ };
79
+ readonly "workstation-resource": {
80
+ readonly source: {
81
+ readonly handleId: "workstation-resource-source";
82
+ readonly nodeKind: "resource";
83
+ };
84
+ readonly target: {
85
+ readonly handleId: "workstation-resource-target";
86
+ readonly nodeKind: "workstation";
87
+ };
88
+ };
89
+ readonly "work-type-state": {
90
+ readonly source: {
91
+ readonly handleId: "work-type-state-source";
92
+ readonly nodeKind: "work-type";
93
+ };
94
+ readonly target: {
95
+ readonly handleId: "work-type-state-target";
96
+ readonly nodeKind: "work-state";
97
+ };
98
+ };
99
+ };
100
+ export type FactoryTopologyConnectionKind = keyof typeof FACTORY_TOPOLOGY_RELATIONSHIPS;
101
+ export type FactoryTopologyHandleId = (typeof FACTORY_TOPOLOGY_RELATIONSHIPS)[FactoryTopologyConnectionKind]["source" | "target"]["handleId"];
102
+ export interface FactoryTopologyHandle {
103
+ id: FactoryTopologyHandleId;
104
+ role: "source" | "target";
105
+ }
106
+ interface FactoryTopologyNodeBase {
107
+ entityId: string;
108
+ handles: FactoryTopologyHandle[];
109
+ id: string;
110
+ kind: FactoryTopologyNodeKind;
111
+ label: string;
112
+ }
113
+ export interface FactoryResourceTopologyNode extends FactoryTopologyNodeBase {
114
+ capacity: number;
115
+ kind: "resource";
116
+ }
117
+ export interface FactoryWorkerTopologyNode extends FactoryTopologyNodeBase {
118
+ kind: "worker";
119
+ }
120
+ export interface FactoryWorkTypeTopologyNode extends FactoryTopologyNodeBase {
121
+ kind: "work-type";
122
+ }
123
+ export interface FactoryWorkStateTopologyNode extends FactoryTopologyNodeBase {
124
+ category: NonNullable<FactoryDefinition["workTypes"]>[number]["states"][number]["type"];
125
+ kind: "work-state";
126
+ workTypeId: string;
127
+ }
128
+ export interface FactoryWorkstationTopologyNode extends FactoryTopologyNodeBase {
129
+ kind: "workstation";
130
+ }
131
+ export type FactoryTopologyNode = FactoryResourceTopologyNode | FactoryWorkerTopologyNode | FactoryWorkStateTopologyNode | FactoryWorkTypeTopologyNode | FactoryWorkstationTopologyNode;
132
+ export interface FactoryTopologyConnectionEndpoint {
133
+ handleId: string;
134
+ nodeId: string;
135
+ }
136
+ export interface FactoryTopologyConnection {
137
+ id: string;
138
+ kind: FactoryTopologyConnectionKind;
139
+ source: FactoryTopologyConnectionEndpoint;
140
+ target: FactoryTopologyConnectionEndpoint;
141
+ }
142
+ export interface FactoryTopologyProjectionIssue {
143
+ code: "DUPLICATE_ENTITY_ID" | "INVALID_CONNECTION_ENDPOINT" | "MISSING_FACTORY" | "UNSUPPORTED_CONNECTION_KIND";
144
+ connectionId?: string;
145
+ connectionKind?: string;
146
+ endpoint?: "source" | "target";
147
+ endpointReason?: "MISSING_HANDLE" | "MISSING_NODE" | "NODE_KIND_MISMATCH";
148
+ expectedNodeKind?: FactoryTopologyNodeKind;
149
+ handleId?: string;
150
+ id: string;
151
+ message: string;
152
+ nodeId?: string;
153
+ sourceReference?: string;
154
+ targetReference?: string;
155
+ }
156
+ export interface FactoryTopologyProjection {
157
+ connections: FactoryTopologyConnection[];
158
+ issues: FactoryTopologyProjectionIssue[];
159
+ nodes: FactoryTopologyNode[];
160
+ ok: boolean;
161
+ selectedTick: number;
162
+ }
163
+ export interface FactoryTopologyConnectionCandidate {
164
+ kind: string;
165
+ sourceNodeId?: string;
166
+ sourceReference: string;
167
+ targetNodeId?: string;
168
+ targetReference: string;
169
+ }
170
+ export type FactoryTopologyConnectionResult = {
171
+ connection: FactoryTopologyConnection;
172
+ issue?: never;
173
+ ok: true;
174
+ } | {
175
+ connection?: never;
176
+ issue: FactoryTopologyProjectionIssue;
177
+ ok: false;
178
+ };
179
+ export interface FactoryTopologyProjectionInput {
180
+ factory?: FactoryDefinition;
181
+ selectedTick: number;
182
+ }
183
+ export interface FactoryTopologyAtTickInput {
184
+ events: readonly FactoryEvent[];
185
+ tick: number;
186
+ }
187
+ export {};
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The canonical relationship vocabulary used to declare node handles and
3
+ * project connection endpoints. Renderers can use this same value to render
4
+ * handles without maintaining a parallel mapping.
5
+ */
6
+ export const FACTORY_TOPOLOGY_RELATIONSHIPS = {
7
+ "worker-assignment": {
8
+ source: { handleId: "worker-assignment-source", nodeKind: "worker" },
9
+ target: {
10
+ handleId: "worker-assignment-target",
11
+ nodeKind: "workstation",
12
+ },
13
+ },
14
+ "worker-resource": {
15
+ source: { handleId: "worker-resource-source", nodeKind: "resource" },
16
+ target: { handleId: "worker-input-target", nodeKind: "worker" },
17
+ },
18
+ "workstation-input": {
19
+ source: { handleId: "workstation-input-source", nodeKind: "work-state" },
20
+ target: {
21
+ handleId: "workstation-input-target",
22
+ nodeKind: "workstation",
23
+ },
24
+ },
25
+ "workstation-on-continue": {
26
+ source: {
27
+ handleId: "workstation-on-continue-source",
28
+ nodeKind: "workstation",
29
+ },
30
+ target: { handleId: "work-state-input-target", nodeKind: "work-state" },
31
+ },
32
+ "workstation-on-failure": {
33
+ source: {
34
+ handleId: "workstation-on-failure-source",
35
+ nodeKind: "workstation",
36
+ },
37
+ target: { handleId: "work-state-input-target", nodeKind: "work-state" },
38
+ },
39
+ "workstation-on-rejection": {
40
+ source: {
41
+ handleId: "workstation-on-rejection-source",
42
+ nodeKind: "workstation",
43
+ },
44
+ target: { handleId: "work-state-input-target", nodeKind: "work-state" },
45
+ },
46
+ "workstation-output": {
47
+ source: {
48
+ handleId: "workstation-output-source",
49
+ nodeKind: "workstation",
50
+ },
51
+ target: { handleId: "work-state-input-target", nodeKind: "work-state" },
52
+ },
53
+ "workstation-resource": {
54
+ source: {
55
+ handleId: "workstation-resource-source",
56
+ nodeKind: "resource",
57
+ },
58
+ target: {
59
+ handleId: "workstation-resource-target",
60
+ nodeKind: "workstation",
61
+ },
62
+ },
63
+ "work-type-state": {
64
+ source: { handleId: "work-type-state-source", nodeKind: "work-type" },
65
+ target: { handleId: "work-type-state-target", nodeKind: "work-state" },
66
+ },
67
+ };
@@ -0,0 +1,5 @@
1
+ import type { FactoryTopologyNodeKind } from "./topology-contract.js";
2
+ /** Resolve the durable public identity used by every topology projection. */
3
+ export declare function factoryTopologyEntityId(explicitId: string | undefined, name: string): string;
4
+ /** Build the renderer-facing node identity for one canonical Factory entity. */
5
+ export declare function factoryTopologyNodeId(kind: FactoryTopologyNodeKind, entityId: string): string;
@@ -0,0 +1,8 @@
1
+ /** Resolve the durable public identity used by every topology projection. */
2
+ export function factoryTopologyEntityId(explicitId, name) {
3
+ return explicitId?.trim() || name;
4
+ }
5
+ /** Build the renderer-facing node identity for one canonical Factory entity. */
6
+ export function factoryTopologyNodeId(kind, entityId) {
7
+ return `${kind}:${entityId}`;
8
+ }
@@ -0,0 +1,9 @@
1
+ import type { FactoryTopologyAtTickInput, FactoryTopologyProjection, FactoryTopologyProjectionInput } from "./topology-contract.js";
2
+ export { projectFactoryTopologyConnection } from "./topology-connection.js";
3
+ export type { FactoryResourceTopologyNode, FactoryTopologyAtTickInput, FactoryTopologyConnection, FactoryTopologyConnectionCandidate, FactoryTopologyConnectionEndpoint, FactoryTopologyConnectionKind, FactoryTopologyConnectionResult, FactoryTopologyHandle, FactoryTopologyHandleId, FactoryTopologyNode, FactoryTopologyNodeKind, FactoryTopologyProjection, FactoryTopologyProjectionInput, FactoryTopologyProjectionIssue, FactoryWorkerTopologyNode, FactoryWorkStateTopologyNode, FactoryWorkstationTopologyNode, FactoryWorkTypeTopologyNode, } from "./topology-contract.js";
4
+ export { FACTORY_TOPOLOGY_RELATIONSHIPS } from "./topology-contract.js";
5
+ export { factoryTopologyEntityId, factoryTopologyNodeId, } from "./topology-identity.js";
6
+ /** Project one public Factory definition without mutating caller-owned data. */
7
+ export declare function projectFactoryTopology(input: FactoryTopologyProjectionInput): FactoryTopologyProjection;
8
+ /** Reconstruct the last canonical Factory topology effective at one tick. */
9
+ export declare function projectFactoryTopologyAtTick(input: FactoryTopologyAtTickInput): FactoryTopologyProjection;