@llblab/pi-kit 0.18.1 → 0.19.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 +12 -0
- package/README.md +4 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -1
- package/node_modules/@llblab/pi-state-flow/README.md +102 -48
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +159 -255
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
- package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
- package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +154 -254
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
- package/node_modules/@llblab/pi-state-flow/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +1 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +1 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +13 -33
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +72 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +27 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +17 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +8 -1
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +0 -10
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +18 -41
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -8
- package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +126 -1
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +46 -0
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +19 -0
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +8 -1
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { projectRecentTransitionsWithLimit
|
|
2
|
-
import {
|
|
1
|
+
import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
2
|
+
import { retainedMemoryScopes } from "./memory.js";
|
|
3
3
|
import { overlayStates } from "./state.js";
|
|
4
4
|
export const STATUS_KEY = "state-flow";
|
|
5
5
|
export function compactStatus(snapshot, colorize) {
|
|
@@ -10,68 +10,40 @@ export function compactStatus(snapshot, colorize) {
|
|
|
10
10
|
function countArtifacts(states, scope) {
|
|
11
11
|
return Object.keys(states[scope].artifacts).length;
|
|
12
12
|
}
|
|
13
|
-
function abbreviatedCommit(commit) {
|
|
14
|
-
return commit.length > 12 ? commit.slice(0, 12) : commit;
|
|
15
|
-
}
|
|
16
13
|
export function detailedStatus(snapshot, diagnostics) {
|
|
17
|
-
const projectedRecent = projectRecentTransitionsWithLimit(
|
|
14
|
+
const projectedRecent = projectRecentTransitionsWithLimit(diagnostics.historyLimit, diagnostics.recent);
|
|
18
15
|
const available = diagnostics.temporal !== undefined && diagnostics.durableStateError === undefined;
|
|
19
16
|
const materialized = !available ? undefined : overlayStates(diagnostics.scopeStates.global, diagnostics.scopeStates.cwd, diagnostics.scopeStates.session);
|
|
20
17
|
const stateJson = materialized === undefined ? undefined : JSON.stringify(materialized, null, 2);
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
?
|
|
24
|
-
:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const staleLines = freshnessError !== undefined
|
|
29
|
-
? [`Artifact freshness unavailable: ${freshnessError}`]
|
|
30
|
-
: diagnostics.staleArtifacts.length === 0
|
|
31
|
-
? ["Stale artifacts: none"]
|
|
32
|
-
: [
|
|
33
|
-
"Stale artifacts:",
|
|
34
|
-
...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
|
|
35
|
-
];
|
|
18
|
+
const invalidated = available ? String(diagnostics.staleArtifacts.length) : "unavailable";
|
|
19
|
+
const invalidationLines = diagnostics.staleArtifacts.length === 0
|
|
20
|
+
? ["Pending artifact invalidations: none"]
|
|
21
|
+
: [
|
|
22
|
+
"Pending artifact invalidations:",
|
|
23
|
+
...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
|
|
24
|
+
];
|
|
36
25
|
const temporal = available ? diagnostics.temporal : undefined;
|
|
37
26
|
const temporalLines = temporal === undefined
|
|
38
27
|
? [`Temporal materialization unavailable: ${diagnostics.durableStateError ?? "no selected branch runtime"}`,
|
|
39
|
-
|
|
28
|
+
`Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`,
|
|
40
29
|
"Retained patch tails: unavailable"]
|
|
41
30
|
: [`Temporal head: ${JSON.stringify(temporal.head.id)}; branch-local position ${temporal.head.position}`,
|
|
42
|
-
`Hot history: offsets 0..${temporal.historyDepth}; maximum depth
|
|
31
|
+
`Hot history: offsets 0..${temporal.historyDepth}; maximum depth ${diagnostics.historyLimit}`,
|
|
43
32
|
`Retained patch tails: global ${temporal.tailCounts.global}; CWD ${temporal.tailCounts.cwd}; session ${temporal.tailCounts.session}`];
|
|
44
33
|
const artifacts = (scope) => available ? countArtifacts(diagnostics.scopeStates, scope) : "unknown";
|
|
45
|
-
const promotions = available ? inspectMemoryPromotions(diagnostics.scopeStates.global) : [];
|
|
46
|
-
const promotionCounts = Object.fromEntries(["pending", "accepted", "failed", "unknown", "invalid"].map((status) => [status, promotions.filter((entry) => entry.status === status).length]));
|
|
47
34
|
const memoryScopes = available ? retainedMemoryScopes(diagnostics.scopeStates) : undefined;
|
|
48
|
-
const oneLine = (value) => value.replace(/\s+/g, " ").slice(0, 240);
|
|
49
|
-
const promotionLines = !available || promotions.length === 0 ? [] : [
|
|
50
|
-
"Memory promotions:",
|
|
51
|
-
...promotions.map((entry) => `- ${oneLine(entry.id)} — ${entry.status}; owner ${oneLine(entry.owner ?? "unavailable")}${entry.pointer ? `; pointer ${oneLine(entry.pointer)}` : ""}${entry.revision ? `; revision ${oneLine(entry.revision)}` : ""}${entry.error ? `; error ${oneLine(entry.error)}` : ""}`),
|
|
52
|
-
];
|
|
53
35
|
return [
|
|
54
36
|
`State Flow diagnostics — config.enabled=${snapshot.config.enabled}; branch mode=${snapshot.config.enabled ? "active" : "inactive"}`,
|
|
55
37
|
`Repository: ${diagnostics.repositoryRoot}`,
|
|
56
38
|
`Scope keys: CWD ${diagnostics.cwdScopeKey}; session ${diagnostics.sessionScopeKey}`,
|
|
57
39
|
"Session files: config.json owns behavior; runtime.json owns branch recovery; meta.json owns scope provenance",
|
|
58
|
-
`Runtime metadata: step #${snapshot.meta.step};
|
|
59
|
-
`Remote publication policy: ${snapshot.meta.remotePublication?.mode ?? "legacy-transition"}`,
|
|
60
|
-
diagnostics.publicationQueueError !== undefined
|
|
61
|
-
? `Remote queue: unavailable; error ${diagnostics.publicationQueueError}`
|
|
62
|
-
: diagnostics.publicationQueue === undefined
|
|
63
|
-
? "Remote queue: idle"
|
|
64
|
-
: `Remote queue: ${diagnostics.publicationQueue.status}; target ${abbreviatedCommit(diagnostics.publicationQueue.target)}; confirmed ${diagnostics.publicationQueue.confirmed ? abbreviatedCommit(diagnostics.publicationQueue.confirmed) : "none"}; attempt ${diagnostics.publicationQueue.attempt}${diagnostics.publicationQueue.error ? `; error ${diagnostics.publicationQueue.error}` : ""}`,
|
|
40
|
+
`Runtime metadata: step #${snapshot.meta.step}; bootstrap ${snapshot.meta.bootstrap === true}`,
|
|
65
41
|
"Memory: owner state-flow; global retention enabled; global fallback active",
|
|
66
42
|
`Memory-bearing scopes: global ${memoryScopes?.global ?? "unknown"}; CWD ${memoryScopes?.cwd ?? "unknown"}; session ${memoryScopes?.session ?? "unknown"}`,
|
|
67
|
-
`Promotion status: pending ${promotionCounts.pending}; accepted ${promotionCounts.accepted}; failed ${promotionCounts.failed}; unknown ${promotionCounts.unknown}; invalid ${promotionCounts.invalid}`,
|
|
68
|
-
...promotionLines,
|
|
69
43
|
...temporalLines,
|
|
70
|
-
`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")};
|
|
44
|
+
`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; pending invalidations ${invalidated}`,
|
|
71
45
|
available ? `Recent transitions: global ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "global")).length}; CWD ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "cwd")).length}; session ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "session")).length}; active ${projectedRecent.length}` : "Recent transitions: unavailable",
|
|
72
|
-
|
|
73
|
-
`Publication: ${publication}`,
|
|
74
|
-
...staleLines,
|
|
46
|
+
...invalidationLines,
|
|
75
47
|
...(stateJson === undefined
|
|
76
48
|
? ["Effective memory: unavailable"]
|
|
77
49
|
: [`Effective memory (${Buffer.byteLength(stateJson, "utf8")} JSON bytes; global → CWD → session overlay):`, "", stateJson]),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DurableFileBase, type OwnedFileUpdate } from "./durable.ts";
|
|
2
|
-
import {
|
|
2
|
+
import type { ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
3
3
|
import { type FileRevision, type SessionRuntime } from "./snapshot.ts";
|
|
4
4
|
export { isFileRevision, type FileRevision } from "./snapshot.ts";
|
|
5
5
|
import type { StateScope } from "./state.ts";
|
|
@@ -7,17 +7,15 @@ import { type TemporalState } from "./temporal.ts";
|
|
|
7
7
|
export interface TemporalFileBase {
|
|
8
8
|
files: DurableFileBase[];
|
|
9
9
|
}
|
|
10
|
-
/** Probe once at a lifecycle boundary, never on cached state reads. Only spawn ENOENT is absence. */
|
|
11
|
-
export declare function detectGitCapability(): "git" | "files";
|
|
12
10
|
export declare function assertStorageDirectory(path: string): void;
|
|
13
11
|
/** Explicit creation only; existing bytes and unrelated files are never adopted or rewritten here. */
|
|
14
12
|
export declare function initializeFileStore(root: string): void;
|
|
15
13
|
/** Wait only for a cooperating live owner; interrupted or malformed locks remain explicit recovery errors. */
|
|
16
14
|
export declare function acquirePublicationLock(path: string, unavailable: (cause: unknown) => Error): number;
|
|
17
|
-
/**
|
|
15
|
+
/** Canonical writers and bounded backup capture share exclusion; no Git work runs under this lock. */
|
|
18
16
|
export declare function withStoragePublicationLock<T>(repositoryRoot: string, action: (root: string) => T): T;
|
|
19
17
|
export declare function assertTemporalFileBase(expected: TemporalFileBase, current: TemporalFileBase): void;
|
|
20
|
-
/**
|
|
18
|
+
/** Plan exact canonical updates; lifecycle-only writes exclude semantic files and provenance. */
|
|
21
19
|
export declare function planTemporalPublication(cwd: string, sessionId: string, view: TemporalState, scopes: readonly StateScope[], current: TemporalFileBase, root: string, runtime?: SessionRuntime, runtimeOnly?: boolean, sessionKey?: string, provenance?: Readonly<Record<StateScope, ArtifactProvenanceRegistry>>): {
|
|
22
20
|
updates: OwnedFileUpdate[];
|
|
23
21
|
changedScopes: StateScope[];
|
|
@@ -38,11 +36,9 @@ export declare function loadTemporalFileRevision(cwd: string, sessionId: string,
|
|
|
38
36
|
};
|
|
39
37
|
revision: `file:${string}`;
|
|
40
38
|
};
|
|
41
|
-
/** Publish a validated
|
|
42
|
-
export declare function publishTemporalStateToFiles(cwd: string, sessionId: string, view: TemporalState, scopes: readonly StateScope[], base: TemporalFileBase, root: string, runtime: SessionRuntime, sessionKey?: string, provenance?: Readonly<Record<StateScope, ArtifactProvenanceRegistry
|
|
39
|
+
/** Publish a validated canonical cohort; runtimeOnly owns only session config/runtime files. */
|
|
40
|
+
export declare function publishTemporalStateToFiles(cwd: string, sessionId: string, view: TemporalState, scopes: readonly StateScope[], base: TemporalFileBase, root: string, runtime: SessionRuntime, sessionKey?: string, provenance?: Readonly<Record<StateScope, ArtifactProvenanceRegistry>>, runtimeOnly?: boolean): {
|
|
43
41
|
base: TemporalFileBase;
|
|
44
42
|
revision: FileRevision;
|
|
45
43
|
changed: boolean;
|
|
46
44
|
};
|
|
47
|
-
/** In-store format conversion only; no Git history or cross-repository import. */
|
|
48
|
-
export declare function migrateLegacyStorageToFiles(cwd: string, sessionId: string, root: string, sessionKey?: string): void;
|
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
// Domain: exact file-cohort publication, current-only recovery, and cooperating worktree exclusion.
|
|
2
2
|
// Excludes: temporal algebra, Pi lifecycle, Git objects/remotes, and backend fallback policy.
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
3
|
import { createHash } from "node:crypto";
|
|
5
4
|
import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
5
|
import { dirname, relative, resolve } from "node:path";
|
|
7
6
|
import { assertOwnedFileUpdates, captureTemporalFileBases, parseScopeProvenance, parseScopeStream, restoreDurableFileBases, serializeScopeMetadata, sessionRuntimePaths, temporalScopePaths, temporalStateFileUpdates, writeOwnedFileUpdates, } from "./durable.js";
|
|
8
|
-
import {
|
|
7
|
+
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
9
8
|
import { hashJson, sameJson } from "./json.js";
|
|
10
9
|
import { RevisionUnavailableError, isFileRevision, parseSessionRuntime, serializeSessionRuntime } from "./snapshot.js";
|
|
11
|
-
import { planLegacyStorageMigration } from "./migration.js";
|
|
12
10
|
export { isFileRevision } from "./snapshot.js";
|
|
13
11
|
import { validateTemporalState } from "./temporal.js";
|
|
14
12
|
const SCOPES = ["global", "cwd", "session"];
|
|
15
|
-
/** Probe once at a lifecycle boundary, never on cached state reads. Only spawn ENOENT is absence. */
|
|
16
|
-
export function detectGitCapability() {
|
|
17
|
-
const result = spawnSync("git", ["--version"], { encoding: "utf8", timeout: 15_000 });
|
|
18
|
-
if (result.error && result.error.code === "ENOENT")
|
|
19
|
-
return "files";
|
|
20
|
-
if (result.error || result.status !== 0) {
|
|
21
|
-
throw new RevisionUnavailableError(`Cannot resolve Git capability: ${result.error?.message ?? result.stderr ?? `exit ${result.status}`}`);
|
|
22
|
-
}
|
|
23
|
-
return "git";
|
|
24
|
-
}
|
|
25
13
|
export function assertStorageDirectory(path) {
|
|
26
14
|
const root = resolve(path);
|
|
27
15
|
const parent = dirname(root);
|
|
@@ -76,7 +64,7 @@ export function acquirePublicationLock(path, unavailable) {
|
|
|
76
64
|
}
|
|
77
65
|
}
|
|
78
66
|
}
|
|
79
|
-
/**
|
|
67
|
+
/** Canonical writers and bounded backup capture share exclusion; no Git work runs under this lock. */
|
|
80
68
|
export function withStoragePublicationLock(repositoryRoot, action) {
|
|
81
69
|
const root = resolve(repositoryRoot);
|
|
82
70
|
assertStorageDirectory(root);
|
|
@@ -98,17 +86,15 @@ export function assertTemporalFileBase(expected, current) {
|
|
|
98
86
|
}))
|
|
99
87
|
throw new Error("Temporal State Flow base or scope identity changed concurrently");
|
|
100
88
|
}
|
|
101
|
-
/**
|
|
89
|
+
/** Plan exact canonical updates; lifecycle-only writes exclude semantic files and provenance. */
|
|
102
90
|
export function planTemporalPublication(cwd, sessionId, view, scopes, current, root, runtime, runtimeOnly = false, sessionKey = sessionId, provenance) {
|
|
103
91
|
const candidates = temporalStateFileUpdates(cwd, sessionId, view, scopes, root, sessionKey);
|
|
104
92
|
const files = new Map(current.files.map((file) => [file.path, file]));
|
|
105
|
-
if (runtimeOnly && scopes.length !== 0)
|
|
106
|
-
throw new Error("Runtime-only publication cannot write semantic scopes");
|
|
93
|
+
if (runtimeOnly && (scopes.length !== 0 || provenance !== undefined))
|
|
94
|
+
throw new Error("Runtime-only publication cannot write semantic scopes or artifact provenance");
|
|
107
95
|
const changedScopes = [];
|
|
108
96
|
for (const scope of SCOPES) {
|
|
109
97
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
110
|
-
if (files.get(resolve(paths.directory, "state.json")).identity !== "missing")
|
|
111
|
-
throw new Error("Legacy State Flow storage requires explicit migration");
|
|
112
98
|
const previous = parseScopeStream(files.get(paths.checkpoint).content, files.get(paths.patches).content, scope, scope === "cwd" ? cwd : undefined, files.get(paths.meta).content);
|
|
113
99
|
if (runtimeOnly || (previous !== undefined && sameJson(previous, view.scopes[scope])))
|
|
114
100
|
continue;
|
|
@@ -132,7 +118,7 @@ export function planTemporalPublication(cwd, sessionId, view, scopes, current, r
|
|
|
132
118
|
}
|
|
133
119
|
}
|
|
134
120
|
const runtimePaths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
|
|
135
|
-
const previousRuntime = parseSessionRuntime(files.get(runtimePaths.config).content, files.get(runtimePaths.runtime).content, cwd, sessionId
|
|
121
|
+
const previousRuntime = parseSessionRuntime(files.get(runtimePaths.config).content, files.get(runtimePaths.runtime).content, cwd, sessionId);
|
|
136
122
|
if (previousRuntime !== undefined && changedScopes.length > 0 && runtime === undefined)
|
|
137
123
|
throw new Error("Temporal semantic publication requires its session runtime cohort");
|
|
138
124
|
const runtimeUpdates = [];
|
|
@@ -143,12 +129,6 @@ export function planTemporalPublication(cwd, sessionId, view, scopes, current, r
|
|
|
143
129
|
if (files.get(runtimePaths.config).content !== sources.config || files.get(runtimePaths.runtime).content !== sources.runtime) {
|
|
144
130
|
runtimeUpdates.push({ path: runtimePaths.config, content: sources.config }, { path: runtimePaths.runtime, content: sources.runtime });
|
|
145
131
|
}
|
|
146
|
-
if (files.get(runtimePaths.runtime).identity === "missing" && files.get(runtimePaths.meta).content !== undefined
|
|
147
|
-
&& previousRuntime !== undefined && !provenanceUpdates.some(({ path }) => path === runtimePaths.meta)) {
|
|
148
|
-
const registry = provenance?.session ?? parseArtifactProvenanceRegistry(previousRuntime.meta.artifacts, "State Flow session artifact provenance");
|
|
149
|
-
const content = serializeScopeMetadata(registry, view.scopes.session, "session", undefined, files.get(runtimePaths.meta).content);
|
|
150
|
-
runtimeUpdates.push({ path: runtimePaths.meta, content });
|
|
151
|
-
}
|
|
152
132
|
}
|
|
153
133
|
const changedPaths = new Set(changedScopes.flatMap((scope) => {
|
|
154
134
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
@@ -177,19 +157,17 @@ function decodeFileCohort(cwd, sessionId, root, base, sessionKey = sessionId) {
|
|
|
177
157
|
const scopes = {};
|
|
178
158
|
for (const scope of SCOPES) {
|
|
179
159
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
180
|
-
if (files.get(resolve(paths.directory, "state.json")) !== undefined)
|
|
181
|
-
throw new Error("Legacy State Flow storage requires explicit migration");
|
|
182
160
|
const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? cwd : undefined, files.get(paths.meta));
|
|
183
161
|
if (!stream)
|
|
184
162
|
throw new Error("Incomplete file-only temporal scope cohort");
|
|
185
163
|
scopes[scope] = stream;
|
|
186
164
|
}
|
|
187
165
|
const paths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
|
|
188
|
-
const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), cwd, sessionId
|
|
189
|
-
if (!runtime
|
|
190
|
-
throw new Error("
|
|
166
|
+
const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), cwd, sessionId);
|
|
167
|
+
if (!runtime)
|
|
168
|
+
throw new Error("Canonical recovery requires a complete session runtime");
|
|
191
169
|
const view = { scopes, lineage: runtime.meta.lineage };
|
|
192
|
-
validateTemporalState(view);
|
|
170
|
+
validateTemporalState(view, MAX_HISTORY_LIMIT);
|
|
193
171
|
const provenance = {
|
|
194
172
|
global: parseScopeProvenance(files.get(temporalScopePaths(cwd, sessionId, "global", root, sessionKey).meta), temporalScopePaths(cwd, sessionId, "global", root, sessionKey).meta),
|
|
195
173
|
cwd: parseScopeProvenance(files.get(temporalScopePaths(cwd, sessionId, "cwd", root, sessionKey).meta), temporalScopePaths(cwd, sessionId, "cwd", root, sessionKey).meta),
|
|
@@ -211,14 +189,12 @@ export function loadTemporalFileRevision(cwd, sessionId, root, revision, session
|
|
|
211
189
|
return { base, revision, ...decodeFileCohort(cwd, sessionId, locked, base, sessionKey) };
|
|
212
190
|
});
|
|
213
191
|
}
|
|
214
|
-
/** Publish a validated
|
|
215
|
-
export function publishTemporalStateToFiles(cwd, sessionId, view, scopes, base, root, runtime, sessionKey = sessionId, provenance) {
|
|
192
|
+
/** Publish a validated canonical cohort; runtimeOnly owns only session config/runtime files. */
|
|
193
|
+
export function publishTemporalStateToFiles(cwd, sessionId, view, scopes, base, root, runtime, sessionKey = sessionId, provenance, runtimeOnly = false) {
|
|
216
194
|
return withStoragePublicationLock(root, (locked) => {
|
|
217
|
-
if (runtime.meta.publication !== "files")
|
|
218
|
-
throw new Error("File publication requires explicit file provenance");
|
|
219
195
|
const current = { files: captureTemporalFileBases(cwd, sessionId, locked, sessionKey) };
|
|
220
196
|
assertTemporalFileBase(base, current);
|
|
221
|
-
const { updates } = planTemporalPublication(cwd, sessionId, view, scopes, current, locked, runtime,
|
|
197
|
+
const { updates } = planTemporalPublication(cwd, sessionId, view, scopes, current, locked, runtime, runtimeOnly, sessionKey, provenance);
|
|
222
198
|
const next = { files: temporalFileReceipts(current, updates) };
|
|
223
199
|
decodeFileCohort(cwd, sessionId, locked, next, sessionKey);
|
|
224
200
|
const revision = fileRevision(next, locked);
|
|
@@ -242,11 +218,3 @@ function publishFileUpdates(bases, updates, root) {
|
|
|
242
218
|
throw error;
|
|
243
219
|
}
|
|
244
220
|
}
|
|
245
|
-
/** In-store format conversion only; no Git history or cross-repository import. */
|
|
246
|
-
export function migrateLegacyStorageToFiles(cwd, sessionId, root, sessionKey = sessionId) {
|
|
247
|
-
withStoragePublicationLock(root, (locked) => {
|
|
248
|
-
const plan = planLegacyStorageMigration(cwd, sessionId, locked, undefined, sessionKey);
|
|
249
|
-
if (plan.updates.length)
|
|
250
|
-
publishFileUpdates(plan.bases, plan.updates, locked);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
@@ -94,7 +94,7 @@ function renderStateFlowTelegramField(value) {
|
|
|
94
94
|
return rendered;
|
|
95
95
|
}
|
|
96
96
|
export function renderStateFlowRichState(scope, step, state) {
|
|
97
|
-
const fields = ["
|
|
97
|
+
const fields = ["intents", "contract", "working", "artifacts", "response", "lazy"];
|
|
98
98
|
return {
|
|
99
99
|
blocks: [
|
|
100
100
|
{
|
|
@@ -25,15 +25,23 @@ export interface TemporalState {
|
|
|
25
25
|
scopes: Record<StateScope, ScopeStream>;
|
|
26
26
|
}
|
|
27
27
|
/** Replay validation is shared by disk codecs and active-lineage materialization. */
|
|
28
|
-
export declare function validateScopeStream(value: unknown, scope: StateScope): asserts value is ScopeStream;
|
|
29
|
-
export declare function validateTemporalLineage(value: unknown): asserts value is TransitionBoundary[];
|
|
28
|
+
export declare function validateScopeStream(value: unknown, scope: StateScope, historyLimit?: number): asserts value is ScopeStream;
|
|
29
|
+
export declare function validateTemporalLineage(value: unknown, historyLimit?: number): asserts value is TransitionBoundary[];
|
|
30
|
+
/** Bind one owned stream to its runtime lineage without requiring patches from unrelated scopes. */
|
|
31
|
+
export declare function validateScopeLineage(stream: ScopeStream, scope: StateScope, lineage: readonly TransitionBoundary[], historyLimit?: number): void;
|
|
30
32
|
/** Validate one revision-selected cohort. Its older ancestry must be bound by the durable loader. */
|
|
31
|
-
export declare function validateTemporalState(view: TemporalState): void;
|
|
33
|
+
export declare function validateTemporalState(view: TemporalState, historyLimit?: number): void;
|
|
32
34
|
/** Adopt revision-proven inherited streams without rewriting their checkpoints or tails. */
|
|
33
|
-
export declare function adoptTemporalStreams(scopes: Record<StateScope, ScopeStream>, id: string): TemporalState;
|
|
35
|
+
export declare function adoptTemporalStreams(scopes: Record<StateScope, ScopeStream>, id: string, historyLimit?: number): TemporalState;
|
|
34
36
|
/** New or migrated state starts at a proven current boundary, with no invented past. */
|
|
35
|
-
export declare function createTemporalState(states: ScopedStates, id: string): TemporalState;
|
|
37
|
+
export declare function createTemporalState(states: ScopedStates, id: string, historyLimit?: number): TemporalState;
|
|
38
|
+
/** Fold retained tails to a lower configured limit without inventing history. */
|
|
39
|
+
export declare function constrainTemporalState(view: TemporalState, historyLimit: number): TemporalState;
|
|
40
|
+
/** Select one scope at a proven retained boundary from its owning runtime lineage. */
|
|
41
|
+
export declare function selectScopeStreamAtBoundary(stream: ScopeStream, scope: StateScope, boundary: TransitionBoundary, historyLimit?: number): ScopeStream;
|
|
42
|
+
/** Select one still-retained causal boundary without consulting an external history store. */
|
|
43
|
+
export declare function selectTemporalStateBoundary(view: TemporalState, boundaryId: string, historyLimit?: number): TemporalState;
|
|
36
44
|
/** Lazy scope/effective read at one shared transition boundary, never by local patch count. */
|
|
37
|
-
export declare function readTemporalState(view: TemporalState, offset?: number, scope?: StateScope): MaterializedState;
|
|
45
|
+
export declare function readTemporalState(view: TemporalState, offset?: number, scope?: StateScope, historyLimit?: number): MaterializedState;
|
|
38
46
|
/** Allocate the identity outside this algebra; only materially effective patches accept it. */
|
|
39
|
-
export declare function advanceTemporalState(view: TemporalState, transitions: readonly RecentScopePatch[], id: string): TemporalState;
|
|
47
|
+
export declare function advanceTemporalState(view: TemporalState, transitions: readonly RecentScopePatch[], id: string, historyLimit?: number): TemporalState;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, validateRecentTransition } from "./history.js";
|
|
2
2
|
import { applyPatch, containsNull, isJsonValue, isObject, sameJson } from "./json.js";
|
|
3
3
|
import { isMaterializedState, overlayStates } from "./state.js";
|
|
4
4
|
const SCOPES = ["global", "cwd", "session"];
|
|
5
|
+
function validateHistoryLimit(limit) {
|
|
6
|
+
if (!Number.isSafeInteger(limit) || limit < 0 || limit > MAX_HISTORY_LIMIT) {
|
|
7
|
+
throw new Error(`State Flow history limit must be an integer from 0 to ${MAX_HISTORY_LIMIT}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
5
10
|
function validateBoundary(boundary) {
|
|
6
11
|
if (!isObject(boundary) || Object.keys(boundary).sort().join(",") !== "id,parent,position"
|
|
7
12
|
|| typeof boundary.id !== "string" || boundary.id.trim().length === 0
|
|
@@ -25,7 +30,8 @@ function sameBoundary(left, right) {
|
|
|
25
30
|
return left.id === right.id && left.position === right.position && left.parent === right.parent;
|
|
26
31
|
}
|
|
27
32
|
/** Replay validation is shared by disk codecs and active-lineage materialization. */
|
|
28
|
-
export function validateScopeStream(value, scope) {
|
|
33
|
+
export function validateScopeStream(value, scope, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
34
|
+
validateHistoryLimit(historyLimit);
|
|
29
35
|
if (!SCOPES.includes(scope))
|
|
30
36
|
throw new Error("Unknown temporal scope");
|
|
31
37
|
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "checkpoint,patches"
|
|
@@ -36,8 +42,8 @@ export function validateScopeStream(value, scope) {
|
|
|
36
42
|
const stream = value;
|
|
37
43
|
validateBoundary(stream.checkpoint.through);
|
|
38
44
|
validateState(stream.checkpoint.state);
|
|
39
|
-
if (stream.patches.length >
|
|
40
|
-
throw new Error(
|
|
45
|
+
if (stream.patches.length > historyLimit)
|
|
46
|
+
throw new Error(`Temporal scope tail exceeds configured history limit ${historyLimit}`);
|
|
41
47
|
let previous = stream.checkpoint.through;
|
|
42
48
|
let state = stream.checkpoint.state;
|
|
43
49
|
const identities = new Set([previous.id]);
|
|
@@ -61,9 +67,10 @@ export function validateScopeStream(value, scope) {
|
|
|
61
67
|
identities.add(previous.id);
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
|
-
export function validateTemporalLineage(value) {
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
export function validateTemporalLineage(value, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
71
|
+
validateHistoryLimit(historyLimit);
|
|
72
|
+
if (!isJsonValue(value) || !Array.isArray(value) || value.length === 0 || value.length > historyLimit + 1) {
|
|
73
|
+
throw new Error(`Temporal lineage must contain between one and ${historyLimit + 1} boundaries`);
|
|
67
74
|
}
|
|
68
75
|
const seen = new Set();
|
|
69
76
|
for (let index = 0; index < value.length; index++) {
|
|
@@ -78,9 +85,29 @@ export function validateTemporalLineage(value) {
|
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
}
|
|
88
|
+
/** Bind one owned stream to its runtime lineage without requiring patches from unrelated scopes. */
|
|
89
|
+
export function validateScopeLineage(stream, scope, lineage, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
90
|
+
validateTemporalLineage(lineage, historyLimit);
|
|
91
|
+
validateScopeStream(stream, scope, historyLimit);
|
|
92
|
+
const oldest = lineage[0];
|
|
93
|
+
const head = lineage.at(-1);
|
|
94
|
+
if (stream.checkpoint.through.position > oldest.position)
|
|
95
|
+
throw new Error("Scope checkpoint is newer than the guaranteed hot boundary");
|
|
96
|
+
const identities = new Map(lineage.map((boundary) => [boundary.id, boundary]));
|
|
97
|
+
for (const boundary of [stream.checkpoint.through, ...stream.patches.map((record) => record.transition)]) {
|
|
98
|
+
if (boundary.position > head.position)
|
|
99
|
+
throw new Error("Temporal scope patch is beyond the active head");
|
|
100
|
+
const identity = identities.get(boundary.id);
|
|
101
|
+
const position = lineage[boundary.position - oldest.position];
|
|
102
|
+
if ((identity && !sameBoundary(identity, boundary)) || (position && !sameBoundary(position, boundary))) {
|
|
103
|
+
throw new Error("Conflicting State Flow temporal lineage");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
81
107
|
/** Validate one revision-selected cohort. Its older ancestry must be bound by the durable loader. */
|
|
82
|
-
export function validateTemporalState(view) {
|
|
83
|
-
|
|
108
|
+
export function validateTemporalState(view, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
109
|
+
validateHistoryLimit(historyLimit);
|
|
110
|
+
validateTemporalLineage(view.lineage, historyLimit);
|
|
84
111
|
const oldest = view.lineage[0];
|
|
85
112
|
const identities = new Map();
|
|
86
113
|
const positions = new Map();
|
|
@@ -100,7 +127,7 @@ export function validateTemporalState(view) {
|
|
|
100
127
|
const head = view.lineage.at(-1);
|
|
101
128
|
for (const scope of SCOPES) {
|
|
102
129
|
const stream = view.scopes[scope];
|
|
103
|
-
validateScopeStream(stream, scope);
|
|
130
|
+
validateScopeStream(stream, scope, historyLimit);
|
|
104
131
|
remember(stream.checkpoint.through);
|
|
105
132
|
if (stream.checkpoint.through.position > oldest.position) {
|
|
106
133
|
throw new Error("Scope checkpoint is newer than the guaranteed hot boundary");
|
|
@@ -123,22 +150,21 @@ export function validateTemporalState(view) {
|
|
|
123
150
|
}
|
|
124
151
|
}
|
|
125
152
|
/** Adopt revision-proven inherited streams without rewriting their checkpoints or tails. */
|
|
126
|
-
export function adoptTemporalStreams(scopes, id) {
|
|
153
|
+
export function adoptTemporalStreams(scopes, id, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
127
154
|
const boundaries = Object.values(scopes).flatMap((stream) => [stream.checkpoint.through, ...stream.patches.map((record) => record.transition)]);
|
|
128
155
|
const origin = { id, position: Math.max(...boundaries.map((boundary) => boundary.position)) + 1, parent: null };
|
|
129
156
|
const view = { lineage: [origin], scopes: structuredClone(scopes) };
|
|
130
|
-
|
|
131
|
-
return view;
|
|
157
|
+
return constrainTemporalState(view, historyLimit);
|
|
132
158
|
}
|
|
133
159
|
/** New or migrated state starts at a proven current boundary, with no invented past. */
|
|
134
|
-
export function createTemporalState(states, id) {
|
|
160
|
+
export function createTemporalState(states, id, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
135
161
|
const through = { id, position: 0, parent: null };
|
|
136
162
|
const stream = (scope) => ({
|
|
137
163
|
checkpoint: { through: structuredClone(through), state: structuredClone(states[scope]) },
|
|
138
164
|
patches: [],
|
|
139
165
|
});
|
|
140
166
|
const view = { lineage: [through], scopes: { global: stream("global"), cwd: stream("cwd"), session: stream("session") } };
|
|
141
|
-
validateTemporalState(view);
|
|
167
|
+
validateTemporalState(view, historyLimit);
|
|
142
168
|
return view;
|
|
143
169
|
}
|
|
144
170
|
function scopeAt(stream, boundary) {
|
|
@@ -150,14 +176,62 @@ function scopeAt(stream, boundary) {
|
|
|
150
176
|
}
|
|
151
177
|
return state;
|
|
152
178
|
}
|
|
179
|
+
/** Fold retained tails to a lower configured limit without inventing history. */
|
|
180
|
+
export function constrainTemporalState(view, historyLimit) {
|
|
181
|
+
validateHistoryLimit(historyLimit);
|
|
182
|
+
validateTemporalState(view, MAX_HISTORY_LIMIT);
|
|
183
|
+
const next = structuredClone(view);
|
|
184
|
+
for (const scope of SCOPES) {
|
|
185
|
+
const stream = next.scopes[scope];
|
|
186
|
+
while (stream.patches.length > historyLimit) {
|
|
187
|
+
const folded = stream.patches.shift();
|
|
188
|
+
stream.checkpoint = { through: folded.transition, state: apply(stream.checkpoint.state, folded.patch) };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
next.lineage = next.lineage.slice(-(historyLimit + 1));
|
|
192
|
+
validateTemporalState(next, historyLimit);
|
|
193
|
+
return next;
|
|
194
|
+
}
|
|
195
|
+
/** Select one scope at a proven retained boundary from its owning runtime lineage. */
|
|
196
|
+
export function selectScopeStreamAtBoundary(stream, scope, boundary, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
197
|
+
validateHistoryLimit(historyLimit);
|
|
198
|
+
validateBoundary(boundary);
|
|
199
|
+
validateScopeStream(stream, scope, historyLimit);
|
|
200
|
+
if (boundary.position < stream.checkpoint.through.position) {
|
|
201
|
+
throw new Error("Selected State Flow history boundary predates the retained scope checkpoint");
|
|
202
|
+
}
|
|
203
|
+
const selected = structuredClone(stream);
|
|
204
|
+
selected.patches = selected.patches.filter(({ transition }) => transition.position <= boundary.position);
|
|
205
|
+
validateScopeStream(selected, scope, historyLimit);
|
|
206
|
+
return selected;
|
|
207
|
+
}
|
|
208
|
+
/** Select one still-retained causal boundary without consulting an external history store. */
|
|
209
|
+
export function selectTemporalStateBoundary(view, boundaryId, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
210
|
+
validateHistoryLimit(historyLimit);
|
|
211
|
+
validateTemporalState(view, historyLimit);
|
|
212
|
+
if (typeof boundaryId !== "string" || boundaryId.trim().length === 0)
|
|
213
|
+
throw new Error("State Flow temporal boundary identity must be non-empty");
|
|
214
|
+
const index = view.lineage.findIndex(({ id }) => id === boundaryId);
|
|
215
|
+
if (index < 0)
|
|
216
|
+
throw new Error("Selected State Flow history boundary is outside the retained temporal window");
|
|
217
|
+
const target = view.lineage[index];
|
|
218
|
+
const selected = structuredClone(view);
|
|
219
|
+
selected.lineage = selected.lineage.slice(0, index + 1);
|
|
220
|
+
for (const scope of SCOPES) {
|
|
221
|
+
selected.scopes[scope].patches = selected.scopes[scope].patches.filter(({ transition }) => transition.position <= target.position);
|
|
222
|
+
}
|
|
223
|
+
validateTemporalState(selected, historyLimit);
|
|
224
|
+
return selected;
|
|
225
|
+
}
|
|
153
226
|
/** Lazy scope/effective read at one shared transition boundary, never by local patch count. */
|
|
154
|
-
export function readTemporalState(view, offset = 0, scope) {
|
|
155
|
-
|
|
156
|
-
|
|
227
|
+
export function readTemporalState(view, offset = 0, scope, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
228
|
+
validateHistoryLimit(historyLimit);
|
|
229
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > historyLimit) {
|
|
230
|
+
throw new Error(`State Flow hot-history offset must be an integer from 0 to ${historyLimit}`);
|
|
157
231
|
}
|
|
158
232
|
if (scope !== undefined && !SCOPES.includes(scope))
|
|
159
233
|
throw new Error("Unknown temporal scope");
|
|
160
|
-
validateTemporalState(view);
|
|
234
|
+
validateTemporalState(view, historyLimit);
|
|
161
235
|
const boundary = view.lineage[view.lineage.length - 1 - offset];
|
|
162
236
|
if (!boundary)
|
|
163
237
|
throw new Error("Requested history predates the proven temporal origin");
|
|
@@ -166,8 +240,9 @@ export function readTemporalState(view, offset = 0, scope) {
|
|
|
166
240
|
return overlayStates(...SCOPES.map((owner) => scopeAt(view.scopes[owner], boundary)));
|
|
167
241
|
}
|
|
168
242
|
/** Allocate the identity outside this algebra; only materially effective patches accept it. */
|
|
169
|
-
export function advanceTemporalState(view, transitions, id) {
|
|
170
|
-
|
|
243
|
+
export function advanceTemporalState(view, transitions, id, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
244
|
+
validateHistoryLimit(historyLimit);
|
|
245
|
+
validateTemporalState(view, historyLimit);
|
|
171
246
|
if (transitions.length === 0)
|
|
172
247
|
return view;
|
|
173
248
|
validateRecentTransition({ id, at: 0, transitions });
|
|
@@ -191,13 +266,18 @@ export function advanceTemporalState(view, transitions, id) {
|
|
|
191
266
|
const next = structuredClone(view);
|
|
192
267
|
for (const { scope, patch } of changes) {
|
|
193
268
|
const stream = next.scopes[scope];
|
|
194
|
-
if (
|
|
269
|
+
if (historyLimit === 0) {
|
|
270
|
+
stream.checkpoint = { through: structuredClone(boundary), state: apply(scopeAt(stream, head), patch) };
|
|
271
|
+
stream.patches = [];
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
while (stream.patches.length >= historyLimit) {
|
|
195
275
|
const folded = stream.patches.shift();
|
|
196
276
|
stream.checkpoint = { through: folded.transition, state: apply(stream.checkpoint.state, folded.patch) };
|
|
197
277
|
}
|
|
198
278
|
stream.patches.push({ transition: structuredClone(boundary), patch: structuredClone(patch) });
|
|
199
279
|
}
|
|
200
|
-
next.lineage = [...next.lineage, boundary].slice(-(
|
|
201
|
-
validateTemporalState(next);
|
|
280
|
+
next.lineage = [...next.lineage, boundary].slice(-(historyLimit + 1));
|
|
281
|
+
validateTemporalState(next, historyLimit);
|
|
202
282
|
return next;
|
|
203
283
|
}
|
|
@@ -12,8 +12,6 @@ export interface StagedScopedTransition {
|
|
|
12
12
|
causalBasis: string;
|
|
13
13
|
committed: boolean;
|
|
14
14
|
}
|
|
15
|
-
/** Validate that final eligibility has no pending acquisition/compilation obligation. */
|
|
16
|
-
export declare function validateFinalEligibility(currentStates: ScopedStates, successfulSkillReads: Iterable<SuccessfulSkillRead>, causalBasis: string, successfulArtifactReads?: Iterable<SuccessfulArtifactRead>): void;
|
|
17
15
|
/** Stage one canonical atomic scope cohort without changing the finalized response. */
|
|
18
16
|
export declare function stageAtomicScopePatches(currentStates: ScopedStates, patches: AtomicScopePatches, successfulSkillReads: Iterable<SuccessfulSkillRead>, causalBasis: string, successfulArtifactReads?: Iterable<SuccessfulArtifactRead>): StagedScopedTransition;
|
|
19
17
|
export declare function stageScopedTransition(currentStates: ScopedStates, transition: TerminalTransition, successfulSkillReads: Iterable<SuccessfulSkillRead>, causalBasis: string, successfulArtifactReads?: Iterable<SuccessfulArtifactRead>): StagedScopedTransition;
|
|
@@ -3,15 +3,15 @@ import { createAcceptedTransition } from "./history.js";
|
|
|
3
3
|
import { applyPatch, containsNull, hashJson, isObject, validatePatch } from "./json.js";
|
|
4
4
|
import { hasCompiledSkillArtifact, SKILL_ARTIFACT_COMPILER } from "./skills.js";
|
|
5
5
|
const SCOPES = new Set(["global", "cwd", "session"]);
|
|
6
|
-
const PATCH_KEYS = new Set(["
|
|
6
|
+
const PATCH_KEYS = new Set(["intents", "contract", "working", "artifacts", "lazy"]);
|
|
7
7
|
function compileReadArtifacts(nextState, patch, successfulArtifactReads, provenance) {
|
|
8
8
|
for (const read of successfulArtifactReads) {
|
|
9
9
|
const output = patch.artifacts[read.path];
|
|
10
10
|
if (!isObject(output)) {
|
|
11
|
-
throw new Error(`
|
|
11
|
+
throw new Error(`Successfully read invalidated artifact requires compiler output at ${read.scope ?? "global"}.artifacts[${JSON.stringify(read.path)}]`);
|
|
12
12
|
}
|
|
13
13
|
const compiled = compileArtifact({
|
|
14
|
-
source: { path: read.path, hash: read.hash },
|
|
14
|
+
source: { path: read.path, scope: read.scope, hash: read.hash, sourceFingerprint: read.sourceFingerprint },
|
|
15
15
|
compiler: ORDINARY_ARTIFACT_COMPILER,
|
|
16
16
|
output: output,
|
|
17
17
|
});
|
|
@@ -77,7 +77,7 @@ function validateScopePatch(scope, patch) {
|
|
|
77
77
|
validatePatch(patch);
|
|
78
78
|
for (const key of Object.keys(patch)) {
|
|
79
79
|
if (!PATCH_KEYS.has(key)) {
|
|
80
|
-
throw new Error(`
|
|
80
|
+
throw new Error(`Unknown State Flow patch key ${JSON.stringify(key)}; expected one of: ${[...PATCH_KEYS].join(", ")}`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
for (const key of ["artifacts", "contract", "working", "intents"]) {
|
|
@@ -85,8 +85,8 @@ function validateScopePatch(scope, patch) {
|
|
|
85
85
|
throw new Error(`Scoped State Flow patch field ${key} must be a JSON object`);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
if (Object.hasOwn(patch, "lazy") && patch.lazy
|
|
89
|
-
throw new Error("Scoped State Flow patch field lazy
|
|
88
|
+
if (Object.hasOwn(patch, "lazy") && !isObject(patch.lazy)) {
|
|
89
|
+
throw new Error("Scoped State Flow patch field lazy must be a JSON object");
|
|
90
90
|
}
|
|
91
91
|
if (isObject(patch.artifacts))
|
|
92
92
|
validateModelArtifactPatch(patch.artifacts);
|
|
@@ -98,7 +98,7 @@ function completePatch(patch, response) {
|
|
|
98
98
|
working: patch.working ?? {},
|
|
99
99
|
intents: patch.intents ?? {},
|
|
100
100
|
response,
|
|
101
|
-
|
|
101
|
+
lazy: structuredClone(patch.lazy ?? {}),
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
/** Stage all scope updates against one immutable basis before any state is published. */
|
|
@@ -120,7 +120,8 @@ function stageScopedSemanticTransition(currentStates, transition, successfulSkil
|
|
|
120
120
|
patches.set(scope, item.patch);
|
|
121
121
|
}
|
|
122
122
|
const cwdPatch = patches.get("cwd") ?? {};
|
|
123
|
-
const
|
|
123
|
+
const artifactReads = [...successfulArtifactReads];
|
|
124
|
+
const nextStates = { ...currentStates };
|
|
124
125
|
const provenanceUpdates = { global: {}, cwd: {}, session: {} };
|
|
125
126
|
for (const scope of SCOPES) {
|
|
126
127
|
const authored = patches.get(scope) ?? {};
|
|
@@ -128,8 +129,8 @@ function stageScopedSemanticTransition(currentStates, transition, successfulSkil
|
|
|
128
129
|
? acceptedResponse
|
|
129
130
|
: currentStates[scope].response;
|
|
130
131
|
const patch = completePatch(authored, response);
|
|
131
|
-
const nextState = applyPatch(
|
|
132
|
-
compileReadArtifacts(nextState, { artifacts:
|
|
132
|
+
const nextState = applyPatch(currentStates[scope], patch);
|
|
133
|
+
compileReadArtifacts(nextState, { artifacts: authored.artifacts ?? {} }, artifactReads.filter((read) => (read.scope ?? "global") === scope), provenanceUpdates[scope]);
|
|
133
134
|
compileReadSkills(nextState, { artifacts: scope === "cwd" ? cwdPatch.artifacts ?? {} : {} }, scope === "cwd" ? successfulSkillReads : [], provenanceUpdates.cwd);
|
|
134
135
|
validateMaterializedTransition(nextState);
|
|
135
136
|
nextStates[scope] = nextState;
|
|
@@ -146,10 +147,6 @@ function stageScopedSemanticTransition(currentStates, transition, successfulSkil
|
|
|
146
147
|
committed: false,
|
|
147
148
|
};
|
|
148
149
|
}
|
|
149
|
-
/** Validate that final eligibility has no pending acquisition/compilation obligation. */
|
|
150
|
-
export function validateFinalEligibility(currentStates, successfulSkillReads, causalBasis, successfulArtifactReads = []) {
|
|
151
|
-
stageScopedSemanticTransition(currentStates, { transitions: [] }, successfulSkillReads, causalBasis, successfulArtifactReads);
|
|
152
|
-
}
|
|
153
150
|
/** Stage one canonical atomic scope cohort without changing the finalized response. */
|
|
154
151
|
export function stageAtomicScopePatches(currentStates, patches, successfulSkillReads, causalBasis, successfulArtifactReads = []) {
|
|
155
152
|
if (!isObject(patches))
|