@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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Port
3
+ Copyright (c) 2026-2026 Andreas Abdi
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,58 +1,90 @@
1
1
  # `@you-agent-factory/factory-replay`
2
2
 
3
- Framework-independent, deterministic Factory-event replay primitives.
4
-
5
- The package accepts public `FactoryEvent` aliases from
6
- `@you-agent-factory/client`, canonicalizes event history by logical tick,
7
- sequence, event time, and id, accepts each event id once, and reconstructs a
8
- caller-defined Factory world at either the latest or an explicit historical
9
- logical tick. The caller supplies a pure reducer and projection adapter, so the
10
- package has no React, Zustand, browser, network, session-routing, persistence,
11
- or diagnostics dependencies.
12
-
13
- `createFactoryReplayCheckpoint` and `advanceFactoryReplay` support live replay
14
- from an accepted tail. Callers provide state-clone and selected-tick adapters;
15
- the kernel applies only unseen events after the checkpoint tick in canonical
16
- order and never mutates the supplied checkpoint.
17
-
18
- `projectFactoryTopologyAtTick` reconstructs the latest public Factory topology
19
- at or before an explicit logical tick. `projectFactoryTopology` projects an
20
- already selected `FactoryDefinition`. Both operations return deterministically
21
- ordered resource, worker, workstation, Work Type, and Work State nodes plus
22
- canonical connections. Node and connection IDs follow the public graph ID
23
- contract, and connection endpoints include renderer-compatible handle IDs.
24
- References that cannot be resolved are omitted from the connection collection
25
- and returned as structured projection issues, so consumers never receive a
26
- dangling edge.
27
-
28
- Canonical initial and replacement topology events retain durable IDs for
29
- resources, workers, workstations, Work Types, and Work States, plus authored
30
- worker and workstation resource requirements. This lets recordings preserve
31
- stable graph identity and both resource connection kinds even when public
32
- entity names change. Internal resource availability arcs present in canonical
33
- workstation IO are excluded from public Work-State routes and do not create
34
- false unresolved-connection issues.
35
-
36
- `projectFactoryActivityAtTick` reconstructs active customer Dispatches,
37
- affected workstations, consumed Work IDs, workers, and resource occupancy from
38
- canonical event history. Consumers that already hold selected replay evidence
39
- can call `projectFactoryActivity` directly. Occupancy reports known occupied
40
- and available quantities, or explicitly reports unavailable evidence when an
41
- active Dispatch lacks resource facts; it never turns missing evidence into a
42
- fabricated zero occupancy.
43
-
44
- `projectFactoryWorkProgressAtTick` reconstructs known customer Work and active
45
- Dispatch consumption at an explicit logical tick, then assigns every Work ID
46
- to exactly one of failed, completed, active, queued, or unclassified. The
47
- classification applies that precedence once, excludes internal time Work, and
48
- uses `unclassified` when partial event or topology evidence cannot safely
49
- determine a lifecycle category. `projectFactoryWorkProgress` exposes the same
50
- pure partition for consumers that already hold selected replay evidence.
51
-
52
- The hosted dashboard projects these three public read models from its selected
53
- checkpoint-safe replay state and exposes them together on the timeline world as
54
- `factoryReplay`. The production timeline projector also derives the legacy
55
- dashboard topology/runtime compatibility fields from the shared topology,
56
- activity, occupancy, and Work-progress decisions. Petri-shaped structures
57
- remain reducer evidence and supplemental dashboard diagnostics, not a second
58
- public topology or progress classifier.
3
+ Framework-independent replay utilities for UI-local Factory consumers. The
4
+ package deterministically orders and accepts Factory events, reconstructs
5
+ reducer-owned state at a logical tick, advances immutable checkpoints, and
6
+ projects customer Work into exclusive progress categories.
7
+
8
+ See the [public package family guide](https://github.com/portpowered/you-agent-factory/blob/main/ui/packages/README.md)
9
+ for the complete layer ordering, packed consumer paths, and execution support
10
+ boundary.
11
+
12
+ The package consumes Factory contracts from `@you-agent-factory/client` as
13
+ types only. Its runtime has no React, React Flow, Zustand, dashboard, network,
14
+ event-stream, or browser-storage dependency.
15
+
16
+ ## Usage
17
+
18
+ Import the public entry point and provide a reducer for the state your consumer
19
+ owns:
20
+
21
+ ```ts
22
+ import {
23
+ projectFactoryStateAtTick,
24
+ type FactoryReplayReducer,
25
+ } from "@you-agent-factory/factory-replay";
26
+
27
+ const result = projectFactoryStateAtTick({ events, reducer, tick: 12 });
28
+ ```
29
+
30
+ Hosts that retain only a selected-tick domain checkpoint can use
31
+ `projectFactoryWorldAtTick` and `advanceFactoryReplay`. Those APIs keep domain
32
+ state cloning, tick assignment, and world projection explicit while retaining
33
+ only accepted event IDs in the checkpoint rather than replay history.
34
+
35
+ ## High-volume replay evidence
36
+
37
+ `ui/src/features/timeline/state/timeline/performance/replay-retained-memory.test.ts`
38
+ replays a deterministic 10,000-event recording through `advanceFactoryReplay`.
39
+ It budgets the UTF-8 serialized retained checkpoint at 2,000,000 bytes and
40
+ verifies the final tick, Work totals, and topology state after an incremental
41
+ same-tick tail containing duplicate input. Replaying the full recording from
42
+ that checkpoint must retain exactly the same bytes and return the same final
43
+ world without reapplying events; the earlier historical world must remain
44
+ unchanged. The caller-owned recording fixture is deliberately excluded from
45
+ the retained measurement: `advanceFactoryReplay` retains only its compact
46
+ checkpoint. The harness uses no wall-clock or process-heap assertions, which
47
+ are not deterministic across CI runtimes, and reports the measured and maximum
48
+ byte counts when the retained bound is exceeded.
49
+
50
+ Use `projectFactoryWorkProgressAtTick` when canonical event history is the
51
+ source for Work progress, or `projectFactoryWorkProgress` when the consumer
52
+ already has explicit selected-tick evidence.
53
+ Progress classification follows failed, completed, active, queued, then
54
+ unclassified precedence. Active Dispatch evidence is ended by responses,
55
+ interruptions, and terminal reconciliation events, using the same lifecycle
56
+ rules as Dispatch overlays.
57
+
58
+ Use `projectFactoryTopologyAtTick` to reconstruct the last canonical Factory
59
+ topology effective at a selected tick. Use `projectFactoryTopology` when the
60
+ consumer already has the selected-tick Factory definition. Both return stable,
61
+ sorted public node and connection identities without mutating caller data.
62
+ `FACTORY_TOPOLOGY_RELATIONSHIPS` is the shared semantic node-kind and handle
63
+ vocabulary for renderers. Invalid relationship endpoints return a fail-closed
64
+ result with structured issues and no partial graph.
65
+
66
+ Use `projectFactoryLoadAtTick` to reconstruct distinct customer Work counts and
67
+ resource occupancy from canonical events, or `projectFactoryLoad` with explicit
68
+ selected-tick evidence. Count and occupancy entries reference the same stable
69
+ Work State and resource node IDs as the topology projection. Missing evidence
70
+ is reported as unavailable rather than fabricated as zero; dangling,
71
+ contradictory, invalid-capacity, and over-capacity evidence remains visible
72
+ through deterministic structured issues.
73
+
74
+ Use `projectFactoryActivityAtTick` to reconstruct active Dispatch overlays
75
+ after all canonically ordered events at a selected tick, or
76
+ `projectFactoryActivity` with explicit selected-tick evidence. Overlays use
77
+ stable Dispatch and Work projection IDs, reference known worker, workstation,
78
+ resource, and topology-connection IDs, and expose unavailable Work, resource,
79
+ or route evidence without inventing endpoints. Completion, interruption, and
80
+ terminal reconciliation remove activity and release occupancy.
81
+
82
+ ## Distribution verification
83
+
84
+ Run `bun run verify` from this directory to typecheck and test the package,
85
+ produce clean ESM runtime and declaration output, validate the runtime boundary,
86
+ inspect the exact registry tarball inventory, and install both the packed client
87
+ and replay packages in a clean temporary consumer. The installed consumer
88
+ compiles against the public topology, semantic handle, issue, overlay, load,
89
+ and progress contracts, then verifies that repeated projections remain pure
90
+ and reproducible across caller-owned presentation changes.
@@ -0,0 +1,62 @@
1
+ import type { FactoryDefinition, FactoryEvent } from "@you-agent-factory/client";
2
+ import type { FactoryResourceOccupancyProjection } from "./load-contract.js";
3
+ export interface FactoryDispatchRouteEvidence {
4
+ stateId?: string;
5
+ stateName?: string;
6
+ workTypeId?: string;
7
+ }
8
+ export interface FactoryActiveDispatchEvidence {
9
+ id: string;
10
+ inputRoutes?: readonly FactoryDispatchRouteEvidence[];
11
+ resourceNames?: readonly string[];
12
+ startedTick: number;
13
+ transitionId?: string;
14
+ workIds?: readonly string[];
15
+ }
16
+ export interface FactoryDispatchOverlayEvidenceStatus {
17
+ resources: "known" | "unavailable";
18
+ route: "known" | "unavailable";
19
+ work: "known" | "unavailable";
20
+ worker: "known" | "unavailable";
21
+ workstation: "known" | "unavailable";
22
+ }
23
+ export interface FactoryDispatchOverlayProjection {
24
+ connectionIds: string[];
25
+ dispatchId: string;
26
+ evidence: FactoryDispatchOverlayEvidenceStatus;
27
+ id: string;
28
+ resourceIds?: string[];
29
+ resourceNodeIds?: string[];
30
+ startedTick: number;
31
+ transitionId?: string;
32
+ workerId?: string;
33
+ workerNodeId?: string;
34
+ workIds?: string[];
35
+ workProjectionIds?: string[];
36
+ workstationId?: string;
37
+ workstationNodeId?: string;
38
+ }
39
+ export interface FactoryActivityProjectionIssue {
40
+ code: "CONTRADICTORY_DISPATCH_EVIDENCE" | "INVALID_RESOURCE_CAPACITY" | "MISSING_FACTORY" | "RESOURCE_CAPACITY_EXCEEDED" | "UNAVAILABLE_TOPOLOGY_PATH" | "UNRESOLVED_RESOURCE" | "UNRESOLVED_ROUTE" | "UNRESOLVED_WORKER" | "UNRESOLVED_WORKSTATION";
41
+ dispatchId?: string;
42
+ id: string;
43
+ message: string;
44
+ reference?: string;
45
+ resourceId?: string;
46
+ }
47
+ export interface FactoryActivityProjection {
48
+ activeDispatchOverlays: FactoryDispatchOverlayProjection[];
49
+ activeWorkstationNodeIds: string[];
50
+ issues: FactoryActivityProjectionIssue[];
51
+ resourceOccupancy: FactoryResourceOccupancyProjection[];
52
+ selectedTick: number;
53
+ }
54
+ export interface FactoryActivityProjectionInput {
55
+ activeDispatches: readonly FactoryActiveDispatchEvidence[];
56
+ factory?: FactoryDefinition;
57
+ selectedTick: number;
58
+ }
59
+ export interface FactoryActivityAtTickInput {
60
+ events: readonly FactoryEvent[];
61
+ tick: number;
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { FactoryActivityAtTickInput, FactoryActivityProjection } from "./activity-contract.js";
2
+ /** Reconstruct active Dispatch overlays after all canonical events at one tick. */
3
+ export declare function projectFactoryActivityAtTick(input: FactoryActivityAtTickInput): FactoryActivityProjection;
@@ -0,0 +1,165 @@
1
+ import { projectFactoryActivity } from "./activity.js";
2
+ import { canonicalizeFactoryEvents } from "./replay.js";
3
+ /** Reconstruct active Dispatch overlays after all canonical events at one tick. */
4
+ export function projectFactoryActivityAtTick(input) {
5
+ const state = {
6
+ activeDispatches: new Map(),
7
+ works: new Map(),
8
+ };
9
+ for (const event of canonicalizeFactoryEvents(input.events)) {
10
+ if (event.context.tick > input.tick)
11
+ break;
12
+ applyActivityEvent(state, event);
13
+ }
14
+ return projectFactoryActivity({
15
+ activeDispatches: [...state.activeDispatches.values()],
16
+ factory: state.factory,
17
+ selectedTick: input.tick,
18
+ });
19
+ }
20
+ function applyActivityEvent(state, event) {
21
+ const payload = objectRecord(event.payload);
22
+ if (event.type === "INITIAL_STRUCTURE_REQUEST" ||
23
+ event.type === "FACTORY_CHANGE") {
24
+ const factory = objectRecord(payload?.factory);
25
+ if (factory)
26
+ state.factory = structuredClone(factory);
27
+ return;
28
+ }
29
+ if (event.type === "WORK_REQUEST") {
30
+ for (const value of arrayValue(payload?.works)) {
31
+ const work = workRouteEvidence(value, state.factory);
32
+ if (work)
33
+ state.works.set(work.id, work);
34
+ }
35
+ return;
36
+ }
37
+ if (event.type === "WORK_STATE_CHANGE") {
38
+ applyWorkStateChange(state, payload);
39
+ return;
40
+ }
41
+ if (event.type === "DISPATCH_REQUEST") {
42
+ applyDispatchRequest(state, event, payload);
43
+ return;
44
+ }
45
+ if (event.type === "DISPATCH_RESPONSE" ||
46
+ event.type === "DISPATCH_INTERRUPTED") {
47
+ if (event.context.dispatchId) {
48
+ state.activeDispatches.delete(event.context.dispatchId);
49
+ }
50
+ return;
51
+ }
52
+ if (event.type === "DISPATCH_RECONCILED") {
53
+ const status = payload?.reconciledStatus;
54
+ if (event.context.dispatchId &&
55
+ (status === "COMPLETED" ||
56
+ status === "FAILED" ||
57
+ status === "INTERRUPTED")) {
58
+ state.activeDispatches.delete(event.context.dispatchId);
59
+ }
60
+ }
61
+ }
62
+ function applyWorkStateChange(state, payload) {
63
+ if (typeof payload?.workId !== "string" || !payload.workId)
64
+ return;
65
+ const previous = state.works.get(payload.workId);
66
+ state.works.set(payload.workId, {
67
+ id: payload.workId,
68
+ ...(typeof payload.toState === "string"
69
+ ? { stateName: payload.toState }
70
+ : {}),
71
+ ...(typeof payload.workTypeName === "string"
72
+ ? { workTypeId: payload.workTypeName }
73
+ : previous?.workTypeId
74
+ ? { workTypeId: previous.workTypeId }
75
+ : {}),
76
+ });
77
+ }
78
+ function applyDispatchRequest(state, event, payload) {
79
+ const transitionId = typeof payload?.transitionId === "string"
80
+ ? payload.transitionId
81
+ : undefined;
82
+ if (transitionId?.startsWith("__system_time:"))
83
+ return;
84
+ const inputValues = Array.isArray(payload?.inputs)
85
+ ? payload.inputs
86
+ : undefined;
87
+ const inputIds = (inputValues ?? []).flatMap((value) => {
88
+ const input = objectRecord(value);
89
+ return typeof input?.workId === "string" && input.workId
90
+ ? [input.workId]
91
+ : [];
92
+ });
93
+ const hasCompleteInputEvidence = inputValues !== undefined && inputIds.length === inputValues.length;
94
+ const hasWorkEvidence = event.context.workIds !== undefined || hasCompleteInputEvidence;
95
+ const workIds = hasWorkEvidence
96
+ ? [...new Set([...(event.context.workIds ?? []), ...inputIds])]
97
+ : undefined;
98
+ const resourceValues = Array.isArray(payload?.resources)
99
+ ? payload.resources
100
+ : undefined;
101
+ const resourceNames = resourceValues?.flatMap((value) => {
102
+ const resource = objectRecord(value);
103
+ return typeof resource?.name === "string" && resource.name
104
+ ? [resource.name]
105
+ : [];
106
+ });
107
+ const completeResources = resourceNames !== undefined &&
108
+ resourceNames.length === resourceValues?.length;
109
+ const inputRoutes = workIds?.flatMap((workId) => {
110
+ const route = state.works.get(workId);
111
+ return route ? [withoutId(route)] : [];
112
+ });
113
+ const completeRoutes = inputRoutes !== undefined && inputRoutes.length === workIds?.length;
114
+ const dispatchId = event.context.dispatchId ?? `incomplete:${event.id}`;
115
+ state.activeDispatches.set(dispatchId, {
116
+ id: dispatchId,
117
+ ...(completeRoutes ? { inputRoutes } : {}),
118
+ ...(completeResources ? { resourceNames } : {}),
119
+ startedTick: event.context.tick,
120
+ ...(transitionId ? { transitionId } : {}),
121
+ ...(workIds ? { workIds } : {}),
122
+ });
123
+ }
124
+ function workRouteEvidence(value, factory) {
125
+ const work = objectRecord(value);
126
+ if (typeof work?.workId !== "string" || !work.workId)
127
+ return undefined;
128
+ const state = objectRecord(work.state);
129
+ const evidence = {
130
+ id: work.workId,
131
+ ...(typeof state?.id === "string" ? { stateId: state.id } : {}),
132
+ ...(typeof state?.name === "string" ? { stateName: state.name } : {}),
133
+ ...(typeof work.workTypeName === "string"
134
+ ? { workTypeId: work.workTypeName }
135
+ : {}),
136
+ };
137
+ if (evidence.stateId || evidence.stateName || !evidence.workTypeId) {
138
+ return evidence;
139
+ }
140
+ const workType = factory?.workTypes?.find((candidate) => candidate.name === evidence.workTypeId ||
141
+ candidate.id === evidence.workTypeId);
142
+ const initial = workType?.states.find((candidate) => candidate.type === "INITIAL");
143
+ return initial
144
+ ? {
145
+ ...evidence,
146
+ ...(initial.id?.trim() ? { stateId: initial.id } : {}),
147
+ stateName: initial.name,
148
+ }
149
+ : evidence;
150
+ }
151
+ function withoutId(work) {
152
+ return {
153
+ ...(work.stateId ? { stateId: work.stateId } : {}),
154
+ ...(work.stateName ? { stateName: work.stateName } : {}),
155
+ ...(work.workTypeId ? { workTypeId: work.workTypeId } : {}),
156
+ };
157
+ }
158
+ function objectRecord(value) {
159
+ return value !== null && typeof value === "object"
160
+ ? value
161
+ : undefined;
162
+ }
163
+ function arrayValue(value) {
164
+ return Array.isArray(value) ? value : [];
165
+ }
@@ -0,0 +1,7 @@
1
+ import type { FactoryActivityProjection, FactoryActivityProjectionInput } from "./activity-contract.js";
2
+ /** Build the stable public identity of one active Dispatch overlay. */
3
+ export declare function factoryDispatchOverlayId(dispatchId: string): string;
4
+ /** Build the stable public identity used when an overlay references Work. */
5
+ export declare function factoryWorkProjectionId(workId: string): string;
6
+ /** Project selected-tick active Dispatch overlays and their topology activity. */
7
+ export declare function projectFactoryActivity(input: FactoryActivityProjectionInput): FactoryActivityProjection;