@llblab/pi-kit 0.24.0 → 0.25.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +6 -5
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +20 -0
- package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-claude-usage/LICENSE +22 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +110 -0
- package/node_modules/@llblab/pi-claude-usage/banner.jpg +0 -0
- package/node_modules/@llblab/pi-claude-usage/index.ts +1159 -0
- package/node_modules/@llblab/pi-claude-usage/package.json +60 -0
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +42 -56
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +16 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -0
- package/node_modules/@llblab/pi-state-flow/README.md +15 -12
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +16 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +5 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +3 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +275 -199
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +11 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +6 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +4 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +13 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +7 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +17 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +17 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +49 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +22 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +30 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +5 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +19 -28
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +17 -15
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -52
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +34 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +13 -19
- package/node_modules/@llblab/pi-state-flow/dist/package.json +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -1
- package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +72 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +36 -32
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +12 -4
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +5 -5
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -6
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +13 -12
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -29
- package/node_modules/@llblab/pi-state-flow/index.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +20 -10
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +15 -14
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -6
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +274 -197
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +5 -1
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +16 -16
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +11 -11
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +19 -13
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +55 -22
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +46 -13
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +23 -32
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +66 -65
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +39 -19
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +19 -27
- package/node_modules/@llblab/pi-state-flow/package.json +3 -3
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +55 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +144 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +19 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +29 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +16 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +13 -5
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +79 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +197 -0
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +33 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +38 -6
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +15 -0
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +6 -1
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +10 -2
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +19 -5
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +7 -3
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type JsonObject, type JsonValue } from "./json.ts";
|
|
2
|
+
import type { ScopedSemanticStates, StateScope } from "./state.ts";
|
|
2
3
|
export declare const DEFAULT_HISTORY_LIMIT = 7;
|
|
3
4
|
export declare const MAX_HISTORY_LIMIT = 100;
|
|
4
5
|
export interface RecentScopePatch {
|
|
5
6
|
scope: StateScope;
|
|
6
|
-
patch:
|
|
7
|
-
|
|
7
|
+
patch: {
|
|
8
|
+
[key: string]: JsonValue | undefined;
|
|
9
|
+
intents?: JsonObject | null;
|
|
10
|
+
contract?: JsonObject | null;
|
|
11
|
+
working?: JsonObject | null;
|
|
12
|
+
artifacts?: JsonObject | null;
|
|
13
|
+
lazy?: JsonObject | null;
|
|
14
|
+
response?: string | null;
|
|
8
15
|
};
|
|
9
16
|
}
|
|
10
17
|
/** Exact accepted replay cohort; temporal runtime owns its causal boundary. */
|
|
@@ -18,6 +25,6 @@ export interface RecentTransition extends AcceptedTransition {
|
|
|
18
25
|
}
|
|
19
26
|
export type RecentTransitionWindow = RecentTransition[];
|
|
20
27
|
export declare function validateRecentTransition(value: unknown): asserts value is RecentTransition;
|
|
21
|
-
export declare function createAcceptedTransition(currentStates:
|
|
28
|
+
export declare function createAcceptedTransition(currentStates: ScopedSemanticStates, nextStates: ScopedSemanticStates, id?: string): AcceptedTransition | undefined;
|
|
22
29
|
/** Preserve the configured per-scope budget, filtering in selected-lineage order. */
|
|
23
30
|
export declare function projectRecentTransitionsWithLimit(limit: number, lineage: readonly RecentTransition[]): RecentTransitionWindow;
|
|
@@ -30,13 +30,10 @@ function validateScopedPatch(value) {
|
|
|
30
30
|
validatePatch(value.patch);
|
|
31
31
|
for (const [key, field] of Object.entries(value.patch)) {
|
|
32
32
|
const valid = key === "response"
|
|
33
|
-
? value.scope === "session" && typeof field === "string"
|
|
34
|
-
: key ===
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!PATCH_KEYS.has(key) || !valid) {
|
|
38
|
-
throw new Error("Recent State Flow patches may contain hot object planes, ordinary-JSON lazy state, and a session response string");
|
|
39
|
-
}
|
|
33
|
+
? value.scope === "session" && (field === null || typeof field === "string")
|
|
34
|
+
: !PATCH_KEYS.has(key) || field === null || isObject(field);
|
|
35
|
+
if (!valid)
|
|
36
|
+
throw new Error("Recent State Flow patches require object planes or deletion, and a session response string or deletion");
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
export function validateRecentTransition(value) {
|
|
@@ -60,6 +57,8 @@ export function createAcceptedTransition(currentStates, nextStates, id) {
|
|
|
60
57
|
const transitions = [];
|
|
61
58
|
for (const scope of SCOPES) {
|
|
62
59
|
const patch = replayPatch(currentStates[scope], nextStates[scope]);
|
|
60
|
+
if ((currentStates[scope].response ?? "") === (nextStates[scope].response ?? ""))
|
|
61
|
+
delete patch.response;
|
|
63
62
|
if (Object.keys(patch).length > 0)
|
|
64
63
|
transitions.push({ scope, patch });
|
|
65
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type StateFlowDiagnosticCategory = "invalid-patch" | "publication-conflict" | "finalization";
|
|
1
|
+
export type StateFlowDiagnosticCategory = "invalid-patch" | "publication-conflict" | "finalization" | "barrier-block";
|
|
2
2
|
/** Minimal structural block; only ordinary text keeps its exact content. */
|
|
3
3
|
export interface StateFlowDiagnosticBlock {
|
|
4
4
|
type: string;
|
|
@@ -15,6 +15,8 @@ export interface StateFlowDiagnosticRecord {
|
|
|
15
15
|
input?: unknown;
|
|
16
16
|
tool?: string;
|
|
17
17
|
toolCallId?: string;
|
|
18
|
+
/** Names only, in native assistant-batch order; never sibling arguments. */
|
|
19
|
+
batchToolNames?: string[];
|
|
18
20
|
}
|
|
19
21
|
/** Preserve exact text blocks and block boundaries; reasoning bodies are never duplicated. */
|
|
20
22
|
export declare function projectDiagnosticContent(content: unknown): StateFlowDiagnosticBlock[];
|
|
@@ -26,6 +28,7 @@ export interface DiagnosticExtras {
|
|
|
26
28
|
input?: unknown;
|
|
27
29
|
tool?: string;
|
|
28
30
|
toolCallId?: string;
|
|
31
|
+
batchToolNames?: readonly string[];
|
|
29
32
|
}
|
|
30
33
|
/** Own diagnostic path safety, projection, persistence, and one-shot failure reporting. */
|
|
31
34
|
export declare class StateFlowDiagnosticWriter {
|
|
@@ -77,6 +77,7 @@ export class StateFlowDiagnosticWriter {
|
|
|
77
77
|
...(extras.input === undefined ? {} : { input: extras.input }),
|
|
78
78
|
...(extras.tool === undefined ? {} : { tool: extras.tool }),
|
|
79
79
|
...(extras.toolCallId === undefined ? {} : { toolCallId: extras.toolCallId }),
|
|
80
|
+
...(extras.batchToolNames === undefined ? {} : { batchToolNames: [...extras.batchToolNames] }),
|
|
80
81
|
});
|
|
81
82
|
return true;
|
|
82
83
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type RecentScopePatch } from "./history.ts";
|
|
1
2
|
import { type JsonValue } from "./json.ts";
|
|
2
|
-
import { type
|
|
3
|
+
import { type SemanticState, type StateScope } from "./state.ts";
|
|
3
4
|
import { type TemporalState, type TransitionBoundary } from "./temporal.ts";
|
|
4
5
|
export type StateReadQuery = {
|
|
5
6
|
kind: "state";
|
|
@@ -15,13 +16,11 @@ export type StateReadQuery = {
|
|
|
15
16
|
export type StateReadResult = {
|
|
16
17
|
path: string;
|
|
17
18
|
boundary: TransitionBoundary;
|
|
18
|
-
state:
|
|
19
|
+
state: SemanticState;
|
|
19
20
|
} | {
|
|
20
21
|
path: string;
|
|
21
22
|
boundary: TransitionBoundary;
|
|
22
|
-
patch:
|
|
23
|
-
response?: string;
|
|
24
|
-
};
|
|
23
|
+
patch: RecentScopePatch["patch"];
|
|
25
24
|
};
|
|
26
25
|
export type StateReadProjection = "value" | "keys" | "patch";
|
|
27
26
|
type StateReadMeta = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
|
|
2
2
|
import { isObject, sameJson } from "./json.js";
|
|
3
|
-
import { projectStateForModel } from "./state.js";
|
|
4
|
-
import {
|
|
3
|
+
import { emptyState, projectSemanticPatch, projectStateForModel } from "./state.js";
|
|
4
|
+
import { readTemporalView } from "./temporal.js";
|
|
5
5
|
const MAX_REFERENCE_SOURCES = 3;
|
|
6
6
|
const MAX_REFERENCE_SCAN_NODES = 10_000;
|
|
7
7
|
const PATH_PATTERN = /^(effective|global|cwd|session)(?:\[(\d+)\])?(?:\.patches(?:\[(\d+)\])?)?$/;
|
|
@@ -32,12 +32,12 @@ export function readStatePath(view, path, historyLimit = DEFAULT_HISTORY_LIMIT)
|
|
|
32
32
|
const boundary = view.lineage[view.lineage.length - 1 - query.offset];
|
|
33
33
|
if (!boundary)
|
|
34
34
|
throw new Error("Requested history predates the proven temporal origin");
|
|
35
|
-
return { path, boundary: structuredClone(boundary), state: projectStateForModel(
|
|
35
|
+
return { path, boundary: structuredClone(boundary), state: projectStateForModel(readTemporalView(view, query.offset, query.scope, historyLimit)) };
|
|
36
36
|
}
|
|
37
37
|
const record = view.scopes[query.scope].patches.at(-1 - query.offset);
|
|
38
38
|
if (!record)
|
|
39
39
|
throw new Error(`Requested ${query.scope} patch predates retained hot history`);
|
|
40
|
-
return { path, boundary: structuredClone(record.transition), patch:
|
|
40
|
+
return { path, boundary: structuredClone(record.transition), patch: projectSemanticPatch(record.patch) };
|
|
41
41
|
}
|
|
42
42
|
function parseValuePath(path) {
|
|
43
43
|
const explicitRoot = /^(?:effective|global|cwd|session)(?:\[\d+\])?(?=\.|$)/.exec(path)?.[0];
|
|
@@ -151,7 +151,7 @@ export function findStateReferenceSources(view, path, historyLimit = DEFAULT_HIS
|
|
|
151
151
|
}
|
|
152
152
|
};
|
|
153
153
|
for (const scope of ["global", "cwd", "session"]) {
|
|
154
|
-
const state =
|
|
154
|
+
const state = readTemporalView(view, 0, scope, historyLimit);
|
|
155
155
|
for (const plane of ["artifacts", "contract", "working", "intents", "lazy"]) {
|
|
156
156
|
const value = state[plane];
|
|
157
157
|
if (value !== undefined)
|
|
@@ -201,13 +201,13 @@ function patchAtPath(view, root, selectors, path, historyLimit) {
|
|
|
201
201
|
throw new Error("Requested history predates the proven temporal origin");
|
|
202
202
|
let patch = {};
|
|
203
203
|
if (rootQuery.scope) {
|
|
204
|
-
patch =
|
|
204
|
+
patch = projectSemanticPatch((view.scopes[rootQuery.scope].patches.find((record) => record.transition.id === boundary.id)?.patch ?? {}));
|
|
205
205
|
}
|
|
206
206
|
else {
|
|
207
207
|
const before = view.lineage.at(-2 - rootQuery.offset);
|
|
208
208
|
if (before) {
|
|
209
|
-
const current =
|
|
210
|
-
const previous =
|
|
209
|
+
const current = readTemporalView(view, rootQuery.offset, undefined, historyLimit);
|
|
210
|
+
const previous = readTemporalView(view, rootQuery.offset + 1, undefined, historyLimit);
|
|
211
211
|
patch = diffObjects(previous, current);
|
|
212
212
|
}
|
|
213
213
|
}
|
|
@@ -270,11 +270,11 @@ export function readProjectedState(view, paths, projection = "value", historyLim
|
|
|
270
270
|
if (query.kind !== "state")
|
|
271
271
|
throw new Error("Value and keys projections require a state path");
|
|
272
272
|
const readsLazy = selectors[0]?.kind === "key" && selectors[0].key === "lazy";
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (
|
|
277
|
-
|
|
273
|
+
const raw = readTemporalView(view, query.offset, query.scope, historyLimit);
|
|
274
|
+
const state = readsLazy ? raw : projectStateForModel(raw);
|
|
275
|
+
const field = selectors.length === 1 && selectors[0]?.kind === "key" ? selectors[0].key : undefined;
|
|
276
|
+
if (projection === "value" && field !== undefined && Object.hasOwn(emptyState(), field) && !Object.hasOwn(state, field))
|
|
277
|
+
return { value: null };
|
|
278
278
|
return projectValue(selectValue(state, selectors, path), projection);
|
|
279
279
|
}
|
|
280
280
|
catch (error) {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { type RetainedBoundaryCheckpoint, type Snapshot } from "./snapshot.ts";
|
|
1
|
+
import { type InactiveMode, type RetainedBoundaryCheckpoint, type Snapshot } from "./snapshot.ts";
|
|
2
2
|
export type RetainedCheckpointSelection = {
|
|
3
3
|
kind: "boundary";
|
|
4
4
|
checkpoint: RetainedBoundaryCheckpoint;
|
|
5
5
|
skipped: string[];
|
|
6
6
|
} | {
|
|
7
|
-
kind: "
|
|
7
|
+
kind: "pre-runtime";
|
|
8
|
+
mode: InactiveMode;
|
|
8
9
|
skipped: string[];
|
|
9
10
|
} | {
|
|
10
11
|
kind: "unavailable";
|
|
11
12
|
snapshot: Snapshot;
|
|
12
13
|
skipped: string[];
|
|
13
14
|
};
|
|
14
|
-
/** Select the newest supported retained-boundary checkpoint or
|
|
15
|
-
export declare function selectRetainedCheckpoint(candidates: readonly unknown[]): RetainedCheckpointSelection;
|
|
16
|
-
/** Withdraw a caller's join without cancelling independently owned recovery or
|
|
15
|
+
/** Select the newest supported retained-boundary checkpoint or pre-runtime mode; unsupported pointers fail closed. */
|
|
16
|
+
export declare function selectRetainedCheckpoint(candidates: readonly unknown[], inactiveMode?: InactiveMode): RetainedCheckpointSelection;
|
|
17
|
+
/** Withdraw a caller's join without cancelling independently owned recovery or mode persistence. */
|
|
17
18
|
export declare function waitForRecovery<T>(operation: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
18
19
|
/** A selected boundary that cannot be resolved stays unavailable; callers never fall through to older evidence. */
|
|
19
|
-
export declare function selectedBoundaryFailure(cause: string): Snapshot;
|
|
20
|
+
export declare function selectedBoundaryFailure(cause: string, mode?: InactiveMode): Snapshot;
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { parseRetainedPiCheckpoint, migrationFailure } from "./snapshot.js";
|
|
2
|
-
/** Select the newest supported retained-boundary checkpoint or
|
|
3
|
-
export function selectRetainedCheckpoint(candidates) {
|
|
2
|
+
/** Select the newest supported retained-boundary checkpoint or pre-runtime mode; unsupported pointers fail closed. */
|
|
3
|
+
export function selectRetainedCheckpoint(candidates, inactiveMode = "passive") {
|
|
4
4
|
const skipped = [];
|
|
5
5
|
for (const candidate of candidates) {
|
|
6
6
|
let retained;
|
|
7
7
|
try {
|
|
8
8
|
if (typeof candidate === "object" && candidate !== null && Object.hasOwn(candidate, "revision")) {
|
|
9
|
-
return { kind: "unavailable", snapshot: migrationFailure({}, "Snapshot restoration failed: revision-pointer checkpoints are unsupported"), skipped };
|
|
9
|
+
return { kind: "unavailable", snapshot: migrationFailure({}, "Snapshot restoration failed: revision-pointer checkpoints are unsupported", inactiveMode), skipped };
|
|
10
10
|
}
|
|
11
|
-
retained = parseRetainedPiCheckpoint(candidate);
|
|
11
|
+
retained = parseRetainedPiCheckpoint(candidate, inactiveMode);
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
14
|
skipped.push(`Snapshot restoration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
15
|
continue;
|
|
16
16
|
}
|
|
17
|
-
return "
|
|
17
|
+
return "boundary" in retained ? { kind: "boundary", checkpoint: retained, skipped } : { kind: "pre-runtime", mode: retained.mode, skipped };
|
|
18
18
|
}
|
|
19
19
|
return {
|
|
20
20
|
kind: "unavailable",
|
|
21
|
-
snapshot: migrationFailure({}, skipped[0] ?? "Snapshot restoration failed: no supported checkpoint"),
|
|
21
|
+
snapshot: migrationFailure({}, skipped[0] ?? "Snapshot restoration failed: no supported checkpoint", inactiveMode),
|
|
22
22
|
skipped,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
/** Withdraw a caller's join without cancelling independently owned recovery or
|
|
25
|
+
/** Withdraw a caller's join without cancelling independently owned recovery or mode persistence. */
|
|
26
26
|
export function waitForRecovery(operation, signal) {
|
|
27
27
|
return new Promise((resolve, reject) => {
|
|
28
28
|
const aborted = () => reject(signal.reason);
|
|
@@ -42,6 +42,6 @@ export function waitForRecovery(operation, signal) {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
/** A selected boundary that cannot be resolved stays unavailable; callers never fall through to older evidence. */
|
|
45
|
-
export function selectedBoundaryFailure(cause) {
|
|
46
|
-
return migrationFailure({}, `Snapshot restoration failed: ${cause}
|
|
45
|
+
export function selectedBoundaryFailure(cause, mode = "passive") {
|
|
46
|
+
return migrationFailure({}, `Snapshot restoration failed: ${cause}`, mode);
|
|
47
47
|
}
|
|
@@ -3,11 +3,11 @@ import { type ArtifactProvenance, type ArtifactProvenanceRegistry } from "./arti
|
|
|
3
3
|
import { publishTemporalStateToFiles } from "./storage.ts";
|
|
4
4
|
import { type AcceptedTransition, type RecentTransitionWindow } from "./history.ts";
|
|
5
5
|
import { type RetainedBoundaryCheckpoint, type RetainedPiCheckpoint, type Snapshot } from "./snapshot.ts";
|
|
6
|
-
import { type MaterializedState, type ScopedStates, type StateScope } from "./state.ts";
|
|
6
|
+
import { type MaterializedState, type SemanticState, type ScopedSemanticStates, type ScopedStates, type StateScope } from "./state.ts";
|
|
7
7
|
import { type TemporalState } from "./temporal.ts";
|
|
8
8
|
export type RuntimePublication = ReturnType<typeof publishTemporalStateToFiles>;
|
|
9
9
|
export interface RuntimePatchTransaction {
|
|
10
|
-
readonly states:
|
|
10
|
+
readonly states: ScopedSemanticStates;
|
|
11
11
|
readonly causalBasis: string;
|
|
12
12
|
readonly provenance: Record<StateScope, ArtifactProvenanceRegistry>;
|
|
13
13
|
publish(snapshot: Snapshot, accepted?: AcceptedTransition, provenance?: Partial<Record<StateScope, Record<string, ArtifactProvenance>>>): RuntimePublication;
|
|
@@ -45,6 +45,7 @@ export declare class TemporalRuntime {
|
|
|
45
45
|
/** Canonical preparation is the default; Git backup is a later independent concern. */
|
|
46
46
|
prepare(): void;
|
|
47
47
|
read(offset?: number, scope?: StateScope): MaterializedState;
|
|
48
|
+
readView(offset?: number, scope?: StateScope): SemanticState;
|
|
48
49
|
states(): ScopedStates;
|
|
49
50
|
causalBasis(): string;
|
|
50
51
|
/** Encode Pi lifecycle state against the current retained semantic boundary. */
|
|
@@ -6,9 +6,8 @@ import { pruneArtifactProvenance } from "./artifact.js";
|
|
|
6
6
|
import { assertTemporalFileBase, captureTemporalFileBase, initializeFileStore, publishTemporalStateToFiles, withStorageTransaction } from "./storage.js";
|
|
7
7
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
|
|
8
8
|
import { hashJson, sameJson } from "./json.js";
|
|
9
|
-
import { HistoryBoundaryExpiredError, RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, retainedBoundaryCheckpoint } from "./snapshot.js";
|
|
10
|
-
import {
|
|
11
|
-
import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalState, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
|
|
9
|
+
import { HistoryBoundaryExpiredError, RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, preRuntimeCheckpoint, retainedBoundaryCheckpoint } from "./snapshot.js";
|
|
10
|
+
import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalScopes, readTemporalState, readTemporalView, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
|
|
12
11
|
const SCOPES = ["global", "cwd", "session"];
|
|
13
12
|
const SHARED_SCOPES = ["global", "cwd"];
|
|
14
13
|
function emptyProvenance() {
|
|
@@ -42,7 +41,7 @@ function removedTargetScopeConflict(scopes) {
|
|
|
42
41
|
return new SharedScopeRemovalConflictError(scopes);
|
|
43
42
|
}
|
|
44
43
|
function freshEmptyScopeStream(scope, origin, historyLimit) {
|
|
45
|
-
return createTemporalState({ global:
|
|
44
|
+
return createTemporalState({ global: {}, cwd: {}, session: {} }, origin, historyLimit).scopes[scope];
|
|
46
45
|
}
|
|
47
46
|
/** Cached branch-selected temporal state and publication basis; excludes Pi event policy. */
|
|
48
47
|
export class TemporalRuntime {
|
|
@@ -89,7 +88,7 @@ export class TemporalRuntime {
|
|
|
89
88
|
return false;
|
|
90
89
|
if (!shared.global)
|
|
91
90
|
throw new Error("Incomplete passive State Flow shared storage: CWD state exists without global state");
|
|
92
|
-
const fresh = createTemporalState({ global:
|
|
91
|
+
const fresh = createTemporalState({ global: {}, cwd: {}, session: {} }, randomUUID(), this.historyLimit);
|
|
93
92
|
// Global memory is valid before this CWD has ever materialized its own scope.
|
|
94
93
|
const view = adoptTemporalStreams({ global: shared.global, cwd: shared.cwd ?? fresh.scopes.cwd, session: fresh.scopes.session }, `passive:files:${randomUUID()}`, this.historyLimit);
|
|
95
94
|
const provenance = {
|
|
@@ -119,6 +118,11 @@ export class TemporalRuntime {
|
|
|
119
118
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
120
119
|
return readTemporalState(this.view, offset, scope, this.historyLimit);
|
|
121
120
|
}
|
|
121
|
+
readView(offset = 0, scope) {
|
|
122
|
+
if (!this.view)
|
|
123
|
+
throw new Error("State Flow temporal runtime is unavailable");
|
|
124
|
+
return readTemporalView(this.view, offset, scope, this.historyLimit);
|
|
125
|
+
}
|
|
122
126
|
states() {
|
|
123
127
|
return { global: this.read(0, "global"), cwd: this.read(0, "cwd"), session: this.read(0, "session") };
|
|
124
128
|
}
|
|
@@ -130,7 +134,7 @@ export class TemporalRuntime {
|
|
|
130
134
|
/** Encode Pi lifecycle state against the current retained semantic boundary. */
|
|
131
135
|
retainedCheckpoint(snapshot) {
|
|
132
136
|
if (!this.view)
|
|
133
|
-
return
|
|
137
|
+
return preRuntimeCheckpoint(snapshot.config.mode);
|
|
134
138
|
return retainedBoundaryCheckpoint(snapshot, this.causalBasis());
|
|
135
139
|
}
|
|
136
140
|
usesCanonicalFiles() {
|
|
@@ -179,7 +183,7 @@ export class TemporalRuntime {
|
|
|
179
183
|
session: selectedSession,
|
|
180
184
|
}, `restore:${checkpoint.boundary}:${randomUUID()}`, this.historyLimit);
|
|
181
185
|
const snapshot = {
|
|
182
|
-
config: {
|
|
186
|
+
config: { mode: checkpoint.mode },
|
|
183
187
|
meta: {
|
|
184
188
|
step: checkpoint.step,
|
|
185
189
|
...(checkpoint.bootstrap === true ? { bootstrap: true } : {}),
|
|
@@ -195,7 +199,7 @@ export class TemporalRuntime {
|
|
|
195
199
|
for (const record of scopes.session.patches) {
|
|
196
200
|
if (record.transition.position <= boundary.position)
|
|
197
201
|
continue;
|
|
198
|
-
for (const path of Object.keys(record.patch.artifacts ?? {}))
|
|
202
|
+
for (const path of Object.keys(record.patch.artifacts === null ? retained : record.patch.artifacts ?? {}))
|
|
199
203
|
delete retained[path];
|
|
200
204
|
}
|
|
201
205
|
}
|
|
@@ -266,8 +270,9 @@ export class TemporalRuntime {
|
|
|
266
270
|
const meta = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).meta;
|
|
267
271
|
return [scope, parseScopeProvenance(files.get(meta), meta)];
|
|
268
272
|
}));
|
|
273
|
+
// Read-only recovery grants no active policy; callers select the inactive mode.
|
|
269
274
|
const snapshot = {
|
|
270
|
-
config: {
|
|
275
|
+
config: { mode: "passive" },
|
|
271
276
|
meta: { step: document.meta.step, ...(document.meta.bootstrap === true ? { bootstrap: true } : {}) },
|
|
272
277
|
};
|
|
273
278
|
this.view = view;
|
|
@@ -355,7 +360,7 @@ export class TemporalRuntime {
|
|
|
355
360
|
streams.session = undefined;
|
|
356
361
|
if (copy)
|
|
357
362
|
streams.session = copy.stream;
|
|
358
|
-
const fresh = createTemporalState({ global:
|
|
363
|
+
const fresh = createTemporalState({ global: {}, cwd: {}, session: {} }, randomUUID(), this.historyLimit);
|
|
359
364
|
const candidate = new TemporalRuntime(this.cwd, this.session, this.root, undefined, this.historyLimit);
|
|
360
365
|
candidate.base = base;
|
|
361
366
|
candidate.view = adoptTemporalStreams({ global: streams.global ?? fresh.scopes.global, cwd: streams.cwd ?? fresh.scopes.cwd, session: streams.session ?? fresh.scopes.session }, `files:${randomUUID()}`, this.historyLimit);
|
|
@@ -512,7 +517,7 @@ export class TemporalRuntime {
|
|
|
512
517
|
if (!document || !stream)
|
|
513
518
|
throw new RevisionUnavailableError("Current State Flow session memory is incomplete");
|
|
514
519
|
validateScopeLineage(stream, "session", document.meta.lineage, MAX_HISTORY_LIMIT);
|
|
515
|
-
throw new RevisionUnavailableError("Existing State Flow session memory is not selected; select an accepted boundary or use /state-flow-
|
|
520
|
+
throw new RevisionUnavailableError("Existing State Flow session memory is not selected; select an accepted boundary or use /state-flow-active");
|
|
516
521
|
}
|
|
517
522
|
const origin = `patch:${randomUUID()}`;
|
|
518
523
|
const streams = Object.fromEntries(SCOPES.map((scope) => {
|
|
@@ -531,7 +536,7 @@ export class TemporalRuntime {
|
|
|
531
536
|
/** Stage and accept synchronously inside an awaited lock; expose neither selection nor raw storage operations. */
|
|
532
537
|
async withPatchTransaction(action, signal) {
|
|
533
538
|
return this.withPublicationTransaction((candidate, publish) => action({
|
|
534
|
-
states: candidate.
|
|
539
|
+
states: readTemporalScopes(candidate.view, 0, this.historyLimit),
|
|
535
540
|
causalBasis: candidate.causalBasis(),
|
|
536
541
|
provenance: structuredClone(candidate.provenanceByScope),
|
|
537
542
|
publish,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type InactiveMode } from "./snapshot.ts";
|
|
1
2
|
export declare const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
|
|
2
3
|
interface BranchEntry {
|
|
3
4
|
type?: unknown;
|
|
@@ -21,8 +22,10 @@ export interface PassiveStopBoundary {
|
|
|
21
22
|
at: number;
|
|
22
23
|
from?: number;
|
|
23
24
|
preserveContext?: true;
|
|
24
|
-
/** A same-owner failed
|
|
25
|
+
/** A same-owner failed mode change remains a write fence until a later accepted checkpoint. */
|
|
25
26
|
persistenceError?: string;
|
|
27
|
+
/** The inactive mode selected by that failed change; legacy markers omit it. */
|
|
28
|
+
mode?: InactiveMode;
|
|
26
29
|
}
|
|
27
30
|
export interface SnapshotDiscovery {
|
|
28
31
|
candidates: unknown[];
|
|
@@ -92,7 +92,7 @@ export function findPassiveStopBoundary(branch, sessionId, entryType) {
|
|
|
92
92
|
}
|
|
93
93
|
if (entry.customType !== entryType)
|
|
94
94
|
continue;
|
|
95
|
-
const { at, from, reset, owner, preserveContext, persistenceError } = entry.data ?? {};
|
|
95
|
+
const { at, from, reset, owner, preserveContext, persistenceError, mode } = entry.data ?? {};
|
|
96
96
|
if (reset === true && owner === sessionId)
|
|
97
97
|
return undefined;
|
|
98
98
|
if (owner !== undefined && owner !== sessionId)
|
|
@@ -103,6 +103,7 @@ export function findPassiveStopBoundary(branch, sessionId, entryType) {
|
|
|
103
103
|
...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
|
|
104
104
|
...(preserveContext === true ? { preserveContext: true } : {}),
|
|
105
105
|
...(!checkpointSeen && owner === sessionId && typeof persistenceError === "string" && persistenceError.trim().length > 0 ? { persistenceError } : {}),
|
|
106
|
+
...(mode === "passive" || mode === "off" ? { mode } : {}),
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
catch {
|
|
@@ -7,8 +7,12 @@ export declare class RevisionUnavailableError extends Error {
|
|
|
7
7
|
/** Expired history cannot be restored, but explicit activation may use validated current memory. */
|
|
8
8
|
export declare class HistoryBoundaryExpiredError extends RevisionUnavailableError {
|
|
9
9
|
}
|
|
10
|
+
export type StateFlowMode = "active" | "passive" | "off";
|
|
11
|
+
export type InactiveMode = Exclude<StateFlowMode, "active">;
|
|
12
|
+
export declare function isStateFlowMode(value: unknown): value is StateFlowMode;
|
|
13
|
+
/** The session's selected mode is the only serialized behavior switch. */
|
|
10
14
|
export interface SnapshotConfig {
|
|
11
|
-
|
|
15
|
+
mode: StateFlowMode;
|
|
12
16
|
}
|
|
13
17
|
interface LegacyValidationFeedback {
|
|
14
18
|
attempt: number;
|
|
@@ -52,23 +56,28 @@ export declare function serializeSessionRuntime(runtime: SessionRuntime, cwd: st
|
|
|
52
56
|
config: string;
|
|
53
57
|
runtime: string;
|
|
54
58
|
};
|
|
59
|
+
/** Legacy `enabled:false` decodes as non-active; native checkpoints, not this file, select branch policy. */
|
|
55
60
|
export declare function parseSessionRuntime(config: string | undefined, runtimeSource: string | undefined, cwd: string, sessionId: string): SessionRuntime | undefined;
|
|
56
|
-
export declare function emptySnapshot(
|
|
61
|
+
export declare function emptySnapshot(mode?: StateFlowMode): Snapshot;
|
|
57
62
|
export type RetainedBoundaryCheckpoint = {
|
|
58
63
|
boundary: string;
|
|
59
|
-
|
|
64
|
+
mode: StateFlowMode;
|
|
60
65
|
step: number;
|
|
61
66
|
bootstrap?: true;
|
|
62
67
|
specification?: string;
|
|
63
68
|
};
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
/** A proven pre-runtime branch retains only its explicit inactive choice, never semantic storage. */
|
|
70
|
+
export type PreRuntimeCheckpoint = {
|
|
71
|
+
mode: InactiveMode;
|
|
66
72
|
};
|
|
73
|
+
export type RetainedPiCheckpoint = RetainedBoundaryCheckpoint | PreRuntimeCheckpoint;
|
|
67
74
|
export type FileRevision = `file:${string}`;
|
|
68
75
|
export declare function isFileRevision(value: unknown): value is FileRevision;
|
|
69
76
|
/** Encode branch lifecycle against one retained temporal identity without semantic or backup data. */
|
|
70
77
|
export declare function retainedBoundaryCheckpoint(snapshot: Snapshot, boundary: string): RetainedBoundaryCheckpoint;
|
|
71
|
-
/**
|
|
72
|
-
export declare function
|
|
73
|
-
|
|
78
|
+
/** Encode an explicit inactive choice on a branch that has no accepted runtime. */
|
|
79
|
+
export declare function preRuntimeCheckpoint(mode: StateFlowMode): PreRuntimeCheckpoint;
|
|
80
|
+
/** Decode the retained-window checkpoint contract; legacy `enabled`/`{disabled:true}` markers map through `inactiveMode`. */
|
|
81
|
+
export declare function parseRetainedPiCheckpoint(value: unknown, inactiveMode?: InactiveMode): RetainedPiCheckpoint;
|
|
82
|
+
export declare function migrationFailure(data: JsonObject, error: string, mode?: InactiveMode): Snapshot;
|
|
74
83
|
export {};
|
|
@@ -11,6 +11,18 @@ export class RevisionUnavailableError extends Error {
|
|
|
11
11
|
/** Expired history cannot be restored, but explicit activation may use validated current memory. */
|
|
12
12
|
export class HistoryBoundaryExpiredError extends RevisionUnavailableError {
|
|
13
13
|
}
|
|
14
|
+
export function isStateFlowMode(value) {
|
|
15
|
+
return value === "active" || value === "passive" || value === "off";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read-only compatibility for session config and native checkpoints written before `mode`:
|
|
19
|
+
* `enabled:true` stays active; `enabled:false` stays the caller's inactive policy.
|
|
20
|
+
*/
|
|
21
|
+
function decodeLegacyMode(value, inactiveMode) {
|
|
22
|
+
if (Object.hasOwn(value, "mode"))
|
|
23
|
+
return Object.hasOwn(value, "enabled") || !isStateFlowMode(value.mode) ? undefined : value.mode;
|
|
24
|
+
return typeof value.enabled === "boolean" ? value.enabled ? "active" : inactiveMode : undefined;
|
|
25
|
+
}
|
|
14
26
|
export function validateSessionRuntime(value, cwd, sessionId) {
|
|
15
27
|
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
|
|
16
28
|
|| !isObject(value.config) || !isObject(value.meta))
|
|
@@ -31,7 +43,7 @@ export function validateSessionRuntime(value, cwd, sessionId) {
|
|
|
31
43
|
}
|
|
32
44
|
const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
|
|
33
45
|
const normalized = {
|
|
34
|
-
config: {
|
|
46
|
+
config: { mode: isStateFlowMode(value.config.mode) ? value.config.mode : "off" },
|
|
35
47
|
meta: restoredMeta(runtimeFields),
|
|
36
48
|
};
|
|
37
49
|
if (runtimeFields.bootstrap === false)
|
|
@@ -61,6 +73,7 @@ export function serializeSessionRuntime(runtime, cwd, sessionId) {
|
|
|
61
73
|
const { temporal: _retiredTemporal, artifacts: _legacyArtifacts, ...runtimeMeta } = runtime.meta;
|
|
62
74
|
return { config: `${canonicalJson(runtime.config)}\n`, runtime: `${canonicalJson(runtimeMeta)}\n` };
|
|
63
75
|
}
|
|
76
|
+
/** Legacy `enabled:false` decodes as non-active; native checkpoints, not this file, select branch policy. */
|
|
64
77
|
export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
|
|
65
78
|
if (config === undefined && runtimeSource === undefined)
|
|
66
79
|
return undefined;
|
|
@@ -68,14 +81,20 @@ export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
|
|
|
68
81
|
throw new Error("Incomplete State Flow config/runtime pair");
|
|
69
82
|
let runtime;
|
|
70
83
|
try {
|
|
71
|
-
|
|
84
|
+
const settings = JSON.parse(config);
|
|
85
|
+
const mode = isObject(settings) && Object.keys(settings).length === 1 ? decodeLegacyMode(settings, "passive") : undefined;
|
|
86
|
+
if (mode === undefined)
|
|
87
|
+
throw new Error("Invalid State Flow runtime configuration or counters");
|
|
88
|
+
runtime = { config: { mode }, meta: JSON.parse(runtimeSource) };
|
|
72
89
|
}
|
|
73
|
-
catch {
|
|
74
|
-
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof SyntaxError)
|
|
92
|
+
throw new Error("State Flow session runtime contains invalid JSON");
|
|
93
|
+
throw error;
|
|
75
94
|
}
|
|
76
95
|
validateSessionRuntime(runtime, cwd, sessionId);
|
|
77
96
|
const { temporal: _legacyTemporal, ...runtimeMeta } = runtime.meta;
|
|
78
|
-
return { config: {
|
|
97
|
+
return { config: { mode: runtime.config.mode }, meta: runtimeMeta };
|
|
79
98
|
}
|
|
80
99
|
function restoredStep(value) {
|
|
81
100
|
return typeof value === "number"
|
|
@@ -109,11 +128,11 @@ function restoredMeta(value, legacy = {}) {
|
|
|
109
128
|
...(meta.bootstrap === true ? { bootstrap: true } : {}),
|
|
110
129
|
};
|
|
111
130
|
}
|
|
112
|
-
function envelope(
|
|
113
|
-
return { config: {
|
|
131
|
+
function envelope(mode, meta) {
|
|
132
|
+
return { config: { mode }, meta };
|
|
114
133
|
}
|
|
115
|
-
export function emptySnapshot(
|
|
116
|
-
return envelope(
|
|
134
|
+
export function emptySnapshot(mode = "passive") {
|
|
135
|
+
return envelope(mode, { step: 0 });
|
|
117
136
|
}
|
|
118
137
|
export function isFileRevision(value) {
|
|
119
138
|
return typeof value === "string" && /^file:[0-9a-f]{64}$/.test(value);
|
|
@@ -124,23 +143,33 @@ export function retainedBoundaryCheckpoint(snapshot, boundary) {
|
|
|
124
143
|
throw new Error("Checkpoint requires a retained temporal boundary identity");
|
|
125
144
|
const checkpoint = {
|
|
126
145
|
boundary,
|
|
127
|
-
|
|
146
|
+
mode: snapshot.config.mode,
|
|
128
147
|
step: snapshot.meta.step,
|
|
129
148
|
...(snapshot.meta.bootstrap === true ? { bootstrap: true } : {}),
|
|
130
149
|
...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
|
|
131
150
|
};
|
|
132
151
|
return parseRetainedPiCheckpoint(checkpoint);
|
|
133
152
|
}
|
|
134
|
-
/**
|
|
135
|
-
export function
|
|
153
|
+
/** Encode an explicit inactive choice on a branch that has no accepted runtime. */
|
|
154
|
+
export function preRuntimeCheckpoint(mode) {
|
|
155
|
+
if (mode === "active")
|
|
156
|
+
throw new Error("An active State Flow branch requires a retained semantic boundary");
|
|
157
|
+
return { mode };
|
|
158
|
+
}
|
|
159
|
+
/** Decode the retained-window checkpoint contract; legacy `enabled`/`{disabled:true}` markers map through `inactiveMode`. */
|
|
160
|
+
export function parseRetainedPiCheckpoint(value, inactiveMode = "passive") {
|
|
136
161
|
if (!isObject(value))
|
|
137
162
|
throw new Error("Invalid State Flow retained-boundary checkpoint");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
163
|
+
const keys = Object.keys(value);
|
|
164
|
+
if (keys.length === 1 && value.disabled === true)
|
|
165
|
+
return { mode: inactiveMode };
|
|
166
|
+
if (keys.length === 1 && (value.mode === "passive" || value.mode === "off"))
|
|
167
|
+
return { mode: value.mode };
|
|
168
|
+
const allowed = new Set(["boundary", "mode", "enabled", "step", "bootstrap", "specification"]);
|
|
169
|
+
const mode = decodeLegacyMode(value, inactiveMode);
|
|
170
|
+
if (keys.some((key) => !allowed.has(key))
|
|
142
171
|
|| typeof value.boundary !== "string" || value.boundary.trim().length === 0
|
|
143
|
-
||
|
|
172
|
+
|| mode === undefined
|
|
144
173
|
|| !Number.isSafeInteger(value.step) || value.step < 0 || value.step > MAX_RESTORED_STEP
|
|
145
174
|
|| (value.bootstrap !== undefined && value.bootstrap !== true)
|
|
146
175
|
|| (value.specification !== undefined && typeof value.specification !== "string")) {
|
|
@@ -148,18 +177,18 @@ export function parseRetainedPiCheckpoint(value) {
|
|
|
148
177
|
}
|
|
149
178
|
return {
|
|
150
179
|
boundary: value.boundary,
|
|
151
|
-
|
|
180
|
+
mode,
|
|
152
181
|
step: value.step,
|
|
153
182
|
...(value.bootstrap === true ? { bootstrap: true } : {}),
|
|
154
183
|
...(typeof value.specification === "string" ? { specification: value.specification } : {}),
|
|
155
184
|
};
|
|
156
185
|
}
|
|
157
|
-
export function migrationFailure(data, error) {
|
|
186
|
+
export function migrationFailure(data, error, mode = "passive") {
|
|
158
187
|
const meta = restoredMeta(data.meta, data);
|
|
159
188
|
meta.validation = {
|
|
160
189
|
attempt: 0,
|
|
161
190
|
error,
|
|
162
191
|
instruction: "Start a fresh State Flow episode; null is reserved for patch deletion.",
|
|
163
192
|
};
|
|
164
|
-
return envelope(
|
|
193
|
+
return envelope(mode, meta);
|
|
165
194
|
}
|