@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/load.js ADDED
@@ -0,0 +1,260 @@
1
+ import { factoryTopologyEntityId, factoryTopologyNodeId, } from "./topology-identity.js";
2
+ const SYSTEM_WORK_TYPE_ID = "__system_time";
3
+ /** Project selected-tick Work State counts and resource occupancy evidence. */
4
+ export function projectFactoryLoad(input) {
5
+ if (!input.factory) {
6
+ return {
7
+ issues: [
8
+ {
9
+ code: "MISSING_FACTORY",
10
+ id: "missing-factory",
11
+ message: "No Factory load topology is available at the selected tick.",
12
+ },
13
+ ],
14
+ resourceOccupancy: [],
15
+ selectedTick: input.selectedTick,
16
+ workStateCounts: [],
17
+ };
18
+ }
19
+ const issues = new Map();
20
+ const index = indexFactory(input.factory);
21
+ const workStateCounts = projectWorkStateCounts(input.works, index, issues);
22
+ const resourceOccupancy = projectResourceOccupancy(input.activeDispatches, index, issues);
23
+ return {
24
+ issues: [...issues.values()].sort((left, right) => left.id.localeCompare(right.id)),
25
+ resourceOccupancy,
26
+ selectedTick: input.selectedTick,
27
+ workStateCounts,
28
+ };
29
+ }
30
+ function indexFactory(factory) {
31
+ const resources = [...(factory.resources ?? [])]
32
+ .map((resource) => {
33
+ const capacity = Number.isFinite(resource.capacity) && resource.capacity >= 0
34
+ ? resource.capacity
35
+ : undefined;
36
+ return {
37
+ ...(capacity === undefined ? {} : { capacity }),
38
+ id: factoryTopologyEntityId(resource.id, resource.name),
39
+ name: resource.name,
40
+ };
41
+ })
42
+ .sort((left, right) => left.id.localeCompare(right.id));
43
+ const resourcesByName = new Map(resources.map((resource) => [resource.name, resource]));
44
+ const states = [];
45
+ const statesByWorkType = new Map();
46
+ for (const workType of factory.workTypes ?? []) {
47
+ const workTypeId = factoryTopologyEntityId(workType.id, workType.name);
48
+ const statesByReference = new Map();
49
+ for (const state of workType.states) {
50
+ const stateId = `${workTypeId}:${factoryTopologyEntityId(state.id, state.name)}`;
51
+ const indexed = {
52
+ id: stateId,
53
+ name: state.name,
54
+ nodeId: factoryTopologyNodeId("work-state", stateId),
55
+ workTypeId,
56
+ };
57
+ states.push(indexed);
58
+ statesByReference.set(state.name, indexed);
59
+ if (state.id?.trim())
60
+ statesByReference.set(state.id, indexed);
61
+ }
62
+ statesByWorkType.set(workType.name, statesByReference);
63
+ if (workType.id?.trim()) {
64
+ statesByWorkType.set(workType.id, statesByReference);
65
+ }
66
+ }
67
+ states.sort((left, right) => left.nodeId.localeCompare(right.nodeId));
68
+ return { resources, resourcesByName, states, statesByWorkType };
69
+ }
70
+ function projectWorkStateCounts(works, index, issues) {
71
+ if (!works) {
72
+ return index.states.map((state) => ({
73
+ evidence: "unavailable",
74
+ workStateId: state.id,
75
+ workStateNodeId: state.nodeId,
76
+ workTypeId: state.workTypeId,
77
+ }));
78
+ }
79
+ const workById = groupWorkEvidence(works);
80
+ const workIdsByState = new Map();
81
+ for (const [workId, evidence] of workById) {
82
+ if (evidence.every((work) => work.workTypeId === SYSTEM_WORK_TYPE_ID)) {
83
+ continue;
84
+ }
85
+ const resolved = evidence.map((work) => resolveWorkState(work, index));
86
+ if (resolved.some((state) => !state)) {
87
+ const work = evidence.find((item) => !resolveWorkState(item, index));
88
+ addIssue(issues, {
89
+ code: "UNRESOLVED_WORK_STATE",
90
+ id: `work:${workId}:unresolved-state`,
91
+ message: `Work ${workId} does not identify a known Work State.`,
92
+ reference: [work?.workTypeId, work?.stateId ?? work?.stateName]
93
+ .filter(Boolean)
94
+ .join(":"),
95
+ workId,
96
+ });
97
+ continue;
98
+ }
99
+ const statesById = new Map(resolved.flatMap((state) => (state ? [[state.nodeId, state]] : [])));
100
+ if (statesById.size > 1) {
101
+ addIssue(issues, {
102
+ code: "CONTRADICTORY_WORK_STATE",
103
+ id: `work:${workId}:contradictory-state`,
104
+ message: `Work ${workId} has contradictory selected-tick Work State evidence.`,
105
+ workId,
106
+ });
107
+ continue;
108
+ }
109
+ const state = statesById.values().next().value;
110
+ if (!state)
111
+ continue;
112
+ const workIds = workIdsByState.get(state.nodeId) ?? new Set();
113
+ workIds.add(workId);
114
+ workIdsByState.set(state.nodeId, workIds);
115
+ }
116
+ return index.states.map((state) => {
117
+ const workIds = [...(workIdsByState.get(state.nodeId) ?? [])].sort();
118
+ return {
119
+ count: workIds.length,
120
+ evidence: "known",
121
+ workIds,
122
+ workStateId: state.id,
123
+ workStateNodeId: state.nodeId,
124
+ workTypeId: state.workTypeId,
125
+ };
126
+ });
127
+ }
128
+ function groupWorkEvidence(works) {
129
+ const grouped = new Map();
130
+ for (const work of works) {
131
+ if (!work.id)
132
+ continue;
133
+ const evidence = grouped.get(work.id) ?? [];
134
+ evidence.push(structuredClone(work));
135
+ grouped.set(work.id, evidence);
136
+ }
137
+ return grouped;
138
+ }
139
+ function resolveWorkState(work, index) {
140
+ if (!work.workTypeId)
141
+ return undefined;
142
+ const states = index.statesByWorkType.get(work.workTypeId);
143
+ return work.stateId
144
+ ? states?.get(work.stateId)
145
+ : work.stateName
146
+ ? states?.get(work.stateName)
147
+ : undefined;
148
+ }
149
+ function projectResourceOccupancy(dispatches, index, issues) {
150
+ const occupiedByResource = new Map();
151
+ const unavailableResources = new Set();
152
+ const normalizedDispatches = normalizeDispatchEvidence(dispatches, issues);
153
+ const evidenceAvailable = normalizedDispatches?.every((dispatch) => dispatch.resourceClaims !== undefined);
154
+ for (const dispatch of normalizedDispatches ?? []) {
155
+ for (const claim of dispatch.resourceClaims ?? []) {
156
+ const resource = index.resourcesByName.get(claim.resourceName);
157
+ if (!resource) {
158
+ addIssue(issues, {
159
+ code: "UNRESOLVED_RESOURCE_CLAIM",
160
+ dispatchId: dispatch.id,
161
+ id: `dispatch:${dispatch.id}:resource:${claim.resourceName}`,
162
+ message: `Dispatch ${dispatch.id} claims unknown resource ${claim.resourceName}.`,
163
+ reference: claim.resourceName,
164
+ });
165
+ continue;
166
+ }
167
+ const quantity = claim.quantity ?? 1;
168
+ if (!Number.isFinite(quantity) || quantity <= 0) {
169
+ unavailableResources.add(resource.id);
170
+ addIssue(issues, {
171
+ code: "CONTRADICTORY_RESOURCE_CLAIM",
172
+ dispatchId: dispatch.id,
173
+ id: `dispatch:${dispatch.id}:resource:${resource.id}:invalid-quantity`,
174
+ message: `Dispatch ${dispatch.id} has an invalid claim quantity for resource ${resource.id}.`,
175
+ resourceId: resource.id,
176
+ });
177
+ continue;
178
+ }
179
+ occupiedByResource.set(resource.id, (occupiedByResource.get(resource.id) ?? 0) + quantity);
180
+ }
181
+ }
182
+ return index.resources.map((resource) => projectOneResource(resource, occupiedByResource.get(resource.id) ?? 0, evidenceAvailable === true && !unavailableResources.has(resource.id), issues));
183
+ }
184
+ function normalizeDispatchEvidence(dispatches, issues) {
185
+ if (!dispatches)
186
+ return undefined;
187
+ const grouped = new Map();
188
+ for (const dispatch of dispatches) {
189
+ const evidence = grouped.get(dispatch.id) ?? [];
190
+ evidence.push(dispatch);
191
+ grouped.set(dispatch.id, evidence);
192
+ }
193
+ const normalized = [];
194
+ for (const [dispatchId, evidence] of grouped) {
195
+ const distinct = new Map(evidence.map((item) => [resourceClaimsKey(item.resourceClaims), item]));
196
+ if (distinct.size > 1) {
197
+ addIssue(issues, {
198
+ code: "CONTRADICTORY_RESOURCE_CLAIM",
199
+ dispatchId,
200
+ id: `dispatch:${dispatchId}:contradictory-resource-claims`,
201
+ message: `Dispatch ${dispatchId} has contradictory selected-tick resource claims.`,
202
+ });
203
+ normalized.push({ id: dispatchId });
204
+ continue;
205
+ }
206
+ const only = distinct.values().next().value;
207
+ if (only)
208
+ normalized.push(only);
209
+ }
210
+ return normalized;
211
+ }
212
+ function resourceClaimsKey(claims) {
213
+ if (!claims)
214
+ return "unavailable";
215
+ return [...claims]
216
+ .map((claim) => `${claim.resourceName}\u0000${claim.quantity ?? 1}`)
217
+ .sort()
218
+ .join("\u0001");
219
+ }
220
+ function projectOneResource(resource, occupiedQuantity, occupancyAvailable, issues) {
221
+ const base = {
222
+ ...(resource.capacity === undefined ? {} : { capacity: resource.capacity }),
223
+ capacityEvidence: resource.capacity === undefined
224
+ ? "unavailable"
225
+ : "known",
226
+ resourceId: resource.id,
227
+ resourceNodeId: factoryTopologyNodeId("resource", resource.id),
228
+ };
229
+ if (resource.capacity === undefined) {
230
+ addIssue(issues, {
231
+ code: "INVALID_RESOURCE_CAPACITY",
232
+ id: `resource:${resource.id}:invalid-capacity`,
233
+ message: `Resource ${resource.id} does not have a valid canonical capacity.`,
234
+ resourceId: resource.id,
235
+ });
236
+ }
237
+ if (!occupancyAvailable)
238
+ return { ...base, evidence: "unavailable" };
239
+ if (resource.capacity !== undefined && occupiedQuantity > resource.capacity) {
240
+ addIssue(issues, {
241
+ code: "RESOURCE_CAPACITY_EXCEEDED",
242
+ id: `resource:${resource.id}:capacity-exceeded`,
243
+ message: `Resource ${resource.id} has ${occupiedQuantity} occupied units for capacity ${resource.capacity}.`,
244
+ resourceId: resource.id,
245
+ });
246
+ }
247
+ return {
248
+ ...base,
249
+ ...(resource.capacity === undefined
250
+ ? {}
251
+ : {
252
+ availableQuantity: Math.max(0, resource.capacity - occupiedQuantity),
253
+ }),
254
+ evidence: "known",
255
+ occupiedQuantity,
256
+ };
257
+ }
258
+ function addIssue(issues, issue) {
259
+ issues.set(issue.id, issue);
260
+ }
@@ -0,0 +1,44 @@
1
+ import type { FactoryDefinition, FactoryEvent } from "@you-agent-factory/client";
2
+ export type FactoryWorkStateCategory = "FAILED" | "INITIAL" | "PROCESSING" | "TERMINAL";
3
+ export type FactoryWorkProgressCategory = "failed" | "completed" | "active" | "queued" | "unclassified";
4
+ export interface FactoryWorkProgressStateEvidence {
5
+ category?: FactoryWorkStateCategory;
6
+ id?: string;
7
+ name?: string;
8
+ }
9
+ export interface FactoryWorkProgressEvidence {
10
+ id: string;
11
+ state?: FactoryWorkProgressStateEvidence;
12
+ workTypeId?: string;
13
+ }
14
+ export interface FactoryWorkProgressItem {
15
+ id: string;
16
+ stateId?: string;
17
+ stateName?: string;
18
+ workTypeId?: string;
19
+ }
20
+ export type FactoryWorkProgressCounts = Record<FactoryWorkProgressCategory, number>;
21
+ export interface FactoryWorkProgressProjection {
22
+ active: FactoryWorkProgressItem[];
23
+ completed: FactoryWorkProgressItem[];
24
+ counts: FactoryWorkProgressCounts;
25
+ failed: FactoryWorkProgressItem[];
26
+ queued: FactoryWorkProgressItem[];
27
+ selectedTick: number;
28
+ total: number;
29
+ unclassified: FactoryWorkProgressItem[];
30
+ }
31
+ export interface FactoryWorkProgressProjectionInput {
32
+ activeWorkIds: readonly string[];
33
+ factory?: FactoryDefinition;
34
+ selectedTick: number;
35
+ works: readonly FactoryWorkProgressEvidence[];
36
+ }
37
+ export interface FactoryWorkProgressAtTickInput {
38
+ events: readonly FactoryEvent[];
39
+ tick: number;
40
+ }
41
+ /** Partition customer Work into one mutually exclusive progress category. */
42
+ export declare function projectFactoryWorkProgress(input: FactoryWorkProgressProjectionInput): FactoryWorkProgressProjection;
43
+ /** Reconstruct and exclusively classify customer Work at one logical tick. */
44
+ export declare function projectFactoryWorkProgressAtTick(input: FactoryWorkProgressAtTickInput): FactoryWorkProgressProjection;
@@ -0,0 +1,278 @@
1
+ import { canonicalizeFactoryEvents } from "./replay.js";
2
+ const SYSTEM_WORK_TYPE_ID = "__system_time";
3
+ /** Partition customer Work into one mutually exclusive progress category. */
4
+ export function projectFactoryWorkProgress(input) {
5
+ const statesByWorkType = indexWorkStates(input.factory);
6
+ const activeWorkIds = new Set(input.activeWorkIds);
7
+ const worksById = new Map();
8
+ for (const evidence of input.works) {
9
+ if (!evidence.id || evidence.workTypeId === SYSTEM_WORK_TYPE_ID)
10
+ continue;
11
+ worksById.set(evidence.id, structuredClone(evidence));
12
+ }
13
+ const projection = {
14
+ active: [],
15
+ completed: [],
16
+ counts: { active: 0, completed: 0, failed: 0, queued: 0, unclassified: 0 },
17
+ failed: [],
18
+ queued: [],
19
+ selectedTick: input.selectedTick,
20
+ total: worksById.size,
21
+ unclassified: [],
22
+ };
23
+ const works = [...worksById.values()].sort((left, right) => left.id.localeCompare(right.id));
24
+ for (const evidence of works) {
25
+ const state = resolveState(evidence, statesByWorkType);
26
+ const item = {
27
+ id: evidence.id,
28
+ ...(evidence.workTypeId ? { workTypeId: evidence.workTypeId } : {}),
29
+ ...(state?.id ? { stateId: state.id } : {}),
30
+ ...(state?.name ? { stateName: state.name } : {}),
31
+ };
32
+ const category = classifyWork(state?.category, activeWorkIds.has(evidence.id));
33
+ projection[category].push(item);
34
+ projection.counts[category] += 1;
35
+ }
36
+ return projection;
37
+ }
38
+ /** Reconstruct and exclusively classify customer Work at one logical tick. */
39
+ export function projectFactoryWorkProgressAtTick(input) {
40
+ const state = {
41
+ activeDispatches: new Map(),
42
+ initialWorkIds: new Set(),
43
+ works: new Map(),
44
+ };
45
+ const events = canonicalizeFactoryEvents(input.events).filter((event) => event.context.tick <= input.tick);
46
+ for (const event of events)
47
+ applyProgressEvent(state, event);
48
+ return projectFactoryWorkProgress({
49
+ activeWorkIds: [...state.activeDispatches.values()].flat(),
50
+ factory: state.factory,
51
+ selectedTick: input.tick,
52
+ works: [...state.works.values()].map((work) => {
53
+ const initialState = state.initialWorkIds.has(work.id)
54
+ ? initialStateForWork(state.factory, work.workTypeId)
55
+ : undefined;
56
+ return initialState ? { ...work, state: initialState } : work;
57
+ }),
58
+ });
59
+ }
60
+ function applyProgressEvent(state, event) {
61
+ const payload = objectRecord(event.payload);
62
+ if (event.type === "INITIAL_STRUCTURE_REQUEST" ||
63
+ event.type === "FACTORY_CHANGE") {
64
+ if (objectRecord(payload?.factory)) {
65
+ state.factory = structuredClone(payload?.factory);
66
+ }
67
+ return;
68
+ }
69
+ if (event.type === "WORK_REQUEST") {
70
+ for (const work of arrayValue(payload?.works)) {
71
+ mergeEventWork(state.works, work);
72
+ const record = objectRecord(work);
73
+ if (typeof record?.workId === "string") {
74
+ if (objectRecord(record.state)) {
75
+ state.initialWorkIds.delete(record.workId);
76
+ }
77
+ else {
78
+ state.initialWorkIds.add(record.workId);
79
+ }
80
+ }
81
+ }
82
+ return;
83
+ }
84
+ if (event.type === "WORK_STATE_CHANGE") {
85
+ applyWorkStateChange(state, payload);
86
+ return;
87
+ }
88
+ if (event.type === "DISPATCH_REQUEST") {
89
+ applyDispatchRequest(state, event, payload);
90
+ return;
91
+ }
92
+ if (event.type === "DISPATCH_RESPONSE") {
93
+ applyDispatchResponse(state, event, payload);
94
+ return;
95
+ }
96
+ if (event.type === "DISPATCH_INTERRUPTED") {
97
+ endDispatch(state, event);
98
+ return;
99
+ }
100
+ if (event.type === "DISPATCH_RECONCILED") {
101
+ const status = payload?.reconciledStatus;
102
+ if (status === "COMPLETED" ||
103
+ status === "FAILED" ||
104
+ status === "INTERRUPTED") {
105
+ endDispatch(state, event);
106
+ }
107
+ }
108
+ }
109
+ function applyWorkStateChange(state, payload) {
110
+ if (typeof payload?.workId !== "string" || !payload.workId)
111
+ return;
112
+ const previous = state.works.get(payload.workId) ?? { id: payload.workId };
113
+ state.initialWorkIds.delete(payload.workId);
114
+ state.works.set(payload.workId, {
115
+ ...previous,
116
+ ...(typeof payload.workTypeName === "string"
117
+ ? { workTypeId: payload.workTypeName }
118
+ : {}),
119
+ ...(typeof payload.toState === "string"
120
+ ? { state: { name: payload.toState } }
121
+ : {}),
122
+ });
123
+ }
124
+ function applyDispatchRequest(state, event, payload) {
125
+ const dispatchId = event.context.dispatchId;
126
+ const transitionId = payload?.transitionId;
127
+ if (typeof transitionId === "string" &&
128
+ transitionId.startsWith("__system_time:")) {
129
+ return;
130
+ }
131
+ const inputIds = arrayValue(payload?.inputs).flatMap((input) => {
132
+ const record = objectRecord(input);
133
+ return typeof record?.workId === "string" && record.workId
134
+ ? [record.workId]
135
+ : [];
136
+ });
137
+ const workIds = [
138
+ ...new Set([...(event.context.workIds ?? []), ...inputIds].filter(Boolean)),
139
+ ];
140
+ for (const workId of workIds) {
141
+ if (!state.works.has(workId))
142
+ state.works.set(workId, { id: workId });
143
+ }
144
+ if (dispatchId)
145
+ state.activeDispatches.set(dispatchId, workIds);
146
+ }
147
+ function applyDispatchResponse(state, event, payload) {
148
+ const dispatchId = event.context.dispatchId;
149
+ const consumedIds = dispatchId
150
+ ? (state.activeDispatches.get(dispatchId) ?? [])
151
+ : [];
152
+ if (dispatchId)
153
+ state.activeDispatches.delete(dispatchId);
154
+ const transitionId = payload?.transitionId;
155
+ if (typeof transitionId !== "string" ||
156
+ !transitionId.startsWith("__system_time:")) {
157
+ for (const workId of event.context.workIds ?? []) {
158
+ if (workId && !state.works.has(workId)) {
159
+ state.works.set(workId, { id: workId });
160
+ }
161
+ }
162
+ }
163
+ const outputWorks = arrayValue(payload?.outputWork);
164
+ const outputIds = new Set(outputWorks.flatMap((work) => {
165
+ const record = objectRecord(work);
166
+ return typeof record?.workId === "string" ? [record.workId] : [];
167
+ }));
168
+ for (const workId of consumedIds) {
169
+ const previous = state.works.get(workId);
170
+ if (!previous || outputIds.has(workId))
171
+ continue;
172
+ state.initialWorkIds.delete(workId);
173
+ if (previous.state?.category !== "FAILED" &&
174
+ previous.state?.category !== "TERMINAL") {
175
+ state.works.set(workId, {
176
+ id: previous.id,
177
+ ...(previous.workTypeId ? { workTypeId: previous.workTypeId } : {}),
178
+ });
179
+ }
180
+ }
181
+ for (const work of outputWorks) {
182
+ mergeEventWork(state.works, work);
183
+ const record = objectRecord(work);
184
+ if (typeof record?.workId === "string") {
185
+ state.initialWorkIds.delete(record.workId);
186
+ }
187
+ }
188
+ }
189
+ function endDispatch(state, event) {
190
+ if (event.context.dispatchId) {
191
+ state.activeDispatches.delete(event.context.dispatchId);
192
+ }
193
+ }
194
+ function indexWorkStates(factory) {
195
+ const statesByWorkType = new Map();
196
+ for (const workType of factory?.workTypes ?? []) {
197
+ const states = new Map();
198
+ for (const state of workType.states) {
199
+ const indexed = { ...state, category: state.type };
200
+ states.set(state.name, indexed);
201
+ if (state.id?.trim())
202
+ states.set(state.id, indexed);
203
+ }
204
+ statesByWorkType.set(workType.name, states);
205
+ if (workType.id?.trim())
206
+ statesByWorkType.set(workType.id, states);
207
+ }
208
+ return statesByWorkType;
209
+ }
210
+ function resolveState(evidence, statesByWorkType) {
211
+ if (evidence.state?.category || !evidence.workTypeId)
212
+ return evidence.state;
213
+ const states = statesByWorkType.get(evidence.workTypeId);
214
+ const indexed = evidence.state?.name
215
+ ? states?.get(evidence.state.name)
216
+ : evidence.state?.id
217
+ ? states?.get(evidence.state.id)
218
+ : undefined;
219
+ return indexed ?? evidence.state;
220
+ }
221
+ function initialStateForWork(factory, workTypeId) {
222
+ if (!workTypeId)
223
+ return undefined;
224
+ const workType = factory?.workTypes?.find((candidate) => candidate.name === workTypeId || candidate.id === workTypeId);
225
+ const state = workType?.states.find((candidate) => candidate.type === "INITIAL");
226
+ return state ? { ...state, category: state.type } : undefined;
227
+ }
228
+ function mergeEventWork(works, value) {
229
+ const record = objectRecord(value);
230
+ if (typeof record?.workId !== "string" || !record.workId)
231
+ return;
232
+ const previous = works.get(record.workId) ?? { id: record.workId };
233
+ const stateRecord = objectRecord(record.state);
234
+ const state = stateRecord
235
+ ? {
236
+ ...(typeof stateRecord.id === "string" ? { id: stateRecord.id } : {}),
237
+ ...(typeof stateRecord.name === "string"
238
+ ? { name: stateRecord.name }
239
+ : {}),
240
+ ...(isWorkStateCategory(stateRecord.type)
241
+ ? { category: stateRecord.type }
242
+ : {}),
243
+ }
244
+ : previous.state;
245
+ works.set(record.workId, {
246
+ ...previous,
247
+ ...(typeof record.workTypeName === "string"
248
+ ? { workTypeId: record.workTypeName }
249
+ : {}),
250
+ ...(state ? { state } : {}),
251
+ });
252
+ }
253
+ function classifyWork(stateCategory, active) {
254
+ if (stateCategory === "FAILED")
255
+ return "failed";
256
+ if (stateCategory === "TERMINAL")
257
+ return "completed";
258
+ if (active)
259
+ return "active";
260
+ if (stateCategory === "INITIAL" || stateCategory === "PROCESSING") {
261
+ return "queued";
262
+ }
263
+ return "unclassified";
264
+ }
265
+ function isWorkStateCategory(value) {
266
+ return (value === "FAILED" ||
267
+ value === "INITIAL" ||
268
+ value === "PROCESSING" ||
269
+ value === "TERMINAL");
270
+ }
271
+ function objectRecord(value) {
272
+ return value !== null && typeof value === "object"
273
+ ? value
274
+ : undefined;
275
+ }
276
+ function arrayValue(value) {
277
+ return Array.isArray(value) ? value : [];
278
+ }
@@ -0,0 +1,98 @@
1
+ import type { FactoryEvent } from "@you-agent-factory/client";
2
+ export interface FactoryReplayFixedSelection {
3
+ mode: "fixed";
4
+ tick: number;
5
+ }
6
+ export type FactoryReplaySelection = {
7
+ mode: "current";
8
+ } | FactoryReplayFixedSelection;
9
+ /** Domain interpretation stays with the consumer; the kernel owns replay semantics. */
10
+ export interface FactoryReplayReducer<State> {
11
+ applyEvent(state: State, event: FactoryEvent): State;
12
+ createState(selectedTick: number): State;
13
+ }
14
+ /** A reducer that also maps its domain state into a consumer-owned view. */
15
+ export interface FactoryReplayWorldReducer<State, World> extends FactoryReplayReducer<State> {
16
+ projectWorld(state: State): World;
17
+ }
18
+ export interface FactoryReplayInitialization<State> {
19
+ events: readonly FactoryEvent[];
20
+ reducer: FactoryReplayReducer<State>;
21
+ selection: FactoryReplaySelection;
22
+ }
23
+ export interface FactoryReplayCheckpoint<State> {
24
+ acceptedEventIds: ReadonlySet<string>;
25
+ appliedEvents: readonly FactoryEvent[];
26
+ selectedTick: number;
27
+ state: State;
28
+ }
29
+ export interface FactoryReplayCheckpointAdvancement<State> {
30
+ checkpoint: FactoryReplayCheckpoint<State>;
31
+ reducer: FactoryReplayReducer<State>;
32
+ tail: readonly FactoryEvent[];
33
+ tick: number;
34
+ }
35
+ export interface FactoryReplayResult<State> extends FactoryReplayCheckpoint<State> {
36
+ acceptedEventIds: Set<string>;
37
+ appliedEvents: FactoryEvent[];
38
+ events: FactoryEvent[];
39
+ latestTick: number;
40
+ selectedTick: number;
41
+ selection: FactoryReplaySelection;
42
+ state: State;
43
+ }
44
+ export interface FactoryReplayWorldResult<State, World> extends FactoryReplayResult<State> {
45
+ world: World;
46
+ }
47
+ export type FactoryReplayStateCloner<State> = (state: State) => State;
48
+ /**
49
+ * A compact checkpoint for consumers that retain domain state but not replay
50
+ * history. Event IDs make repeated tails idempotent without retaining events.
51
+ */
52
+ export interface FactoryReplayWorldCheckpoint<State> {
53
+ acceptedEventIDs: readonly string[];
54
+ selectedTick: number;
55
+ state: State;
56
+ }
57
+ export interface FactoryReplayWorldAdvanceInput<State, World> {
58
+ checkpoint: FactoryReplayWorldCheckpoint<State>;
59
+ cloneState: FactoryReplayStateCloner<State>;
60
+ events: readonly FactoryEvent[];
61
+ reducer: FactoryReplayWorldReducer<State, World>;
62
+ setSelectedTick(state: State, tick: number): State;
63
+ tick: number;
64
+ }
65
+ export interface FactoryReplayWorldAdvanceResult<State, World> {
66
+ appliedEvents: FactoryEvent[];
67
+ checkpoint: FactoryReplayWorldCheckpoint<State>;
68
+ latestTick: number;
69
+ selectedTick: number;
70
+ state: State;
71
+ world: World;
72
+ }
73
+ /**
74
+ * Return one immutable, canonically ordered accepted history. When an ID is
75
+ * repeated, the canonically earliest event owns that ID.
76
+ */
77
+ export declare function canonicalizeFactoryEvents(events: readonly FactoryEvent[]): FactoryEvent[];
78
+ /** Reconstruct reducer-owned state for a current or fixed logical tick. */
79
+ export declare function initializeFactoryReplay<State>(input: FactoryReplayInitialization<State>): FactoryReplayResult<State>;
80
+ /** Advance an immutable checkpoint with unseen accepted events through a tick. */
81
+ export declare function advanceFactoryReplayCheckpoint<State>(input: FactoryReplayCheckpointAdvancement<State>): FactoryReplayResult<State>;
82
+ /** Reconstruct reducer-owned state at one explicit logical tick. */
83
+ export declare function projectFactoryStateAtTick<State>(input: Omit<FactoryReplayInitialization<State>, "selection"> & {
84
+ tick: number;
85
+ }): FactoryReplayResult<State>;
86
+ /** Create an independent compact checkpoint from a selected-tick projection. */
87
+ export declare function createFactoryReplayWorldCheckpoint<State, World>(result: FactoryReplayWorldResult<State, World>, cloneState: FactoryReplayStateCloner<State>): FactoryReplayWorldCheckpoint<State>;
88
+ /**
89
+ * Advance a compact checkpoint with an accepted tail without retaining the
90
+ * checkpoint's historical events. The explicit clone and tick adapters keep
91
+ * domain-state ownership with the consumer.
92
+ */
93
+ export declare function advanceFactoryReplay<State, World>(input: FactoryReplayWorldAdvanceInput<State, World>): FactoryReplayWorldAdvanceResult<State, World>;
94
+ /** Reconstruct a consumer-owned world at one explicit logical tick. */
95
+ export declare function projectFactoryWorldAtTick<State, World>(input: Omit<FactoryReplayInitialization<State>, "selection"> & {
96
+ reducer: FactoryReplayWorldReducer<State, World>;
97
+ tick: number;
98
+ }): FactoryReplayWorldResult<State, World>;