@llblab/pi-kit 0.37.1 → 0.38.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/CHANGELOG.md +10 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +1 -1
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +2 -2
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/index.ts +39 -9
- package/node_modules/@llblab/pi-clean-room/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-state-flow/README.md +30 -14
- package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
- package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
- package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
- package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
- package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
- package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
- package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
- package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
- package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
- package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
- package/node_modules/@llblab/pi-state-flow/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
- package/package.json +4 -4
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { captureScopeSelection, type ScopeSelection } from "./selection.ts";
|
|
1
2
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, validateRecentTransition, type RecentScopePatch } from "./history.ts";
|
|
2
3
|
import { applyPatch, containsNull, isJsonValue, isObject, sameJson, type JsonObject } from "./json.ts";
|
|
3
|
-
import {
|
|
4
|
+
import { validateRetainedModules, type RetainedModules } from "./semantic-module.ts";
|
|
5
|
+
import { validateExternalPlanes, emptyState, isSemanticState, overlayStates, projectSemanticState, type MaterializedState, type SemanticState, type ScopedSemanticStates, type StateScope } from "./state.ts";
|
|
4
6
|
|
|
5
7
|
/** Owns hot temporal algebra; excludes filesystem, Git, identity allocation, and Pi lifecycle. */
|
|
6
8
|
export interface TransitionBoundary {
|
|
@@ -21,6 +23,8 @@ export interface TemporalPatch {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface ScopeStream {
|
|
26
|
+
/** Presence selects metadata4; absent preserves the legacy exact three-key stream. */
|
|
27
|
+
modules?: RetainedModules;
|
|
24
28
|
/** Monotonic semantic revision owned by this scope; independent of branch-local boundary positions. */
|
|
25
29
|
revision: number;
|
|
26
30
|
checkpoint: ScopeCheckpoint;
|
|
@@ -33,9 +37,60 @@ export interface TemporalState {
|
|
|
33
37
|
scopes: Record<StateScope, ScopeStream>;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
/** Value-only witnesses copied from a validated cohort; not physical store/publication authority. */
|
|
41
|
+
export interface ExcludedTransitionWitness {
|
|
42
|
+
transition: TransitionBoundary;
|
|
43
|
+
owners: ScopeSelection;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SelectedTemporalState {
|
|
47
|
+
selection: ScopeSelection;
|
|
48
|
+
lineage: TransitionBoundary[];
|
|
49
|
+
scopes: Partial<Record<StateScope, ScopeStream>>;
|
|
50
|
+
/** Only excluded-owner witnesses for retained steps with no selected semantic patch. No values or patches. */
|
|
51
|
+
causalBasis: ExcludedTransitionWitness[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type TemporalCohort = TemporalState | SelectedTemporalState;
|
|
36
55
|
const SCOPES: StateScope[] = ["global", "cwd", "session"];
|
|
37
56
|
export type ScopeRevisions = Record<StateScope, number>;
|
|
38
57
|
|
|
58
|
+
function selected(view: TemporalCohort): view is SelectedTemporalState {
|
|
59
|
+
return Object.hasOwn(view, "selection");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function owners(view: TemporalCohort): readonly StateScope[] {
|
|
63
|
+
return selected(view) ? view.selection : SCOPES;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function matchesOwnerKeys(value: unknown, selection: readonly StateScope[]): boolean {
|
|
67
|
+
return isObject(value) && Object.keys(value).sort().join(",") === [...selection].sort().join(",");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function streamFor(view: TemporalCohort, scope: StateScope): ScopeStream {
|
|
71
|
+
const stream = view.scopes[scope];
|
|
72
|
+
if (!stream) throw new Error(`State Flow ${scope} scope is excluded or unavailable`);
|
|
73
|
+
return stream;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateCohort(view: TemporalCohort, historyLimit: number): void {
|
|
77
|
+
if (selected(view)) validateSelectedTemporalState(view, historyLimit);
|
|
78
|
+
else validateTemporalState(view, historyLimit);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cloneCohort<T extends TemporalCohort>(view: T): T {
|
|
82
|
+
const next = structuredClone(view);
|
|
83
|
+
if (selected(next)) next.selection = captureScopeSelection(next.selection);
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function trimCausalBasis(view: TemporalCohort): void {
|
|
88
|
+
if (!selected(view)) return;
|
|
89
|
+
const retained = new Set(view.lineage.slice(1).map((boundary) => boundary.id));
|
|
90
|
+
view.causalBasis = view.causalBasis.filter((witness) => retained.has(witness.transition.id));
|
|
91
|
+
for (const witness of view.causalBasis) witness.owners = captureScopeSelection(witness.owners);
|
|
92
|
+
}
|
|
93
|
+
|
|
39
94
|
function validateHistoryLimit(limit: number): void {
|
|
40
95
|
if (!Number.isSafeInteger(limit) || limit < 0 || limit > MAX_HISTORY_LIMIT) {
|
|
41
96
|
throw new Error(`State Flow history limit must be an integer from 0 to ${MAX_HISTORY_LIMIT}`);
|
|
@@ -52,9 +107,10 @@ function validateBoundary(boundary: TransitionBoundary): void {
|
|
|
52
107
|
}
|
|
53
108
|
}
|
|
54
109
|
|
|
55
|
-
function validateState(state: JsonObject, location?: string): asserts state is SemanticState {
|
|
110
|
+
function validateState(state: JsonObject, location?: string, modules?: RetainedModules): asserts state is SemanticState {
|
|
111
|
+
if (modules !== undefined) validateExternalPlanes(state, modules);
|
|
56
112
|
const json = isJsonValue(state);
|
|
57
|
-
const hasNull = json && isObject(state) && Object.keys(emptyState()).some((key) => containsNull(state[key]));
|
|
113
|
+
const hasNull = json && isObject(state) && [...Object.keys(emptyState()), ...Object.keys(modules ?? {})].some((key) => containsNull(state[key]));
|
|
58
114
|
if (json && isSemanticState(state) && !hasNull) return;
|
|
59
115
|
const reason = !json ? "expected finite, acyclic JSON data"
|
|
60
116
|
: !isObject(state) ? "expected a semantic object"
|
|
@@ -63,9 +119,10 @@ function validateState(state: JsonObject, location?: string): asserts state is S
|
|
|
63
119
|
throw new Error(`Invalid temporal materialized semantic state${location ? ` in ${location}` : ""}: ${reason}`);
|
|
64
120
|
}
|
|
65
121
|
|
|
66
|
-
function apply(state: SemanticState, patch: TemporalPatch["patch"], location?: string): SemanticState {
|
|
122
|
+
function apply(state: SemanticState, patch: TemporalPatch["patch"], location?: string, modules?: RetainedModules): SemanticState {
|
|
123
|
+
if (modules !== undefined) validateExternalPlanes(patch as JsonObject, modules, true);
|
|
67
124
|
const next = applyPatch(state, patch as JsonObject);
|
|
68
|
-
validateState(next, location);
|
|
125
|
+
validateState(next, location, modules);
|
|
69
126
|
return next;
|
|
70
127
|
}
|
|
71
128
|
|
|
@@ -77,7 +134,7 @@ function sameBoundary(left: TransitionBoundary, right: TransitionBoundary): bool
|
|
|
77
134
|
export function validateScopeStream(value: unknown, scope: StateScope, historyLimit = DEFAULT_HISTORY_LIMIT): asserts value is ScopeStream {
|
|
78
135
|
validateHistoryLimit(historyLimit);
|
|
79
136
|
if (!SCOPES.includes(scope)) throw new Error("Unknown temporal scope");
|
|
80
|
-
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "checkpoint,patches,revision"
|
|
137
|
+
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== (Object.hasOwn(value, "modules") ? "checkpoint,modules,patches,revision" : "checkpoint,patches,revision")
|
|
81
138
|
|| !Number.isSafeInteger(value.revision) || (value.revision as number) < 0
|
|
82
139
|
|| !isObject(value.checkpoint) || Object.keys(value.checkpoint).sort().join(",") !== "state,through"
|
|
83
140
|
|| !Array.isArray(value.patches)) {
|
|
@@ -85,7 +142,8 @@ export function validateScopeStream(value: unknown, scope: StateScope, historyLi
|
|
|
85
142
|
}
|
|
86
143
|
const stream = value as unknown as ScopeStream;
|
|
87
144
|
validateBoundary(stream.checkpoint.through);
|
|
88
|
-
|
|
145
|
+
if (Object.hasOwn(stream, "modules")) validateRetainedModules(stream.modules);
|
|
146
|
+
validateState(stream.checkpoint.state, `${scope} checkpoint`, stream.modules);
|
|
89
147
|
if (stream.patches.length > historyLimit) throw new Error(`Temporal scope tail exceeds configured history limit ${historyLimit}`);
|
|
90
148
|
if (stream.revision < stream.patches.length) throw new Error("Temporal scope revision predates its retained patch tail");
|
|
91
149
|
let previous = stream.checkpoint.through;
|
|
@@ -103,7 +161,7 @@ export function validateScopeStream(value: unknown, scope: StateScope, historyLi
|
|
|
103
161
|
throw new Error("Disconnected State Flow temporal ancestry");
|
|
104
162
|
}
|
|
105
163
|
validateRecentTransition({ id: record.transition.id, at: 0, transitions: [{ scope, patch: record.patch }] });
|
|
106
|
-
const next = apply(state, record.patch, `${scope} tail
|
|
164
|
+
const next = apply(state, record.patch, `${scope} tail`, stream.modules);
|
|
107
165
|
state = next;
|
|
108
166
|
previous = record.transition;
|
|
109
167
|
identities.add(previous.id);
|
|
@@ -132,22 +190,69 @@ export function validateTemporalLineage(value: unknown, historyLimit = DEFAULT_H
|
|
|
132
190
|
export function validateScopeLineage(stream: ScopeStream, scope: StateScope, lineage: readonly TransitionBoundary[], historyLimit = DEFAULT_HISTORY_LIMIT): void {
|
|
133
191
|
validateTemporalLineage(lineage, historyLimit);
|
|
134
192
|
validateScopeStream(stream, scope, historyLimit);
|
|
193
|
+
const mismatch = scopeLineageMismatch(stream, lineage);
|
|
194
|
+
if (mismatch) throw new Error(mismatch);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Classify live shared drift only after both stream and lineage have passed their own validators. */
|
|
198
|
+
export function scopeLineageMatches(stream: ScopeStream, scope: StateScope, lineage: readonly TransitionBoundary[], historyLimit = DEFAULT_HISTORY_LIMIT): boolean {
|
|
199
|
+
validateTemporalLineage(lineage, historyLimit);
|
|
200
|
+
validateScopeStream(stream, scope, historyLimit);
|
|
201
|
+
return scopeLineageMismatch(stream, lineage) === undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function scopeLineageMismatch(stream: ScopeStream, lineage: readonly TransitionBoundary[]): string | undefined {
|
|
135
205
|
const oldest = lineage[0]!;
|
|
136
206
|
const head = lineage.at(-1)!;
|
|
137
|
-
if (stream.checkpoint.through.position > oldest.position)
|
|
207
|
+
if (stream.checkpoint.through.position > oldest.position) return "Scope checkpoint is newer than the guaranteed hot boundary";
|
|
138
208
|
const identities = new Map(lineage.map((boundary) => [boundary.id, boundary]));
|
|
139
209
|
for (const boundary of [stream.checkpoint.through, ...stream.patches.map((record) => record.transition)]) {
|
|
140
|
-
if (boundary.position > head.position)
|
|
210
|
+
if (boundary.position > head.position) return "Temporal scope patch is beyond the active head";
|
|
141
211
|
const identity = identities.get(boundary.id);
|
|
142
212
|
const position = lineage[boundary.position - oldest.position];
|
|
143
213
|
if ((identity && !sameBoundary(identity, boundary)) || (position && !sameBoundary(position, boundary))) {
|
|
144
|
-
|
|
214
|
+
return "Conflicting State Flow temporal lineage";
|
|
145
215
|
}
|
|
146
216
|
}
|
|
217
|
+
return undefined;
|
|
147
218
|
}
|
|
148
219
|
|
|
149
220
|
/** Validate one revision-selected cohort. Its older ancestry must be bound by the durable loader. */
|
|
150
221
|
export function validateTemporalState(view: TemporalState, historyLimit = DEFAULT_HISTORY_LIMIT): void {
|
|
222
|
+
if (Object.hasOwn(view, "selection") || Object.hasOwn(view, "causalBasis")) throw new Error("Selected temporal state requires selected-cohort admission");
|
|
223
|
+
validateTemporalCohort(view, SCOPES, historyLimit);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Structural consistency only: storage must additionally bind witnesses to accepted source authority. */
|
|
227
|
+
export function validateSelectedTemporalState(view: SelectedTemporalState, historyLimit = DEFAULT_HISTORY_LIMIT): void {
|
|
228
|
+
if (!isObject(view) || Object.keys(view).sort().join(",") !== "causalBasis,lineage,scopes,selection"
|
|
229
|
+
|| !isObject(view.scopes) || !Array.isArray(view.causalBasis)) throw new Error("Invalid selected temporal envelope");
|
|
230
|
+
const selection = captureScopeSelection(view.selection);
|
|
231
|
+
if (!sameJson(selection, view.selection) || Object.keys(view.scopes).sort().join(",") !== [...selection].sort().join(",")) {
|
|
232
|
+
throw new Error("Selected temporal owners must match the canonical selection exactly");
|
|
233
|
+
}
|
|
234
|
+
validateTemporalLineage(view.lineage, historyLimit);
|
|
235
|
+
const boundaries = new Map(view.lineage.slice(1).map((boundary) => [boundary.id, boundary]));
|
|
236
|
+
const witnessed = new Set<string>();
|
|
237
|
+
let position = view.lineage[0]!.position;
|
|
238
|
+
for (const witness of view.causalBasis) {
|
|
239
|
+
if (!isObject(witness) || Object.keys(witness).sort().join(",") !== "owners,transition") throw new Error("Invalid excluded temporal witness");
|
|
240
|
+
validateBoundary(witness.transition);
|
|
241
|
+
const identity = boundaries.get(witness.transition.id);
|
|
242
|
+
const sourceOwners = captureScopeSelection(witness.owners);
|
|
243
|
+
if (!sameJson(sourceOwners, witness.owners) || sourceOwners.some((scope) => selection.includes(scope))
|
|
244
|
+
|| !identity || !sameBoundary(identity, witness.transition) || witness.transition.position <= position
|
|
245
|
+
|| witnessed.has(identity.id)) throw new Error("Invalid excluded temporal witness basis");
|
|
246
|
+
witnessed.add(identity.id);
|
|
247
|
+
position = witness.transition.position;
|
|
248
|
+
}
|
|
249
|
+
validateTemporalCohort(view, selection, historyLimit, witnessed);
|
|
250
|
+
if (view.causalBasis.some((witness) => selection.some((scope) => streamFor(view, scope).patches.some((record) => record.transition.id === witness.transition.id)))) {
|
|
251
|
+
throw new Error("Excluded temporal witness duplicates a selected patch");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function validateTemporalCohort(view: TemporalCohort, activeOwners: readonly StateScope[], historyLimit: number, witnessed = new Set<string>()): void {
|
|
151
256
|
validateHistoryLimit(historyLimit);
|
|
152
257
|
validateTemporalLineage(view.lineage, historyLimit);
|
|
153
258
|
const oldest = view.lineage[0]!;
|
|
@@ -165,8 +270,8 @@ export function validateTemporalState(view: TemporalState, historyLimit = DEFAUL
|
|
|
165
270
|
};
|
|
166
271
|
for (const boundary of view.lineage) remember(boundary);
|
|
167
272
|
const head = view.lineage.at(-1)!;
|
|
168
|
-
for (const scope of
|
|
169
|
-
const stream = view
|
|
273
|
+
for (const scope of activeOwners) {
|
|
274
|
+
const stream = streamFor(view, scope);
|
|
170
275
|
validateScopeStream(stream, scope, historyLimit);
|
|
171
276
|
remember(stream.checkpoint.through);
|
|
172
277
|
if (stream.checkpoint.through.position > oldest.position) {
|
|
@@ -182,7 +287,7 @@ export function validateTemporalState(view: TemporalState, historyLimit = DEFAUL
|
|
|
182
287
|
if (previous && boundary.parent !== previous.id) throw new Error("Disconnected State Flow temporal ancestry");
|
|
183
288
|
}
|
|
184
289
|
for (const boundary of view.lineage.slice(1)) {
|
|
185
|
-
if (!
|
|
290
|
+
if (!activeOwners.some((scope) => streamFor(view, scope).patches.some((record) => record.transition.id === boundary.id)) && !witnessed.has(boundary.id)) {
|
|
186
291
|
throw new Error("Temporal boundary has no semantic patch");
|
|
187
292
|
}
|
|
188
293
|
}
|
|
@@ -209,117 +314,229 @@ export function createTemporalState(states: ScopedSemanticStates, id: string, hi
|
|
|
209
314
|
return view;
|
|
210
315
|
}
|
|
211
316
|
|
|
317
|
+
/** Restrict a validated value cohort; expanding selection requires separately acquired current streams. */
|
|
318
|
+
export function selectTemporalState(source: TemporalCohort, selection: ScopeSelection, historyLimit = DEFAULT_HISTORY_LIMIT): SelectedTemporalState {
|
|
319
|
+
validateCohort(source, historyLimit);
|
|
320
|
+
const wanted = captureScopeSelection(selection);
|
|
321
|
+
if (wanted.some((scope) => !owners(source).includes(scope))) throw new Error("Cannot select an unacquired temporal owner");
|
|
322
|
+
const inherited = new Map(selected(source) ? source.causalBasis.map((witness) => [witness.transition.id, witness]) : []);
|
|
323
|
+
const causalBasis: ExcludedTransitionWitness[] = [];
|
|
324
|
+
for (const boundary of source.lineage.slice(1)) {
|
|
325
|
+
const live = owners(source).filter((scope) => streamFor(source, scope).patches.some((record) => record.transition.id === boundary.id));
|
|
326
|
+
if (live.some((scope) => wanted.includes(scope))) continue;
|
|
327
|
+
const evidence = [...new Set([...live, ...(inherited.get(boundary.id)?.owners ?? [])])];
|
|
328
|
+
causalBasis.push({ transition: structuredClone(boundary), owners: captureScopeSelection(evidence) });
|
|
329
|
+
}
|
|
330
|
+
const view: SelectedTemporalState = {
|
|
331
|
+
selection: wanted, lineage: structuredClone(source.lineage),
|
|
332
|
+
scopes: Object.fromEntries(wanted.map((scope) => [scope, structuredClone(streamFor(source, scope))])), causalBasis,
|
|
333
|
+
};
|
|
334
|
+
validateSelectedTemporalState(view, historyLimit);
|
|
335
|
+
return view;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Complete retained witness skeleton, derived from validated semantic records/basis, never from lineage alone. */
|
|
339
|
+
export function temporalCausalWitnesses(view: TemporalCohort, historyLimit = DEFAULT_HISTORY_LIMIT): ExcludedTransitionWitness[] {
|
|
340
|
+
validateCohort(view, historyLimit);
|
|
341
|
+
const inherited = new Map(selected(view) ? view.causalBasis.map((witness) => [witness.transition.id, witness]) : []);
|
|
342
|
+
return view.lineage.slice(1).map((transition) => {
|
|
343
|
+
const live = owners(view).filter((scope) => streamFor(view, scope).patches.some((record) => record.transition.id === transition.id));
|
|
344
|
+
return { transition: structuredClone(transition), owners: captureScopeSelection([...new Set([...live, ...(inherited.get(transition.id)?.owners ?? [])])]) };
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Metadata syntax only; a skeleton is neither semantic replay nor acquisition/publication authority. */
|
|
349
|
+
export function captureTemporalWitnesses(lineage: readonly TransitionBoundary[], witnesses: unknown, historyLimit = DEFAULT_HISTORY_LIMIT): ExcludedTransitionWitness[] {
|
|
350
|
+
validateTemporalLineage(lineage, historyLimit);
|
|
351
|
+
if (!Array.isArray(witnesses)) throw new Error("Invalid accepted temporal witness array");
|
|
352
|
+
const boundaries = new Map(lineage.slice(1).map((boundary) => [boundary.id, boundary]));
|
|
353
|
+
const admitted: ExcludedTransitionWitness[] = [];
|
|
354
|
+
let position = lineage[0]!.position;
|
|
355
|
+
for (const witness of witnesses) {
|
|
356
|
+
if (!isObject(witness) || Object.keys(witness).sort().join(",") !== "owners,transition") throw new Error("Invalid accepted temporal witness");
|
|
357
|
+
const transition = witness.transition as unknown as TransitionBoundary;
|
|
358
|
+
validateBoundary(transition);
|
|
359
|
+
const identity = boundaries.get(transition.id);
|
|
360
|
+
const sourceOwners = captureScopeSelection(witness.owners);
|
|
361
|
+
if (!identity || !sameBoundary(identity, transition) || transition.position <= position || !sameJson(sourceOwners, witness.owners)) {
|
|
362
|
+
throw new Error("Accepted temporal witness does not match its lineage");
|
|
363
|
+
}
|
|
364
|
+
admitted.push({ transition: structuredClone(transition), owners: sourceOwners });
|
|
365
|
+
position = transition.position;
|
|
366
|
+
}
|
|
367
|
+
return admitted;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Reconstruct selected values from caller-bound accepted evidence; this validates syntax/consistency, not physical authority. */
|
|
371
|
+
export function restoreSelectedTemporalState(
|
|
372
|
+
scopes: Partial<Record<StateScope, ScopeStream>>, selection: ScopeSelection, lineage: readonly TransitionBoundary[],
|
|
373
|
+
witnesses: unknown, historyLimit = DEFAULT_HISTORY_LIMIT,
|
|
374
|
+
): SelectedTemporalState {
|
|
375
|
+
const wanted = captureScopeSelection(selection);
|
|
376
|
+
if (!matchesOwnerKeys(scopes, wanted)) throw new Error("Selected temporal streams must match their owners exactly");
|
|
377
|
+
validateTemporalLineage(lineage, historyLimit);
|
|
378
|
+
for (const scope of wanted) validateScopeLineage(scopes[scope]!, scope, lineage, historyLimit);
|
|
379
|
+
const skeleton = captureTemporalWitnesses(lineage, witnesses, historyLimit);
|
|
380
|
+
const admitted = new Map(skeleton.map((witness) => [witness.transition.id, witness]));
|
|
381
|
+
for (const witness of skeleton) for (const scope of witness.owners.filter((owner) => wanted.includes(owner))) {
|
|
382
|
+
if (!scopes[scope]!.patches.some((record) => sameBoundary(record.transition, witness.transition))) throw new Error("Accepted temporal witness is missing its selected owner patch");
|
|
383
|
+
}
|
|
384
|
+
const causalBasis: ExcludedTransitionWitness[] = [];
|
|
385
|
+
for (const transition of lineage.slice(1)) {
|
|
386
|
+
if (wanted.some((scope) => scopes[scope]!.patches.some((record) => record.transition.id === transition.id))) continue;
|
|
387
|
+
const witness = admitted.get(transition.id);
|
|
388
|
+
if (!witness) throw new Error("Temporal boundary has no accepted owner witness");
|
|
389
|
+
causalBasis.push(witness);
|
|
390
|
+
}
|
|
391
|
+
const view: SelectedTemporalState = { selection: wanted, lineage: structuredClone([...lineage]), scopes: structuredClone(scopes), causalBasis };
|
|
392
|
+
validateSelectedTemporalState(view, historyLimit);
|
|
393
|
+
return view;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Explicit selected values at a new origin; this constructor grants no initialization/acquisition permission. */
|
|
397
|
+
export function createSelectedTemporalState(states: Partial<ScopedSemanticStates>, selection: ScopeSelection, id: string, historyLimit = DEFAULT_HISTORY_LIMIT): SelectedTemporalState {
|
|
398
|
+
const wanted = captureScopeSelection(selection);
|
|
399
|
+
if (!matchesOwnerKeys(states, wanted)) throw new Error("Selected temporal values must match their owners exactly");
|
|
400
|
+
const through: TransitionBoundary = { id, position: 0, parent: null };
|
|
401
|
+
const view: SelectedTemporalState = {
|
|
402
|
+
selection: wanted, lineage: [through], causalBasis: [],
|
|
403
|
+
scopes: Object.fromEntries(wanted.map((scope) => [scope, { revision: 0, checkpoint: { through: structuredClone(through), state: structuredClone(states[scope]) }, patches: [] }])),
|
|
404
|
+
};
|
|
405
|
+
validateSelectedTemporalState(view, historyLimit);
|
|
406
|
+
return view;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Adopt explicitly admitted selected streams at current values only, without invented aligned history. */
|
|
410
|
+
export function adoptSelectedTemporalStreams(scopes: Partial<Record<StateScope, ScopeStream>>, selection: ScopeSelection, id: string, historyLimit = DEFAULT_HISTORY_LIMIT): SelectedTemporalState {
|
|
411
|
+
const wanted = captureScopeSelection(selection);
|
|
412
|
+
if (!matchesOwnerKeys(scopes, wanted)) throw new Error("Selected temporal streams must match their owners exactly");
|
|
413
|
+
for (const scope of wanted) validateScopeStream(scopes[scope], scope, MAX_HISTORY_LIMIT);
|
|
414
|
+
const boundaries = wanted.flatMap((scope) => [scopes[scope]!.checkpoint.through, ...scopes[scope]!.patches.map((record) => record.transition)]);
|
|
415
|
+
const origin: TransitionBoundary = { id, position: Math.max(...boundaries.map((boundary) => boundary.position)) + 1, parent: null };
|
|
416
|
+
const view: SelectedTemporalState = { selection: wanted, lineage: [origin], scopes: structuredClone(scopes), causalBasis: [] };
|
|
417
|
+
return constrainTemporalState(view, historyLimit);
|
|
418
|
+
}
|
|
419
|
+
|
|
212
420
|
function scopeAt(stream: ScopeStream, boundary: TransitionBoundary): SemanticState {
|
|
213
421
|
let state = structuredClone(stream.checkpoint.state);
|
|
214
422
|
for (const record of stream.patches) {
|
|
215
423
|
if (record.transition.position > boundary.position) break;
|
|
216
|
-
state = apply(state, record.patch);
|
|
424
|
+
state = apply(state, record.patch, undefined, stream.modules);
|
|
217
425
|
}
|
|
218
426
|
return state;
|
|
219
427
|
}
|
|
220
428
|
|
|
221
429
|
/** Fold retained tails to a lower configured limit without inventing history. */
|
|
222
|
-
export function constrainTemporalState(view:
|
|
430
|
+
export function constrainTemporalState<T extends TemporalCohort>(view: T, historyLimit: number): T {
|
|
223
431
|
validateHistoryLimit(historyLimit);
|
|
224
|
-
|
|
225
|
-
const next =
|
|
226
|
-
for (const scope of
|
|
227
|
-
const stream = next
|
|
432
|
+
validateCohort(view, MAX_HISTORY_LIMIT);
|
|
433
|
+
const next = cloneCohort(view);
|
|
434
|
+
for (const scope of owners(next)) {
|
|
435
|
+
const stream = streamFor(next, scope);
|
|
228
436
|
while (stream.patches.length > historyLimit) {
|
|
229
437
|
const folded = stream.patches.shift()!;
|
|
230
|
-
stream.checkpoint = { through: folded.transition, state: apply(stream.checkpoint.state, folded.patch) };
|
|
438
|
+
stream.checkpoint = { through: folded.transition, state: apply(stream.checkpoint.state, folded.patch, undefined, stream.modules) };
|
|
231
439
|
}
|
|
232
440
|
}
|
|
233
441
|
next.lineage = next.lineage.slice(-(historyLimit + 1));
|
|
234
|
-
|
|
442
|
+
trimCausalBasis(next);
|
|
443
|
+
validateCohort(next, historyLimit);
|
|
235
444
|
return next;
|
|
236
445
|
}
|
|
237
446
|
|
|
238
447
|
/** Current independent scope revisions; Effective uses this vector rather than inventing a scalar owner. */
|
|
239
|
-
export function temporalScopeRevisions(view: TemporalState): ScopeRevisions
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
};
|
|
448
|
+
export function temporalScopeRevisions(view: TemporalState): ScopeRevisions;
|
|
449
|
+
export function temporalScopeRevisions(view: SelectedTemporalState): Partial<ScopeRevisions>;
|
|
450
|
+
export function temporalScopeRevisions(view: TemporalCohort): Partial<ScopeRevisions> {
|
|
451
|
+
if (selected(view)) validateSelectedTemporalState(view, MAX_HISTORY_LIMIT);
|
|
452
|
+
const revisions = Object.fromEntries(owners(view).map((scope) => [scope, streamFor(view, scope).revision]));
|
|
245
453
|
if (Object.values(revisions).some((revision) => !Number.isSafeInteger(revision) || revision < 0)) {
|
|
246
454
|
throw new Error("Invalid State Flow scope revision vector");
|
|
247
455
|
}
|
|
248
456
|
return revisions;
|
|
249
457
|
}
|
|
250
458
|
|
|
251
|
-
function readBoundary(view:
|
|
459
|
+
function readBoundary(view: TemporalCohort, offset: number, historyLimit: number): TransitionBoundary {
|
|
252
460
|
validateHistoryLimit(historyLimit);
|
|
253
461
|
if (!Number.isSafeInteger(offset) || offset < 0 || offset > historyLimit) {
|
|
254
462
|
throw new Error(`State Flow hot-history offset must be an integer from 0 to ${historyLimit}`);
|
|
255
463
|
}
|
|
256
|
-
|
|
464
|
+
validateCohort(view, historyLimit);
|
|
257
465
|
const boundary = view.lineage[view.lineage.length - 1 - offset];
|
|
258
466
|
if (!boundary) throw new Error("Requested history predates the proven temporal origin");
|
|
259
467
|
return boundary;
|
|
260
468
|
}
|
|
261
469
|
|
|
262
470
|
/** Exact scope semantics for authored staging; defaults must never become implicit writes. */
|
|
263
|
-
export function readTemporalScopes(view: TemporalState, offset
|
|
471
|
+
export function readTemporalScopes(view: TemporalState, offset?: number, historyLimit?: number): ScopedSemanticStates;
|
|
472
|
+
export function readTemporalScopes(view: SelectedTemporalState, offset?: number, historyLimit?: number): Partial<ScopedSemanticStates>;
|
|
473
|
+
export function readTemporalScopes(view: TemporalCohort, offset = 0, historyLimit = DEFAULT_HISTORY_LIMIT): Partial<ScopedSemanticStates> {
|
|
264
474
|
const boundary = readBoundary(view, offset, historyLimit);
|
|
265
|
-
return
|
|
475
|
+
return Object.fromEntries(owners(view).map((scope) => [scope, scopeAt(streamFor(view, scope), boundary)]));
|
|
266
476
|
}
|
|
267
477
|
|
|
268
478
|
/** Sparse current/historical view: unknown planes and absent values never become effective data. */
|
|
269
|
-
export function readTemporalView(view:
|
|
479
|
+
export function readTemporalView(view: TemporalCohort, offset = 0, scope?: StateScope, historyLimit = DEFAULT_HISTORY_LIMIT): SemanticState {
|
|
270
480
|
if (scope !== undefined && !SCOPES.includes(scope)) throw new Error("Unknown temporal scope");
|
|
481
|
+
if (scope !== undefined && !owners(view).includes(scope)) throw new Error(`State Flow ${scope} scope is excluded`);
|
|
271
482
|
const boundary = readBoundary(view, offset, historyLimit);
|
|
272
|
-
const
|
|
273
|
-
return
|
|
483
|
+
const active = scope === undefined ? owners(view) : [scope];
|
|
484
|
+
return active.reduce<SemanticState>((state, owner) => {
|
|
485
|
+
const stream = streamFor(view, owner), raw = scopeAt(stream, boundary);
|
|
486
|
+
const projected = projectSemanticState(raw, stream.modules);
|
|
487
|
+
return applyPatch(state, projected);
|
|
488
|
+
}, {});
|
|
274
489
|
}
|
|
275
490
|
|
|
276
491
|
/** Internal defaulted materialization for consumers that require object registries. */
|
|
277
|
-
export function readTemporalState(view:
|
|
492
|
+
export function readTemporalState(view: TemporalCohort, offset = 0, scope?: StateScope, historyLimit = DEFAULT_HISTORY_LIMIT): MaterializedState {
|
|
278
493
|
return overlayStates(readTemporalView(view, offset, scope, historyLimit));
|
|
279
494
|
}
|
|
280
495
|
|
|
281
496
|
/** Allocate the identity outside this algebra; only materially effective patches accept it. */
|
|
282
|
-
export function advanceTemporalState(
|
|
283
|
-
view:
|
|
497
|
+
export function advanceTemporalState<T extends TemporalCohort>(
|
|
498
|
+
view: T,
|
|
284
499
|
transitions: readonly RecentScopePatch[],
|
|
285
500
|
id: string,
|
|
286
501
|
historyLimit = DEFAULT_HISTORY_LIMIT,
|
|
287
|
-
):
|
|
502
|
+
): T {
|
|
288
503
|
validateHistoryLimit(historyLimit);
|
|
289
|
-
|
|
504
|
+
validateCohort(view, historyLimit);
|
|
290
505
|
if (transitions.length === 0) return view;
|
|
291
506
|
validateRecentTransition({ id, at: 0, transitions });
|
|
507
|
+
if (transitions.some(({ scope }) => !owners(view).includes(scope))) throw new Error("State Flow patch contains an excluded temporal owner");
|
|
292
508
|
const head = view.lineage.at(-1)!;
|
|
293
509
|
const changes = transitions.filter(({ scope, patch }) => {
|
|
294
|
-
const current = scopeAt(view
|
|
295
|
-
return !sameJson(current, apply(current, patch));
|
|
510
|
+
const current = scopeAt(streamFor(view, scope), head);
|
|
511
|
+
return !sameJson(current, apply(current, patch, undefined, streamFor(view, scope).modules));
|
|
296
512
|
});
|
|
297
513
|
if (changes.length === 0) return view;
|
|
298
514
|
const knownIds = new Set(view.lineage.map((boundary) => boundary.id));
|
|
299
|
-
for (const scope of
|
|
300
|
-
knownIds.add(view
|
|
301
|
-
for (const record of view
|
|
515
|
+
for (const scope of owners(view)) {
|
|
516
|
+
knownIds.add(streamFor(view, scope).checkpoint.through.id);
|
|
517
|
+
for (const record of streamFor(view, scope).patches) knownIds.add(record.transition.id);
|
|
302
518
|
}
|
|
303
519
|
if (knownIds.has(id)) throw new Error("State Flow transition identity has already been used");
|
|
304
520
|
const boundary: TransitionBoundary = { id, position: head.position + 1, parent: head.id };
|
|
305
521
|
validateBoundary(boundary);
|
|
306
|
-
const next =
|
|
522
|
+
const next = cloneCohort(view);
|
|
307
523
|
for (const { scope, patch } of changes) {
|
|
308
|
-
const stream = next
|
|
524
|
+
const stream = streamFor(next, scope);
|
|
309
525
|
if (stream.revision >= Number.MAX_SAFE_INTEGER) throw new Error(`State Flow ${scope} scope revision is exhausted`);
|
|
310
526
|
stream.revision += 1;
|
|
311
527
|
if (historyLimit === 0) {
|
|
312
|
-
stream.checkpoint = { through: structuredClone(boundary), state: apply(scopeAt(stream, head), patch) };
|
|
528
|
+
stream.checkpoint = { through: structuredClone(boundary), state: apply(scopeAt(stream, head), patch, undefined, stream.modules) };
|
|
313
529
|
stream.patches = [];
|
|
314
530
|
continue;
|
|
315
531
|
}
|
|
316
532
|
while (stream.patches.length >= historyLimit) {
|
|
317
533
|
const folded = stream.patches.shift()!;
|
|
318
|
-
stream.checkpoint = { through: folded.transition, state: apply(stream.checkpoint.state, folded.patch) };
|
|
534
|
+
stream.checkpoint = { through: folded.transition, state: apply(stream.checkpoint.state, folded.patch, undefined, stream.modules) };
|
|
319
535
|
}
|
|
320
536
|
stream.patches.push({ transition: structuredClone(boundary), patch: structuredClone(patch) });
|
|
321
537
|
}
|
|
322
538
|
next.lineage = [...next.lineage, boundary].slice(-(historyLimit + 1));
|
|
323
|
-
|
|
539
|
+
trimCausalBasis(next);
|
|
540
|
+
validateCohort(next, historyLimit);
|
|
324
541
|
return next;
|
|
325
542
|
}
|