@llblab/pi-kit 0.37.1 → 0.38.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/CHANGELOG.md +5 -0
- package/README.md +2 -2
- 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 +2 -2
|
@@ -3,12 +3,14 @@ import { lstatSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { classifyScopeStream, hasCwdMaterialization, parseScopeProvenance, parseScopeStream, sessionRuntimePaths, temporalScopePaths, type SessionAddress } from "./durable.ts";
|
|
5
5
|
import { pruneArtifactProvenance, type ArtifactProvenance, type ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
6
|
-
import { assertTemporalFileBase, captureTemporalFileBase, initializeFileStore, publishTemporalStateToFiles, withStorageTransaction, type StorageTransaction, type TemporalFileBase } from "./storage.ts";
|
|
6
|
+
import { assertTemporalFileBase, captureSelectedModuleAdditions, captureSelectedProvenanceUpdates, captureTemporalFileBase, temporalFileContents, initializeFileStore, publishTemporalStateToFiles, withStorageTransaction, type StorageTransaction, type TemporalFileBase, type SelectedFilePublication, type SelectedOriginRequest, type SelectedSessionAcquisitionRequest, type SelectedForkCopyRequest, type SelectedFileCohort, type SelectedModuleAdditions, type SelectedProvenanceUpdates, type SelectedReadObservation } from "./storage.ts";
|
|
7
7
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, type AcceptedTransition, type RecentTransitionWindow } from "./history.ts";
|
|
8
|
-
import { hashJson, sameJson } from "./json.ts";
|
|
9
|
-
import { RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, preRuntimeCheckpoint, retainedBoundaryCheckpoint, type RetainedPiCheckpoint, type Snapshot, type StateFlowMode } from "./snapshot.ts";
|
|
10
|
-
import { type MaterializedState, type SemanticState, type ScopedSemanticStates, type ScopedStates, type StateScope } from "./state.ts";
|
|
11
|
-
import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalScopes, readTemporalState, readTemporalView, validateScopeLineage, type ScopeStream, type TemporalState } from "./temporal.ts";
|
|
8
|
+
import { hashJson, isObject, sameJson } from "./json.ts";
|
|
9
|
+
import { RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, preRuntimeCheckpoint, retainedBoundaryCheckpoint, captureLifecycleSnapshot, type RetainedPiCheckpoint, type SelectedRetainedPiCheckpoint, type Snapshot, type StateFlowMode, type PrivateAcquisition } from "./snapshot.ts";
|
|
10
|
+
import { type MaterializedState, type SemanticState, type ScopedSemanticStates, type ScopedStates, type ScopeStateValues, type StateScope } from "./state.ts";
|
|
11
|
+
import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalScopes, readTemporalState, readTemporalView, selectTemporalState, validateScopeLineage, type ScopeStream, type TemporalState, type SelectedTemporalState } from "./temporal.ts";
|
|
12
|
+
|
|
13
|
+
import { captureScopeSelection, type ScopeSelection } from "./selection.ts";
|
|
12
14
|
|
|
13
15
|
const SCOPES = ["global", "cwd", "session"] as const;
|
|
14
16
|
const SHARED_SCOPES = ["global", "cwd"] as const;
|
|
@@ -21,6 +23,42 @@ export interface RuntimePatchTransaction {
|
|
|
21
23
|
publish(snapshot: Snapshot, accepted?: AcceptedTransition, provenance?: Partial<Record<StateScope, Record<string, ArtifactProvenance>>>): RuntimePublication;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
export interface SelectedRuntimePatchTransaction {
|
|
27
|
+
readonly selection: ScopeSelection;
|
|
28
|
+
readonly states: ScopeStateValues;
|
|
29
|
+
readonly modules?: SelectedModuleAdditions;
|
|
30
|
+
readonly snapshot: Snapshot;
|
|
31
|
+
readonly causalBasis: string;
|
|
32
|
+
readonly provenance: SelectedProvenanceUpdates;
|
|
33
|
+
publish(snapshot: Snapshot, accepted?: AcceptedTransition, provenance?: SelectedProvenanceUpdates, moduleAdditions?: SelectedModuleAdditions): SelectedFilePublication;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SelectedLifecycleInspection {
|
|
37
|
+
readonly selection: ScopeSelection;
|
|
38
|
+
readonly privateAcquisition: PrivateAcquisition;
|
|
39
|
+
readonly snapshot: Snapshot;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function selectedModules(view: SelectedTemporalState): SelectedModuleAdditions {
|
|
43
|
+
return Object.fromEntries(view.selection.filter(scope => view.scopes[scope]?.modules !== undefined)
|
|
44
|
+
.map(scope => [scope, structuredClone(view.scopes[scope]!.modules!)]));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function selectedRunSnapshot(cohort: Pick<SelectedFileCohort, "lifecycle">): Snapshot {
|
|
48
|
+
const source = cohort.lifecycle.runtime;
|
|
49
|
+
const meta = { step: source.meta.step, ...Object.fromEntries(["specification", "bootstrap", "validation"]
|
|
50
|
+
.filter((key) => Object.hasOwn(source.meta, key)).map((key) => [key, source.meta[key]])) };
|
|
51
|
+
return captureLifecycleSnapshot({ config: { mode: source.config.mode }, meta });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Detached replay inputs; excluded patch bodies must not be touched even for a rejected raw replay. */
|
|
55
|
+
function captureSelectedReplay(accepted: AcceptedTransition | undefined, selection: ScopeSelection): AcceptedTransition | undefined {
|
|
56
|
+
if (accepted === undefined) return undefined;
|
|
57
|
+
if (!isObject(accepted) || !Array.isArray(accepted.transitions)) throw new Error("Invalid selected runtime replay");
|
|
58
|
+
for (const item of accepted.transitions) if (!isObject(item) || !selection.includes(item.scope as StateScope)) throw new Error("Selected runtime replay contains an excluded owner");
|
|
59
|
+
return structuredClone(accepted);
|
|
60
|
+
}
|
|
61
|
+
|
|
24
62
|
type PublicationSelection =
|
|
25
63
|
| { kind: "patch" | "lifecycle" }
|
|
26
64
|
| { kind: "start"; allowCreateOrigin: boolean }
|
|
@@ -78,10 +116,22 @@ function freshEmptyScopeStream(scope: StateScope, origin: string, historyLimit:
|
|
|
78
116
|
|
|
79
117
|
/** Cached branch-selected temporal state and publication basis; excludes Pi event policy. */
|
|
80
118
|
export class TemporalRuntime {
|
|
81
|
-
view: TemporalState |
|
|
119
|
+
private memory: { kind: "legacy"; view: TemporalState } | { kind: "selected"; cohort: SelectedFileCohort }
|
|
120
|
+
| { kind: "observed"; observation: SelectedReadObservation } | undefined;
|
|
121
|
+
/** Compatibility access to the legacy cache only; selected memory never masquerades as three owners. */
|
|
122
|
+
get view(): TemporalState | undefined { return this.memory?.kind === "legacy" ? this.memory.view : undefined; }
|
|
123
|
+
set view(value: TemporalState | undefined) {
|
|
124
|
+
if (value !== undefined) this.assertLegacyAdmission();
|
|
125
|
+
this.memory = value === undefined ? undefined : { kind: "legacy", view: value };
|
|
126
|
+
}
|
|
82
127
|
private base: TemporalFileBase | undefined;
|
|
83
128
|
private semanticRevision: string | undefined;
|
|
84
129
|
private savedRuntime: string | undefined;
|
|
130
|
+
/** Backend choice survives cache clearing but grants no origin, private acquisition or publication authority. */
|
|
131
|
+
private selectedBackendChosen = false;
|
|
132
|
+
private selectedOriginAccepted = false;
|
|
133
|
+
private observedSessionAuthority = false;
|
|
134
|
+
private observedPrivateSemantics = false;
|
|
85
135
|
private transaction: StorageTransaction | undefined;
|
|
86
136
|
private provenanceByScope: Record<StateScope, ArtifactProvenanceRegistry> = emptyProvenance();
|
|
87
137
|
/** Shared scopes whose wholly absent live basis was accepted after one stale-target refusal. */
|
|
@@ -101,19 +151,201 @@ export class TemporalRuntime {
|
|
|
101
151
|
get sessionId(): string { return this.session.id; }
|
|
102
152
|
get sessionKey(): string { return this.session.key; }
|
|
103
153
|
|
|
154
|
+
private rememberSessionAuthority(base: TemporalFileBase | undefined): void {
|
|
155
|
+
const privatePaths = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
156
|
+
const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
157
|
+
const privateFiles = new Set([privatePaths.checkpoint, privatePaths.patches, privatePaths.meta]);
|
|
158
|
+
for (const file of base?.files ?? []) if (file.identity !== "missing") {
|
|
159
|
+
if (privateFiles.has(file.path)) this.observedPrivateSemantics = true;
|
|
160
|
+
if (privateFiles.has(file.path) || file.path === paths.config || file.path === paths.runtime) this.observedSessionAuthority = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private assertLegacyAdmission(): void {
|
|
165
|
+
if (this.selectedBackendChosen) throw new Error("Selected State Flow memory requires selected runtime operations");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Only owned durable acceptance calls this; returned receipts and read-only candidates are not installers. */
|
|
169
|
+
private installSelected(cohort: SelectedFileCohort): void {
|
|
170
|
+
this.memory = { kind: "selected", cohort: structuredClone(cohort) };
|
|
171
|
+
this.selectedBackendChosen = true;
|
|
172
|
+
this.clearLegacyBasis();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private clearLegacyBasis(): void {
|
|
176
|
+
this.base = undefined;
|
|
177
|
+
this.savedRuntime = undefined;
|
|
178
|
+
this.semanticRevision = undefined;
|
|
179
|
+
this.provenanceByScope = emptyProvenance();
|
|
180
|
+
this.absentSharedScopes.clear();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private installObserved(observation: SelectedReadObservation): void {
|
|
184
|
+
this.memory = { kind: "observed", observation: structuredClone(observation) };
|
|
185
|
+
this.selectedBackendChosen = true;
|
|
186
|
+
this.clearLegacyBasis();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private observationGuard(revalidateNativePolicy: () => void, signal?: AbortSignal): () => void {
|
|
190
|
+
const memory = this.memory;
|
|
191
|
+
return () => {
|
|
192
|
+
signal?.throwIfAborted();
|
|
193
|
+
if (this.selectedOriginAccepted || this.semanticRevision || this.savedRuntime) throw new Error("Accepted State Flow memory requires current acceptance, not observation");
|
|
194
|
+
if (this.memory !== memory) throw new Error("Selected State Flow runtime selection or acceptance changed concurrently");
|
|
195
|
+
if (typeof revalidateNativePolicy !== "function") throw new Error("Selected observation requires independent native policy authority");
|
|
196
|
+
revalidateNativePolicy();
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private selectedReadCache(): Pick<SelectedReadObservation, "view" | "provenance"> | undefined {
|
|
201
|
+
if (this.memory?.kind === "selected") return this.memory.cohort;
|
|
202
|
+
if (this.memory?.kind === "observed") return this.memory.observation;
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Detached read-only selected values; never accepted run fields or a publication/acquisition basis. */
|
|
207
|
+
observedSelectedView(): SelectedTemporalState | undefined {
|
|
208
|
+
return this.memory?.kind === "observed" ? structuredClone(this.memory.observation.view) : undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Explicit shared inspection before acquisition; missing owners stay unavailable rather than becoming empty streams. */
|
|
212
|
+
async refreshSelectedShared(selection: ScopeSelection, revalidateNativePolicy: () => void, signal?: AbortSignal): Promise<boolean> {
|
|
213
|
+
const wanted = captureScopeSelection(selection);
|
|
214
|
+
if (wanted.includes("session")) throw new Error("Shared State Flow observation cannot select Session");
|
|
215
|
+
const assertAuthority = this.observationGuard(revalidateNativePolicy, signal);
|
|
216
|
+
const install = (observation: SelectedReadObservation): boolean => {
|
|
217
|
+
assertAuthority();
|
|
218
|
+
this.installObserved(observation);
|
|
219
|
+
return observation.view !== undefined;
|
|
220
|
+
};
|
|
221
|
+
assertAuthority();
|
|
222
|
+
if (!lstatSync(this.root, { throwIfNoEntry: false })) return install({ selection: wanted, provenance: {} });
|
|
223
|
+
return withStorageTransaction(this.root, storage => {
|
|
224
|
+
assertAuthority();
|
|
225
|
+
return install(storage.observeSelectedShared(this.cwd, wanted, this.root));
|
|
226
|
+
}, signal);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Header-only disposition data; never semantic observation, acceptance, or acquisition permission. */
|
|
230
|
+
async inspectSelectedLifecycle(selection: ScopeSelection, revalidateNativePolicy: () => void, signal?: AbortSignal): Promise<SelectedLifecycleInspection | undefined> {
|
|
231
|
+
const wanted = captureScopeSelection(selection);
|
|
232
|
+
const memory = this.memory;
|
|
233
|
+
const check = () => {
|
|
234
|
+
signal?.throwIfAborted();
|
|
235
|
+
if (this.memory !== memory) throw new Error("State Flow attachment selection changed");
|
|
236
|
+
revalidateNativePolicy();
|
|
237
|
+
};
|
|
238
|
+
check();
|
|
239
|
+
if (!lstatSync(this.root, { throwIfNoEntry: false })) {
|
|
240
|
+
if (this.observedSessionAuthority) throw new RevisionUnavailableError("Known selected session authority is unavailable");
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
return withStorageTransaction(this.root, storage => {
|
|
244
|
+
check();
|
|
245
|
+
const lifecycle = storage.inspectSelectedLifecycle(this.cwd, this.sessionId, wanted, this.root, this.sessionKey);
|
|
246
|
+
check();
|
|
247
|
+
if (!lifecycle) {
|
|
248
|
+
if (this.observedSessionAuthority) throw new RevisionUnavailableError("Known selected session authority is unavailable");
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
if (this.observedPrivateSemantics && lifecycle.runtime.meta.privateAcquisition !== "acquired") throw new Error("Known acquired State Flow private authority cannot be downgraded");
|
|
252
|
+
this.observedSessionAuthority = true;
|
|
253
|
+
if (lifecycle.runtime.meta.privateAcquisition === "acquired") this.observedPrivateSemantics = true;
|
|
254
|
+
return { selection: captureScopeSelection(lifecycle.runtime.config.scopes), privateAcquisition: lifecycle.runtime.meta.privateAcquisition,
|
|
255
|
+
snapshot: selectedRunSnapshot({ lifecycle }) };
|
|
256
|
+
}, signal);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Native bookmarks never decide whether current canonical authority exists. */
|
|
260
|
+
async hasSelectedCurrentAuthority(selection: ScopeSelection, revalidateNativePolicy: () => void, signal?: AbortSignal): Promise<boolean> {
|
|
261
|
+
return await this.inspectSelectedLifecycle(selection, revalidateNativePolicy, signal) !== undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Fenced inactive restoration/operator inspection: source proof only, without conversion or durable acceptance. */
|
|
265
|
+
async refreshSelectedCurrent(selection: ScopeSelection, revalidateNativePolicy: () => void, signal?: AbortSignal): Promise<Snapshot> {
|
|
266
|
+
const wanted = captureScopeSelection(selection);
|
|
267
|
+
const assertAuthority = this.observationGuard(revalidateNativePolicy, signal);
|
|
268
|
+
assertAuthority();
|
|
269
|
+
if (!lstatSync(this.root, { throwIfNoEntry: false })) throw new RevisionUnavailableError("Selected current session storage is unavailable");
|
|
270
|
+
return withStorageTransaction(this.root, storage => {
|
|
271
|
+
assertAuthority();
|
|
272
|
+
const current = storage.prepareSelectedCurrent(this.cwd, this.sessionId, wanted, this.historyLimit, this.root, this.sessionKey);
|
|
273
|
+
if (this.observedPrivateSemantics && current.lifecycle.runtime.meta.privateAcquisition !== "acquired") throw new Error("Known acquired State Flow private authority cannot be downgraded");
|
|
274
|
+
// Current acceptance can plan absent shared owners; observation must not expose those planned empty streams.
|
|
275
|
+
const present = wanted.filter(scope => {
|
|
276
|
+
const path = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).checkpoint;
|
|
277
|
+
return current.basis.files.some(file => file.path === path && file.identity !== "missing");
|
|
278
|
+
});
|
|
279
|
+
const observation: SelectedReadObservation = { selection: wanted,
|
|
280
|
+
provenance: Object.fromEntries(present.map(scope => [scope, current.provenance[scope]!])),
|
|
281
|
+
...(present.length === 0 ? {} : { view: selectTemporalState(current.view, captureScopeSelection(present), this.historyLimit) }),
|
|
282
|
+
};
|
|
283
|
+
const snapshot = selectedRunSnapshot(current);
|
|
284
|
+
assertAuthority();
|
|
285
|
+
this.rememberSessionAuthority(current.basis);
|
|
286
|
+
if (current.lifecycle.runtime.meta.privateAcquisition === "acquired") this.observedPrivateSemantics = true;
|
|
287
|
+
this.installObserved(observation);
|
|
288
|
+
return snapshot;
|
|
289
|
+
}, signal);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Detached accepted selected values only, never a file receipt or publication capability. */
|
|
293
|
+
selectedView(): SelectedTemporalState | undefined {
|
|
294
|
+
return this.memory?.kind === "selected" ? structuredClone(this.memory.cohort.view) : undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Accepted run fields only; local policy and pending native prompts remain the adapter's responsibility. */
|
|
298
|
+
selectedSnapshot(): Snapshot {
|
|
299
|
+
if (this.memory?.kind !== "selected") throw new Error("Selected State Flow runtime is unavailable");
|
|
300
|
+
return selectedRunSnapshot(this.memory.cohort);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Native bookkeeping may change mode, never invent accepted run fields or a semantic boundary. */
|
|
304
|
+
retainedSelectedCheckpoint(mode?: StateFlowMode): SelectedRetainedPiCheckpoint {
|
|
305
|
+
const memory = this.memory;
|
|
306
|
+
if (memory?.kind !== "selected") throw new Error("Selected State Flow runtime is unavailable");
|
|
307
|
+
const snapshot = selectedRunSnapshot(memory.cohort);
|
|
308
|
+
if (mode !== undefined) snapshot.config.mode = mode;
|
|
309
|
+
return { ...retainedBoundaryCheckpoint(snapshot, memory.cohort.view.lineage.at(-1)!.id), version: 2, scopes: captureScopeSelection(memory.cohort.view.selection) };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Read-only cache identity lease; no source acquisition, storage receipt or publication authority. */
|
|
313
|
+
captureSelectedReadBasis(): () => void {
|
|
314
|
+
const memory = this.memory;
|
|
315
|
+
if (!this.selectedReadCache()?.view) throw new Error("Selected State Flow runtime is unavailable");
|
|
316
|
+
return () => { if (this.memory !== memory) throw new Error("State Flow module context content basis changed"); };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Exact present owners for the existing selected staging engine; no defaulted disabled scopes. */
|
|
320
|
+
selectedStates(offset = 0): { selection: ScopeSelection; states: ScopeStateValues } {
|
|
321
|
+
const view = this.selectedReadCache()?.view;
|
|
322
|
+
if (!view) throw new Error("Selected State Flow runtime is unavailable");
|
|
323
|
+
return { selection: captureScopeSelection(view.selection), states: readTemporalScopes(view, offset, this.historyLimit) };
|
|
324
|
+
}
|
|
325
|
+
|
|
104
326
|
/** Runtime-owned artifact compilation evidence for one scope; never model-visible state. */
|
|
105
327
|
artifactProvenance(scope: StateScope): ArtifactProvenanceRegistry {
|
|
328
|
+
const selected = this.selectedReadCache();
|
|
329
|
+
if (selected) {
|
|
330
|
+
if (!selected.view) throw new Error(`State Flow ${scope} scope is excluded or unavailable`);
|
|
331
|
+
if (!selected.view.selection.includes(scope)) throw new Error(`State Flow ${scope} scope is excluded`);
|
|
332
|
+
return structuredClone(selected.provenance[scope]!);
|
|
333
|
+
}
|
|
334
|
+
this.assertLegacyAdmission();
|
|
106
335
|
return structuredClone(this.provenanceByScope[scope]);
|
|
107
336
|
}
|
|
108
337
|
|
|
109
338
|
/** Read canonical shared memory without creating, migrating, or publishing storage. */
|
|
110
339
|
loadPassive(): boolean {
|
|
340
|
+
this.assertLegacyAdmission();
|
|
111
341
|
if (!lstatSync(this.root, { throwIfNoEntry: false })) return false;
|
|
112
342
|
return this.loadPassiveBase(captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey));
|
|
113
343
|
}
|
|
114
344
|
|
|
115
345
|
private loadPassiveBase(base: TemporalFileBase): boolean {
|
|
116
|
-
|
|
346
|
+
this.rememberSessionAuthority(this.base);
|
|
347
|
+
this.rememberSessionAuthority(base);
|
|
348
|
+
const files = temporalFileContents(base);
|
|
117
349
|
const shared = Object.fromEntries(SHARED_SCOPES.map((scope) => {
|
|
118
350
|
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
119
351
|
return [scope, parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope,
|
|
@@ -150,26 +382,31 @@ export class TemporalRuntime {
|
|
|
150
382
|
|
|
151
383
|
|
|
152
384
|
read(offset = 0, scope?: StateScope): MaterializedState {
|
|
153
|
-
|
|
154
|
-
|
|
385
|
+
const view = this.selectedReadCache()?.view ?? this.view;
|
|
386
|
+
if (!view) throw new Error("State Flow temporal runtime is unavailable");
|
|
387
|
+
return readTemporalState(view, offset, scope, this.historyLimit);
|
|
155
388
|
}
|
|
156
389
|
|
|
157
390
|
readView(offset = 0, scope?: StateScope): SemanticState {
|
|
158
|
-
|
|
159
|
-
|
|
391
|
+
const view = this.selectedReadCache()?.view ?? this.view;
|
|
392
|
+
if (!view) throw new Error("State Flow temporal runtime is unavailable");
|
|
393
|
+
return readTemporalView(view, offset, scope, this.historyLimit);
|
|
160
394
|
}
|
|
161
395
|
|
|
162
396
|
states(): ScopedStates {
|
|
397
|
+
this.assertLegacyAdmission();
|
|
163
398
|
return { global: this.read(0, "global"), cwd: this.read(0, "cwd"), session: this.read(0, "session") };
|
|
164
399
|
}
|
|
165
400
|
|
|
166
401
|
causalBasis(): string {
|
|
167
|
-
|
|
168
|
-
|
|
402
|
+
const view = this.selectedReadCache()?.view ?? this.view;
|
|
403
|
+
if (!view) throw new Error("State Flow temporal runtime is unavailable");
|
|
404
|
+
return view.lineage.at(-1)!.id;
|
|
169
405
|
}
|
|
170
406
|
|
|
171
407
|
/** Encode Pi lifecycle state against the current retained semantic boundary. */
|
|
172
408
|
retainedCheckpoint(snapshot: Snapshot): RetainedPiCheckpoint {
|
|
409
|
+
this.assertLegacyAdmission();
|
|
173
410
|
if (!this.view) return preRuntimeCheckpoint(snapshot.config.mode);
|
|
174
411
|
return retainedBoundaryCheckpoint(snapshot, this.causalBasis());
|
|
175
412
|
}
|
|
@@ -180,9 +417,11 @@ export class TemporalRuntime {
|
|
|
180
417
|
|
|
181
418
|
recent(): RecentTransitionWindow {
|
|
182
419
|
const result: RecentTransitionWindow = [];
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
420
|
+
const selected = this.selectedReadCache()?.view;
|
|
421
|
+
const view = selected ?? this.view;
|
|
422
|
+
for (const boundary of view?.lineage.slice(1) ?? []) {
|
|
423
|
+
const transitions = (selected?.selection ?? SCOPES).flatMap((scope) => {
|
|
424
|
+
const record = view!.scopes[scope]!.patches.find(({ transition }) => transition.id === boundary.id);
|
|
186
425
|
return record ? [{ scope, patch: structuredClone(record.patch) }] : [];
|
|
187
426
|
});
|
|
188
427
|
if (transitions.length) result.push({ id: boundary.id, at: boundary.position, transitions });
|
|
@@ -193,6 +432,7 @@ export class TemporalRuntime {
|
|
|
193
432
|
/** Await a coherent read-only recovery view; this neither activates policy nor accepts publication authority. */
|
|
194
433
|
async refreshCurrentMemory(signal?: AbortSignal): Promise<Snapshot | undefined> {
|
|
195
434
|
signal?.throwIfAborted();
|
|
435
|
+
this.assertLegacyAdmission();
|
|
196
436
|
if (!lstatSync(this.root, { throwIfNoEntry: false })) return undefined;
|
|
197
437
|
return withStorageTransaction(this.root, (storage) => {
|
|
198
438
|
const base = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
@@ -202,10 +442,11 @@ export class TemporalRuntime {
|
|
|
202
442
|
}
|
|
203
443
|
|
|
204
444
|
private loadCurrentMemoryBase(base: TemporalFileBase): Snapshot | undefined {
|
|
205
|
-
const files =
|
|
445
|
+
const files = temporalFileContents(base);
|
|
206
446
|
const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
207
447
|
const session = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
208
448
|
if ([paths.config, paths.runtime, session.checkpoint, session.patches, session.meta].every((path) => files.get(path) === undefined)) return undefined;
|
|
449
|
+
this.rememberSessionAuthority(base);
|
|
209
450
|
const document = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), this.cwd, this.sessionId);
|
|
210
451
|
if (!document) throw new RevisionUnavailableError("Current State Flow session runtime is unavailable");
|
|
211
452
|
const origin = `start:${randomUUID()}`;
|
|
@@ -252,12 +493,13 @@ export class TemporalRuntime {
|
|
|
252
493
|
newSessionOrigin?: boolean;
|
|
253
494
|
copy?: SessionCopy;
|
|
254
495
|
}): RuntimePublication | undefined {
|
|
496
|
+
if (this.selectedBackendChosen) throw new Error("Selected State Flow origin requires selected runtime integration");
|
|
255
497
|
const { allowCreateCwd, expectedShared, newSessionOrigin = false, copy } = options;
|
|
256
498
|
const hasCwd = hasCwdMaterialization(this.cwd, this.root);
|
|
257
499
|
if (!allowCreateCwd && !hasCwd) return undefined;
|
|
258
500
|
if (!copy && allowCreateCwd) initializeFileStore(this.root);
|
|
259
501
|
const base = captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
260
|
-
const files =
|
|
502
|
+
const files = temporalFileContents(base);
|
|
261
503
|
if (copy) {
|
|
262
504
|
const session = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
263
505
|
const owned = [session.checkpoint, session.patches, session.meta, join(session.directory, "config.json"), join(session.directory, "runtime.json"), join(session.directory, "state.json")];
|
|
@@ -399,6 +641,7 @@ export class TemporalRuntime {
|
|
|
399
641
|
/** Await coherent shared inspection, lazily loading an empty private view only when none is selected. */
|
|
400
642
|
async refreshShared(signal?: AbortSignal): Promise<boolean> {
|
|
401
643
|
signal?.throwIfAborted();
|
|
644
|
+
this.assertLegacyAdmission();
|
|
402
645
|
if (!this.view && !lstatSync(this.root, { throwIfNoEntry: false })) return false;
|
|
403
646
|
return withStorageTransaction(this.root, (storage) => {
|
|
404
647
|
const current = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
@@ -428,7 +671,7 @@ export class TemporalRuntime {
|
|
|
428
671
|
candidate.provenanceByScope = reconciled.provenance;
|
|
429
672
|
return candidate;
|
|
430
673
|
}
|
|
431
|
-
const files =
|
|
674
|
+
const files = temporalFileContents(current);
|
|
432
675
|
const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
433
676
|
const session = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
434
677
|
if ([paths.config, paths.runtime, session.checkpoint, session.patches, session.meta].some((path) => files.get(path) !== undefined)) {
|
|
@@ -464,11 +707,314 @@ export class TemporalRuntime {
|
|
|
464
707
|
}), { kind: "patch" }, signal);
|
|
465
708
|
}
|
|
466
709
|
|
|
710
|
+
/** Authored under exclusion against validated live selected values; use the existing selected staging/commit engine. */
|
|
711
|
+
async withSelectedPatchTransaction<T>(
|
|
712
|
+
action: (transaction: SelectedRuntimePatchTransaction) => T, revalidateNativePolicy: () => void, signal?: AbortSignal, capturedTargets: readonly StateScope[] = [],
|
|
713
|
+
): Promise<T> {
|
|
714
|
+
return this.withSelectedPublicationTransaction(action, "current", revalidateNativePolicy, signal, capturedTargets);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/** Mode-only Passive persistence: exact accepted sources, no current-origin adoption or caller-authored run fields. */
|
|
718
|
+
async persistSelectedPassive(revalidateNativePolicy: () => void, signal?: AbortSignal): Promise<SelectedFilePublication> {
|
|
719
|
+
const memory = this.memory;
|
|
720
|
+
if (memory?.kind !== "selected") throw new Error("Selected State Flow runtime is unavailable");
|
|
721
|
+
const assertAuthority = (): void => {
|
|
722
|
+
signal?.throwIfAborted();
|
|
723
|
+
if (this.memory !== memory) throw new Error("Selected State Flow runtime selection or acceptance changed concurrently");
|
|
724
|
+
if (typeof revalidateNativePolicy !== "function") throw new Error("Selected Passive persistence requires independent native policy authority");
|
|
725
|
+
revalidateNativePolicy();
|
|
726
|
+
};
|
|
727
|
+
assertAuthority();
|
|
728
|
+
return withStorageTransaction(this.root, storage => {
|
|
729
|
+
assertAuthority();
|
|
730
|
+
const publication = storage.persistSelectedPassive(this.cwd, this.sessionId, memory.cohort, this.root, this.sessionKey);
|
|
731
|
+
if (publication.changed) this.installSelected(publication.cohort);
|
|
732
|
+
return publication;
|
|
733
|
+
}, signal);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Precomputed replay remains tied to its accepted cache; changed selected shared targets conflict before publication. */
|
|
737
|
+
async publishSelected(
|
|
738
|
+
snapshot: Snapshot, accepted: AcceptedTransition | undefined, revalidateNativePolicy: () => void,
|
|
739
|
+
options: { provenance?: SelectedProvenanceUpdates; moduleAdditions?: SelectedModuleAdditions; signal?: AbortSignal } = {},
|
|
740
|
+
): Promise<SelectedFilePublication> {
|
|
741
|
+
if (this.memory?.kind !== "selected") throw new Error("Selected State Flow runtime is unavailable");
|
|
742
|
+
const provenance = captureSelectedProvenanceUpdates(options.provenance, this.memory.cohort.view.selection);
|
|
743
|
+
const replay = captureSelectedReplay(accepted, this.memory.cohort.view.selection);
|
|
744
|
+
const additions = captureSelectedModuleAdditions(options.moduleAdditions, this.memory.cohort.view.selection);
|
|
745
|
+
const lifecycle = captureLifecycleSnapshot(snapshot);
|
|
746
|
+
return this.withSelectedPublicationTransaction(transaction => transaction.publish(lifecycle, replay, provenance, additions), "replay", revalidateNativePolicy, options.signal);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private async withSelectedPublicationTransaction<T>(
|
|
750
|
+
action: (transaction: SelectedRuntimePatchTransaction) => T, kind: "current" | "replay",
|
|
751
|
+
revalidateNativePolicy: () => void, signal?: AbortSignal, capturedTargets: readonly StateScope[] = [],
|
|
752
|
+
): Promise<T> {
|
|
753
|
+
const memory = this.memory;
|
|
754
|
+
if (memory?.kind !== "selected") throw new Error("Selected State Flow runtime is unavailable");
|
|
755
|
+
const selection = captureScopeSelection(memory.cohort.view.selection);
|
|
756
|
+
const targets = capturedTargets.length ? captureScopeSelection(capturedTargets) : undefined;
|
|
757
|
+
if (targets?.some(scope => !selection.includes(scope))) throw new Error("Captured State Flow write target is excluded");
|
|
758
|
+
const assertAuthority = (): void => {
|
|
759
|
+
signal?.throwIfAborted();
|
|
760
|
+
if (this.memory !== memory) throw new Error("Selected State Flow runtime selection or acceptance changed concurrently");
|
|
761
|
+
if (typeof revalidateNativePolicy !== "function") throw new Error("Selected semantic transaction requires independent native policy authority");
|
|
762
|
+
revalidateNativePolicy();
|
|
763
|
+
};
|
|
764
|
+
assertAuthority();
|
|
765
|
+
return withStorageTransaction(this.root, storage => {
|
|
766
|
+
assertAuthority();
|
|
767
|
+
const current = storage.prepareSelectedCurrent(this.cwd, this.sessionId, selection, this.historyLimit, this.root, this.sessionKey);
|
|
768
|
+
const sharedPaths = new Set(selection.filter(scope => scope !== "session").flatMap(scope => {
|
|
769
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
770
|
+
return [paths.checkpoint, paths.patches, paths.meta];
|
|
771
|
+
}));
|
|
772
|
+
assertTemporalFileBase({ files: memory.cohort.basis.files.filter(file => !sharedPaths.has(file.path)) },
|
|
773
|
+
{ files: current.basis.files.filter(file => !sharedPaths.has(file.path)) });
|
|
774
|
+
if (!sameJson(memory.cohort.presence, current.presence)) throw new Error("Selected State Flow presence dependencies changed concurrently");
|
|
775
|
+
if (targets) this.assertSelectedReplayTargets(memory.cohort, current, undefined, undefined, undefined, targets);
|
|
776
|
+
let consumed = false;
|
|
777
|
+
let published = false;
|
|
778
|
+
const result = action(Object.freeze<SelectedRuntimePatchTransaction>({
|
|
779
|
+
selection, states: readTemporalScopes(current.view, 0, this.historyLimit), modules: selectedModules(current.view), snapshot: selectedRunSnapshot(current),
|
|
780
|
+
causalBasis: current.view.lineage.at(-1)!.id, provenance: structuredClone(current.provenance),
|
|
781
|
+
publish: (snapshot, accepted, requestedProvenance, requestedModules) => {
|
|
782
|
+
if (consumed) throw new Error("Selected semantic transaction publication was already consumed");
|
|
783
|
+
consumed = true;
|
|
784
|
+
assertAuthority();
|
|
785
|
+
const provenance = captureSelectedProvenanceUpdates(requestedProvenance, selection);
|
|
786
|
+
const transition = captureSelectedReplay(accepted, selection);
|
|
787
|
+
const moduleAdditions = captureSelectedModuleAdditions(requestedModules, selection);
|
|
788
|
+
if (kind === "replay") this.assertSelectedReplayTargets(memory.cohort, current, transition, provenance, moduleAdditions);
|
|
789
|
+
const publication = storage.acceptSelectedCurrent(this.cwd, this.sessionId,
|
|
790
|
+
{ selection, historyLimit: this.historyLimit, lifecycle: captureLifecycleSnapshot(snapshot), transition, provenance, moduleAdditions }, current, this.root, this.sessionKey);
|
|
791
|
+
this.installSelected(publication.cohort);
|
|
792
|
+
published = true;
|
|
793
|
+
return publication;
|
|
794
|
+
},
|
|
795
|
+
}));
|
|
796
|
+
if (!published) throw new Error("Selected semantic transaction requires one synchronous publication");
|
|
797
|
+
return result;
|
|
798
|
+
}, signal);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private assertSelectedReplayTargets(
|
|
802
|
+
base: SelectedFileCohort, current: SelectedFileCohort, transition?: AcceptedTransition, provenance?: SelectedProvenanceUpdates, additions?: SelectedModuleAdditions, capturedTargets: readonly StateScope[] = [],
|
|
803
|
+
): void {
|
|
804
|
+
const targets = new Set([...capturedTargets, ...(transition?.transitions.map(item => item.scope) ?? []), ...Object.keys(provenance ?? {}) as StateScope[], ...Object.keys(additions ?? {}) as StateScope[]]);
|
|
805
|
+
const files = temporalFileContents(current.basis);
|
|
806
|
+
const changed: StateScope[] = [], removed: StateScope[] = [];
|
|
807
|
+
for (const scope of base.view.selection.filter(scope => scope !== "session" && targets.has(scope))) {
|
|
808
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
809
|
+
const live = classifyScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? this.cwd : undefined, files.get(paths.meta));
|
|
810
|
+
if (live.kind === "absent") { removed.push(scope); continue; }
|
|
811
|
+
if (!sameJson(live.stream, base.view.scopes[scope]) || !sameJson(current.provenance[scope], base.provenance[scope])) changed.push(scope);
|
|
812
|
+
}
|
|
813
|
+
if (removed.length > 0) throw removedTargetScopeConflict(removed);
|
|
814
|
+
if (changed.length > 0) throw targetScopeConflict(changed);
|
|
815
|
+
}
|
|
816
|
+
|
|
467
817
|
/** Activate current owned memory; the caller must authorize a wholly absent private origin after waiting. */
|
|
468
818
|
async withStartTransaction<T>(action: (current: Snapshot | undefined, publish: (snapshot: Snapshot) => RuntimePublication) => T, signal?: AbortSignal, allowCreateOrigin = false): Promise<T> {
|
|
469
819
|
return this.withPublicationTransaction((_candidate, publish, current) => action(current, (snapshot) => publish(snapshot)), { kind: "start", allowCreateOrigin }, signal);
|
|
470
820
|
}
|
|
471
821
|
|
|
822
|
+
/** New-session shared-only acceptance; caller rechecks native origin authority after waiting. */
|
|
823
|
+
async withSelectedSharedOriginTransaction<T>(
|
|
824
|
+
selection: ScopeSelection, snapshot: Snapshot, action: (publish: () => SelectedFilePublication) => T,
|
|
825
|
+
signal?: AbortSignal, allowCreateOrigin = false,
|
|
826
|
+
): Promise<T> {
|
|
827
|
+
return this.withSelectedOriginAcceptance({ kind: "shared-new", selection, snapshot, historyLimit: this.historyLimit }, action, signal, allowCreateOrigin);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/** Child-owned deferred marker only; no parent locator or parent memory is acquired here. */
|
|
831
|
+
async withSelectedDeferredForkTransaction<T>(
|
|
832
|
+
selection: ScopeSelection, lifecycle: ForkLifecycle, action: (publish: () => SelectedFilePublication) => T,
|
|
833
|
+
signal?: AbortSignal, allowDeferFork = false, modeAtPublication?: () => StateFlowMode,
|
|
834
|
+
): Promise<T> {
|
|
835
|
+
if (!lifecycle || Object.keys(lifecycle).some((key) => key !== "mode" && key !== "bootstrap")
|
|
836
|
+
|| (lifecycle.bootstrap !== undefined && lifecycle.bootstrap !== true)) throw new Error("Invalid selected fork lifecycle");
|
|
837
|
+
if (modeAtPublication !== undefined && typeof modeAtPublication !== "function") throw new Error("Invalid selected fork mode sampler");
|
|
838
|
+
const snapshot: Snapshot = { config: { mode: lifecycle.mode }, meta: { step: 0, ...(lifecycle.bootstrap ? { bootstrap: true } : {}) } };
|
|
839
|
+
// Sample mode only; callers cannot replace the fresh child run fields through this hook.
|
|
840
|
+
const author: SelectedOriginRequest["author"] = modeAtPublication ? (basis, publish) => {
|
|
841
|
+
const mode = modeAtPublication();
|
|
842
|
+
signal?.throwIfAborted();
|
|
843
|
+
publish({ config: { mode }, meta: basis.snapshot.meta });
|
|
844
|
+
} : undefined;
|
|
845
|
+
return this.withSelectedOriginAcceptance({ kind: "shared-deferred", selection, snapshot, historyLimit: this.historyLimit, author }, action, signal, allowDeferFork);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** Explicit first private acquisition, never deferred-parent recovery or repair of lost acquired state. */
|
|
849
|
+
async withSelectedSessionAcquisitionTransaction<T>(
|
|
850
|
+
request: SelectedSessionAcquisitionRequest, action: (publish: () => SelectedFilePublication) => T,
|
|
851
|
+
signal?: AbortSignal, allowAcquirePrivate = false,
|
|
852
|
+
): Promise<T> {
|
|
853
|
+
return this.withSelectedOriginAcceptance(request, action, signal, allowAcquirePrivate);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/** Stage first lifecycle/semantic authoring into one origin acceptance, never an empty publication followed by a patch. */
|
|
857
|
+
async withSelectedFirstPatchTransaction<T>(
|
|
858
|
+
selection: ScopeSelection, snapshot: Snapshot, action: (transaction: SelectedRuntimePatchTransaction) => T,
|
|
859
|
+
revalidateNativePolicy: () => void, signal?: AbortSignal, allowCreateOrigin = false,
|
|
860
|
+
): Promise<T> {
|
|
861
|
+
const memory = this.memory;
|
|
862
|
+
const check = () => {
|
|
863
|
+
signal?.throwIfAborted();
|
|
864
|
+
if (!allowCreateOrigin) throw new Error("Selected first authoring requires explicit native origin authority");
|
|
865
|
+
if (this.memory !== memory) throw new Error("Selected first authoring selection changed concurrently");
|
|
866
|
+
revalidateNativePolicy();
|
|
867
|
+
};
|
|
868
|
+
check();
|
|
869
|
+
const wanted = captureScopeSelection(selection);
|
|
870
|
+
let result: T;
|
|
871
|
+
const author: NonNullable<SelectedOriginRequest["author"]> = (basis, publish) => {
|
|
872
|
+
check();
|
|
873
|
+
result = action(Object.freeze<SelectedRuntimePatchTransaction>({
|
|
874
|
+
selection: captureScopeSelection(basis.view.selection), states: readTemporalScopes(basis.view, 0, this.historyLimit), modules: selectedModules(basis.view),
|
|
875
|
+
snapshot: basis.snapshot, causalBasis: basis.view.lineage.at(-1)!.id, provenance: basis.provenance,
|
|
876
|
+
publish: (next, transition, provenance, moduleAdditions) => {
|
|
877
|
+
check();
|
|
878
|
+
return publish(next, transition, provenance, captureSelectedModuleAdditions(moduleAdditions, wanted));
|
|
879
|
+
},
|
|
880
|
+
}));
|
|
881
|
+
};
|
|
882
|
+
await this.withSelectedOriginAcceptance({ kind: wanted.includes("session") ? "new" : "shared-new", selection: wanted, snapshot, historyLimit: this.historyLimit, author },
|
|
883
|
+
publish => { check(); publish(); }, signal, allowCreateOrigin);
|
|
884
|
+
return result!;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** Same-session current acceptance/authoring; native policy checks are synchronous under exclusion. */
|
|
888
|
+
async withSelectedCurrentTransaction<T>(
|
|
889
|
+
selection: ScopeSelection, action: (current: Snapshot,
|
|
890
|
+
publish: (snapshot?: Snapshot) => SelectedFilePublication,
|
|
891
|
+
transaction: SelectedRuntimePatchTransaction) => T,
|
|
892
|
+
revalidateNativePolicy: () => void, signal?: AbortSignal,
|
|
893
|
+
): Promise<T> {
|
|
894
|
+
const wanted = captureScopeSelection(selection);
|
|
895
|
+
const historyLimit = this.historyLimit;
|
|
896
|
+
const assertAuthority = (): void => {
|
|
897
|
+
signal?.throwIfAborted();
|
|
898
|
+
if (typeof revalidateNativePolicy !== "function") throw new Error("Selected current acceptance requires independent native policy authority");
|
|
899
|
+
if (historyLimit !== this.historyLimit) throw new Error("Selected current history policy changed concurrently");
|
|
900
|
+
revalidateNativePolicy();
|
|
901
|
+
};
|
|
902
|
+
assertAuthority();
|
|
903
|
+
if (!lstatSync(this.root, { throwIfNoEntry: false })) throw new RevisionUnavailableError("Selected current session storage is unavailable");
|
|
904
|
+
return withStorageTransaction(this.root, (storage) => {
|
|
905
|
+
assertAuthority();
|
|
906
|
+
const base = storage.prepareSelectedCurrent(this.cwd, this.sessionId, wanted, historyLimit, this.root, this.sessionKey);
|
|
907
|
+
this.rememberSessionAuthority(base.basis);
|
|
908
|
+
if (this.observedPrivateSemantics && base.lifecycle.runtime.meta.privateAcquisition !== "acquired") throw new Error("Known acquired State Flow private authority cannot be downgraded");
|
|
909
|
+
this.observedSessionAuthority = true;
|
|
910
|
+
if (base.lifecycle.runtime.meta.privateAcquisition === "acquired") this.observedPrivateSemantics = true;
|
|
911
|
+
const current = selectedRunSnapshot(base);
|
|
912
|
+
let consumed = false, published = false, authorActive = true;
|
|
913
|
+
const publish = (snapshot?: Snapshot, accepted?: AcceptedTransition, requestedProvenance?: SelectedProvenanceUpdates, requestedModules?: SelectedModuleAdditions): SelectedFilePublication => {
|
|
914
|
+
if (!authorActive) throw new Error("Selected current authoring has ended");
|
|
915
|
+
if (consumed) throw new Error("Selected current acceptance publication was already consumed");
|
|
916
|
+
consumed = true;
|
|
917
|
+
assertAuthority();
|
|
918
|
+
const lifecycle = snapshot === undefined ? undefined : captureLifecycleSnapshot(snapshot);
|
|
919
|
+
const transition = captureSelectedReplay(accepted, wanted);
|
|
920
|
+
const provenance = captureSelectedProvenanceUpdates(requestedProvenance, wanted);
|
|
921
|
+
const moduleAdditions = captureSelectedModuleAdditions(requestedModules, wanted);
|
|
922
|
+
const publication = storage.acceptSelectedCurrent(this.cwd, this.sessionId, { selection: wanted, historyLimit, lifecycle, transition, provenance, moduleAdditions }, base, this.root, this.sessionKey);
|
|
923
|
+
this.selectedOriginAccepted = true;
|
|
924
|
+
this.installSelected(publication.cohort);
|
|
925
|
+
published = true;
|
|
926
|
+
return publication;
|
|
927
|
+
};
|
|
928
|
+
try {
|
|
929
|
+
const result = action(current, snapshot => publish(snapshot), Object.freeze<SelectedRuntimePatchTransaction>({
|
|
930
|
+
selection: wanted, states: readTemporalScopes(base.view, 0, historyLimit), modules: selectedModules(base.view), snapshot: structuredClone(current),
|
|
931
|
+
causalBasis: base.view.lineage.at(-1)!.id, provenance: structuredClone(base.provenance), publish,
|
|
932
|
+
}));
|
|
933
|
+
if (!published) throw new Error("Selected current acceptance requires one synchronous publication");
|
|
934
|
+
return result;
|
|
935
|
+
} finally { authorActive = false; }
|
|
936
|
+
}, signal);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/** Prepare may read persisted native identity outside exclusion; recheck must use in-memory native policy only. */
|
|
940
|
+
async withSelectedForkCopyTransaction<T>(
|
|
941
|
+
request: SelectedForkCopyRequest, action: (publish: () => SelectedFilePublication) => T,
|
|
942
|
+
revalidateDirectParent: (source: Readonly<SessionAddress>, phase: "prepare" | "recheck") => void, signal?: AbortSignal,
|
|
943
|
+
): Promise<T> {
|
|
944
|
+
return this.withSelectedOriginAcceptance(request, action, signal, true, revalidateDirectParent);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private async withSelectedOriginAcceptance<T>(
|
|
948
|
+
request: SelectedSessionAcquisitionRequest | SelectedForkCopyRequest | (SelectedOriginRequest & { kind: "shared-new" | "shared-deferred" }),
|
|
949
|
+
action: (publish: () => SelectedFilePublication) => T, signal: AbortSignal | undefined, allowed: boolean,
|
|
950
|
+
revalidateDirectParent?: (source: Readonly<SessionAddress>, phase: "prepare" | "recheck") => void,
|
|
951
|
+
): Promise<T> {
|
|
952
|
+
if (!["shared-new", "shared-deferred", "new", "unacquired", "fork-new", "fork-deferred"].includes(request.kind)) throw new Error("Invalid selected origin admission kind");
|
|
953
|
+
const kind = request.kind;
|
|
954
|
+
const historyLimit = request.historyLimit;
|
|
955
|
+
const wanted = captureScopeSelection(request.selection);
|
|
956
|
+
const lifecycle = captureLifecycleSnapshot(request.snapshot);
|
|
957
|
+
const requestedAuthor = request.author;
|
|
958
|
+
const base = kind === "unacquired" || kind === "fork-deferred" ? structuredClone(request.base) : undefined;
|
|
959
|
+
const copying = kind === "fork-new" || kind === "fork-deferred";
|
|
960
|
+
const source = copying ? Object.freeze({ ...request.source }) : undefined;
|
|
961
|
+
const acquiring = kind === "new" || kind === "unacquired" || copying;
|
|
962
|
+
const install = (accepted: SelectedFilePublication) => {
|
|
963
|
+
this.selectedOriginAccepted = true;
|
|
964
|
+
this.observedSessionAuthority = true;
|
|
965
|
+
if (acquiring) this.observedPrivateSemantics = true;
|
|
966
|
+
this.installSelected(accepted.cohort);
|
|
967
|
+
};
|
|
968
|
+
const author: SelectedOriginRequest["author"] = requestedAuthor ? (basis, publish) => requestedAuthor(basis, (snapshot, transition, provenance, moduleAdditions) => {
|
|
969
|
+
const accepted = publish(snapshot, transition, provenance, captureSelectedModuleAdditions(moduleAdditions, wanted));
|
|
970
|
+
install(accepted);
|
|
971
|
+
return accepted;
|
|
972
|
+
}) : undefined;
|
|
973
|
+
const assertAuthority = (phase: "prepare" | "recheck" = "recheck"): void => {
|
|
974
|
+
signal?.throwIfAborted();
|
|
975
|
+
if (!allowed) throw new Error(acquiring ? "Selected private acquisition requires explicit native authority" : kind === "shared-deferred" ? "Selected fork deferral requires explicit native fork authority" : "Selected State Flow shared origin requires explicit new-session authority");
|
|
976
|
+
if (wanted.includes("session") !== acquiring) throw new Error(acquiring ? "Selected private acquisition requires Session" : "Selected shared origin cannot acquire Session");
|
|
977
|
+
if (lifecycle.config.mode === "off") throw new Error("Off cannot initialize State Flow memory");
|
|
978
|
+
if (historyLimit !== this.historyLimit) throw new Error("Selected origin history policy does not match its runtime");
|
|
979
|
+
if (copying) {
|
|
980
|
+
if (!source || typeof source.id !== "string" || typeof source.key !== "string" || source.id === this.sessionId || source.key === this.sessionKey) throw new Error("Selected fork requires a distinct direct parent");
|
|
981
|
+
if (typeof revalidateDirectParent !== "function") throw new Error("Selected fork copy requires independent native direct-parent authority");
|
|
982
|
+
revalidateDirectParent(source, phase);
|
|
983
|
+
}
|
|
984
|
+
this.rememberSessionAuthority(this.base);
|
|
985
|
+
if (kind === "unacquired" || kind === "fork-deferred") {
|
|
986
|
+
if (this.observedPrivateSemantics) throw new Error("Known acquired State Flow private memory cannot become an empty origin");
|
|
987
|
+
} else if (this.selectedOriginAccepted || this.observedSessionAuthority || this.semanticRevision || this.savedRuntime) {
|
|
988
|
+
throw new Error("Known State Flow session requires current-memory acceptance, not a new origin");
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
assertAuthority("prepare");
|
|
992
|
+
if (kind === "unacquired" || copying) {
|
|
993
|
+
if (!lstatSync(this.root, { throwIfNoEntry: false })) throw new RevisionUnavailableError("Selected private acquisition source storage is unavailable");
|
|
994
|
+
} else initializeFileStore(this.root);
|
|
995
|
+
return withStorageTransaction(this.root, (storage) => {
|
|
996
|
+
assertAuthority();
|
|
997
|
+
let consumed = false;
|
|
998
|
+
let published = false;
|
|
999
|
+
const result = action(() => {
|
|
1000
|
+
if (consumed) throw new Error("Selected State Flow origin publication was already consumed");
|
|
1001
|
+
consumed = true;
|
|
1002
|
+
assertAuthority();
|
|
1003
|
+
const input = { selection: wanted, snapshot: lifecycle, historyLimit: this.historyLimit, ...(author ? { author } : {}) };
|
|
1004
|
+
const accepted = kind === "shared-new"
|
|
1005
|
+
? storage.initializeSelectedSharedOrigin(this.cwd, this.sessionId, input, this.root, this.sessionKey)
|
|
1006
|
+
: kind === "shared-deferred" ? storage.deferSelectedFork(this.cwd, this.sessionId, input, this.root, this.sessionKey)
|
|
1007
|
+
: copying ? storage.copySelectedFork(this.cwd, this.sessionId, kind === "fork-new" ? { ...input, kind, source: source! } : { ...input, kind: "fork-deferred", source: source!, base: base! }, this.root, this.sessionKey)
|
|
1008
|
+
: storage.acquireSelectedSession(this.cwd, this.sessionId, kind === "new" ? { ...input, kind: "new" } : { ...input, kind: "unacquired", base: base! }, this.root, this.sessionKey);
|
|
1009
|
+
if (!author) install(accepted);
|
|
1010
|
+
published = true;
|
|
1011
|
+
return accepted;
|
|
1012
|
+
});
|
|
1013
|
+
if (!published) throw new Error("Selected State Flow origin requires one synchronous publication");
|
|
1014
|
+
return result;
|
|
1015
|
+
}, signal);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
472
1018
|
/** Copy the parent's current session memory into an unoccupied child; the caller rechecks native selection after waiting. */
|
|
473
1019
|
async withForkTransaction<T>(source: SessionAddress, lifecycle: ForkLifecycle, action: (selected: Snapshot, publish: (snapshot: Snapshot) => RuntimePublication) => T, signal?: AbortSignal): Promise<T> {
|
|
474
1020
|
signal?.throwIfAborted();
|
|
@@ -483,7 +1029,7 @@ export class TemporalRuntime {
|
|
|
483
1029
|
const parent = new TemporalRuntime(this.cwd, source, this.root, undefined, this.historyLimit);
|
|
484
1030
|
if (!parent.loadCurrentMemoryBase(sourceBase)) throw new RevisionUnavailableError("State Flow fork parent memory is unavailable");
|
|
485
1031
|
const base = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
486
|
-
const files =
|
|
1032
|
+
const files = temporalFileContents(base);
|
|
487
1033
|
const paths = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
488
1034
|
if ([paths.checkpoint, paths.patches, paths.meta, join(paths.directory, "config.json"), join(paths.directory, "runtime.json")].some((path) => files.get(path) !== undefined)) {
|
|
489
1035
|
throw new Error("State Flow fork target already has session storage");
|
|
@@ -520,6 +1066,7 @@ export class TemporalRuntime {
|
|
|
520
1066
|
const allowCreateOrigin = selection.kind === "start" && selection.allowCreateOrigin;
|
|
521
1067
|
const semantic = kind !== "lifecycle";
|
|
522
1068
|
const assertAuthority = (): void => {
|
|
1069
|
+
if (this.selectedBackendChosen) throw new Error("Selected State Flow origin requires selected runtime integration");
|
|
523
1070
|
if (selection.kind === "fork") {
|
|
524
1071
|
if (selection.source.id === this.sessionId || selection.source.key === this.sessionKey) throw new Error("State Flow fork requires a distinct session identity and key");
|
|
525
1072
|
if (this.view) throw new Error("State Flow fork target already has session storage");
|
|
@@ -613,7 +1160,13 @@ export class TemporalRuntime {
|
|
|
613
1160
|
const updates = options.provenance?.[scope];
|
|
614
1161
|
if (updates) for (const [path, entry] of Object.entries(updates)) nextProvenance[scope][path] = structuredClone(entry);
|
|
615
1162
|
}
|
|
616
|
-
|
|
1163
|
+
const legacyBasis = SCOPES.some((scope) => {
|
|
1164
|
+
const meta = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).meta;
|
|
1165
|
+
const content = base.files.find((file) => file.path === meta)?.content;
|
|
1166
|
+
return content !== undefined && JSON.parse(content).version < 3;
|
|
1167
|
+
});
|
|
1168
|
+
// A representation-only activation must not turn provenance normalization into migration side effects.
|
|
1169
|
+
if (!runtimeOnly && (!legacyBasis || accepted !== undefined || provenanceScopes.length > 0)) for (const scope of SCOPES) {
|
|
617
1170
|
nextProvenance[scope] = pruneArtifactProvenance(nextProvenance[scope], readTemporalState(next, 0, scope, this.historyLimit).artifacts);
|
|
618
1171
|
}
|
|
619
1172
|
const provenanceChanged = !sameJson(nextProvenance, this.provenanceByScope);
|