@llblab/pi-kit 0.18.2 → 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.
Files changed (113) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +3 -1
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -1
  6. package/node_modules/@llblab/pi-state-flow/README.md +102 -48
  7. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
  8. package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +159 -255
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
  60. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
  61. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
  62. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
  63. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
  64. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
  65. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
  66. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
  67. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
  68. package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
  69. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
  70. package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
  71. package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
  72. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
  73. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
  74. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
  75. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
  76. package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
  77. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
  78. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
  79. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
  80. package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
  81. package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
  82. package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
  83. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
  84. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
  85. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
  86. package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
  87. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
  88. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
  89. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +154 -254
  90. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
  91. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
  92. package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
  93. package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
  94. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
  95. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
  96. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
  97. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
  98. package/node_modules/@llblab/pi-state-flow/package.json +9 -6
  99. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
  100. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
  101. package/package.json +2 -2
  102. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
  103. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
  104. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
  105. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
  106. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
  107. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
  108. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
  109. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
  110. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
  111. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
  112. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
  113. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
@@ -1,15 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { lstatSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { classifyScopeStream, parseScopeProvenance, parseScopeStream, sessionRuntimePaths, temporalScopePaths } from "./durable.js";
4
+ import { classifyScopeStream, hasCwdMaterialization, parseScopeProvenance, parseScopeStream, sessionRuntimePaths, temporalScopePaths } from "./durable.js";
5
5
  import { pruneArtifactProvenance } from "./artifact.js";
6
- import { adoptFileStateToGit, initializeGitRepository, isLocalGitRepository, captureTemporalGitBase, loadTemporalRevision, migrateLegacyStorageToGit, publishTemporalStateToGit } from "./git.js";
7
- import { captureTemporalFileBase, detectGitCapability, initializeFileStore, loadTemporalFileRevision, migrateLegacyStorageToFiles, publishTemporalStateToFiles } from "./storage.js";
6
+ import { captureTemporalFileBase, initializeFileStore, publishTemporalStateToFiles } from "./storage.js";
7
+ import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
8
8
  import { hashJson, sameJson } from "./json.js";
9
- import { hasCwdMaterialization, hasLegacyStateSources } from "./migration.js";
10
- import { RevisionUnavailableError, createSessionRuntime, isFileRevision, parseSessionRuntime, resolveFileSessionRuntime, resolveSessionRuntime } from "./snapshot.js";
9
+ import { RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, retainedBoundaryCheckpoint } from "./snapshot.js";
11
10
  import { emptyState } from "./state.js";
12
- import { adoptTemporalStreams, advanceTemporalState, createTemporalState, readTemporalState, validateTemporalState } from "./temporal.js";
11
+ import { adoptTemporalStreams, advanceTemporalState, createTemporalState, readTemporalState, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
13
12
  const SCOPES = ["global", "cwd", "session"];
14
13
  const SHARED_SCOPES = ["global", "cwd"];
15
14
  function emptyProvenance() {
@@ -42,34 +41,8 @@ export class SharedScopeRemovalConflictError extends Error {
42
41
  function removedTargetScopeConflict(scopes) {
43
42
  return new SharedScopeRemovalConflictError(scopes);
44
43
  }
45
- function freshEmptyScopeStream(scope, origin) {
46
- return createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, origin).scopes[scope];
47
- }
48
- export class MissingSessionRuntimeError extends Error {
49
- constructor() { super("Linked State Flow revision has no session runtime"); }
50
- }
51
- /** Immutable target validation is independent of acquiring the live publication basis. */
52
- export function inspectRuntimeRevision(cwd, sessionId, root, revision, sessionKey = sessionId) {
53
- const loaded = loadTemporalRevision(cwd, sessionId, root, revision, sessionKey);
54
- if (!loaded.runtime)
55
- throw new MissingSessionRuntimeError();
56
- const resolved = resolveSessionRuntime(loaded.runtime.document, loaded.runtime.revision);
57
- if (!loaded.scopes.global || !loaded.scopes.cwd || !loaded.scopes.session)
58
- throw new Error("Incomplete temporal scope cohort");
59
- const view = { lineage: resolved.lineage, scopes: { global: loaded.scopes.global, cwd: loaded.scopes.cwd, session: loaded.scopes.session } };
60
- validateTemporalState(view);
61
- return { runtime: loaded.runtime, resolved, view, provenance: loaded.provenance };
62
- }
63
- /** Validate one supported immutable target before any live migration/publication. */
64
- export function inspectSnapshotRevision(cwd, sessionId, root, revision, _legacySnapshot, sessionKey = sessionId) {
65
- if (isFileRevision(revision)) {
66
- const file = loadTemporalFileRevision(cwd, sessionId, root, revision, sessionKey);
67
- return { snapshot: resolveFileSessionRuntime(file.runtime, revision), file };
68
- }
69
- if (detectGitCapability() === "files")
70
- throw new RevisionUnavailableError("Git is unavailable; cannot restore a Git-linked revision");
71
- const temporal = inspectRuntimeRevision(cwd, sessionId, root, revision, sessionKey);
72
- return { snapshot: temporal.resolved.snapshot, temporal };
44
+ function freshEmptyScopeStream(scope, origin, historyLimit) {
45
+ return createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, origin, historyLimit).scopes[scope];
73
46
  }
74
47
  /** Cached branch-selected temporal state and publication basis; excludes Pi event policy. */
75
48
  export class TemporalRuntime {
@@ -77,21 +50,25 @@ export class TemporalRuntime {
77
50
  base;
78
51
  semanticRevision;
79
52
  savedRuntime;
80
- backend;
53
+ restoredOriginPending = false;
81
54
  provenanceByScope = emptyProvenance();
82
55
  /** Shared scopes whose wholly absent live basis was accepted after one stale-target refusal. */
83
56
  absentSharedScopes = new Set();
84
57
  cwd;
85
58
  session;
86
59
  root;
87
- constructor(cwd, session, root, sessionKey) {
60
+ historyLimit;
61
+ constructor(cwd, session, root, sessionKey, historyLimit = DEFAULT_HISTORY_LIMIT) {
88
62
  this.cwd = cwd;
89
63
  this.session = Object.freeze(typeof session === "string" ? { id: session, key: sessionKey ?? session } : { ...session });
90
64
  this.root = root;
65
+ if (!Number.isSafeInteger(historyLimit) || historyLimit < 0 || historyLimit > MAX_HISTORY_LIMIT)
66
+ throw new Error(`State Flow history limit must be an integer from 0 to ${MAX_HISTORY_LIMIT}`);
67
+ this.historyLimit = historyLimit;
91
68
  }
92
69
  get sessionId() { return this.session.id; }
93
70
  get sessionKey() { return this.session.key; }
94
- /** Runtime-owned artifact freshness evidence for one scope; never model-visible state. */
71
+ /** Runtime-owned artifact compilation evidence for one scope; never model-visible state. */
95
72
  artifactProvenance(scope) {
96
73
  return structuredClone(this.provenanceByScope[scope]);
97
74
  }
@@ -99,10 +76,7 @@ export class TemporalRuntime {
99
76
  loadPassive() {
100
77
  if (!lstatSync(this.root, { throwIfNoEntry: false }))
101
78
  return false;
102
- const backend = detectGitCapability() === "git" && lstatSync(join(this.root, ".git"), { throwIfNoEntry: false }) ? "git" : "files";
103
- const base = backend === "git"
104
- ? captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey)
105
- : captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
79
+ const base = captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
106
80
  const files = new Map(base.files.map((file) => [file.path, file.content]));
107
81
  const shared = Object.fromEntries(SHARED_SCOPES.map((scope) => {
108
82
  const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
@@ -112,12 +86,10 @@ export class TemporalRuntime {
112
86
  return false;
113
87
  if (!shared.global)
114
88
  throw new Error("Incomplete passive State Flow shared storage: CWD state exists without global state");
115
- const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID());
116
- const basis = backend === "git" ? base.head ?? "unborn" : "files";
89
+ const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID(), this.historyLimit);
117
90
  // Global memory is valid before this CWD has ever materialized its own scope.
118
- this.view = adoptTemporalStreams({ global: shared.global, cwd: shared.cwd ?? fresh.scopes.cwd, session: fresh.scopes.session }, `passive:${basis}:${randomUUID()}`);
91
+ this.view = adoptTemporalStreams({ global: shared.global, cwd: shared.cwd ?? fresh.scopes.cwd, session: fresh.scopes.session }, `passive:files:${randomUUID()}`, this.historyLimit);
119
92
  this.base = base;
120
- this.backend = backend;
121
93
  this.provenanceByScope = {
122
94
  global: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "global", this.root, this.sessionKey).meta), "State Flow global metadata"),
123
95
  cwd: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "cwd", this.root, this.sessionKey).meta), "State Flow CWD metadata"),
@@ -125,73 +97,18 @@ export class TemporalRuntime {
125
97
  };
126
98
  return true;
127
99
  }
128
- /** Explicit start owns directory/repository creation; reads never call this. */
129
- prepare() {
130
- const backend = detectGitCapability();
131
- if (backend === "git")
132
- initializeGitRepository(this.root);
133
- else
134
- initializeFileStore(this.root);
135
- this.backend = backend;
136
- }
137
- /** Explicit start migrates a predecessor envelope even when passive restore already populated the cache. */
138
- migrateLegacyStorage() {
139
- if (!this.view || !this.backend || !hasLegacyStateSources(this.cwd, this.sessionId, this.root, this.sessionKey))
140
- return undefined;
141
- const publication = this.backend === "git"
142
- ? migrateLegacyStorageToGit(this.cwd, this.sessionId, this.root, this.sessionKey)
143
- : (migrateLegacyStorageToFiles(this.cwd, this.sessionId, this.root, this.sessionKey), undefined);
144
- const base = this.backend === "files"
145
- ? captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey)
146
- : captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
147
- const files = new Map(base.files.map((file) => [file.path, file.content]));
148
- const scopes = Object.fromEntries(SCOPES.map((scope) => {
149
- const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
150
- const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? this.cwd : undefined, files.get(paths.meta));
151
- if (!stream)
152
- throw new Error(`State Flow ${scopeLabel(scope)} scope disappeared during migration`);
153
- return [scope, stream];
154
- }));
155
- const migrated = { lineage: this.view.lineage, scopes };
156
- validateTemporalState(migrated);
157
- if (!sameJson(this.states(), {
158
- global: readTemporalState(migrated, 0, "global"),
159
- cwd: readTemporalState(migrated, 0, "cwd"),
160
- session: readTemporalState(migrated, 0, "session"),
161
- }))
162
- throw new Error("State Flow migration changed materialized semantic state");
163
- this.view = migrated;
164
- this.base = base;
165
- this.provenanceByScope = {
166
- global: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "global", this.root, this.sessionKey).meta), "State Flow global metadata"),
167
- cwd: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "cwd", this.root, this.sessionKey).meta), "State Flow CWD metadata"),
168
- session: this.provenanceByScope.session,
169
- };
170
- if (publication?.commit)
171
- this.semanticRevision = publication.commit;
100
+ /** Select canonical file acceptance even when Git is available; backup remains a later concern. */
101
+ prepareCanonical() {
102
+ initializeFileStore(this.root);
172
103
  }
173
- /** Explicit start can upgrade a proven file cohort; failed adoption leaves the cache in file mode. */
174
- promote(snapshot) {
175
- if (this.backend !== "files" || !this.view || detectGitCapability() === "files")
176
- return undefined;
177
- if (!snapshot.meta.durableBase || snapshot.meta.durableBase !== this.semanticRevision)
178
- throw new Error("Git adoption requires the selected file revision");
179
- const pushRemote = (snapshot.meta.remotePublication?.mode ?? "transition") === "transition";
180
- const result = adoptFileStateToGit(this.cwd, this.sessionId, this.root, snapshot.meta.durableBase, snapshot, this.sessionKey, pushRemote);
181
- this.provenanceByScope = structuredClone(result.provenance);
182
- const savedRuntime = hashJson(createSessionRuntime(snapshot, this.cwd, this.sessionId, result.view.lineage, "unconfirmed", this.provenanceByScope.session));
183
- this.view = result.view;
184
- this.base = result.base;
185
- this.backend = "git";
186
- this.semanticRevision = result.revision;
187
- this.savedRuntime = savedRuntime;
188
- this.absentSharedScopes.clear();
189
- return result;
104
+ /** Canonical preparation is the default; Git backup is a later independent concern. */
105
+ prepare() {
106
+ this.prepareCanonical();
190
107
  }
191
108
  read(offset = 0, scope) {
192
109
  if (!this.view)
193
110
  throw new Error("State Flow temporal runtime is unavailable");
194
- return readTemporalState(this.view, offset, scope);
111
+ return readTemporalState(this.view, offset, scope, this.historyLimit);
195
112
  }
196
113
  states() {
197
114
  return { global: this.read(0, "global"), cwd: this.read(0, "cwd"), session: this.read(0, "session") };
@@ -201,6 +118,15 @@ export class TemporalRuntime {
201
118
  throw new Error("State Flow temporal runtime is unavailable");
202
119
  return this.view.lineage.at(-1).id;
203
120
  }
121
+ /** Encode Pi lifecycle state against the current retained semantic boundary. */
122
+ retainedCheckpoint(snapshot) {
123
+ if (!this.view)
124
+ return { disabled: true };
125
+ return retainedBoundaryCheckpoint(snapshot, this.causalBasis());
126
+ }
127
+ usesCanonicalFiles() {
128
+ return true;
129
+ }
204
130
  recent() {
205
131
  const result = [];
206
132
  for (const boundary of this.view?.lineage.slice(1) ?? []) {
@@ -213,107 +139,115 @@ export class TemporalRuntime {
213
139
  }
214
140
  return result;
215
141
  }
216
- /** Validate selection without installing state; only a matching immutable Git inspection is reusable. */
217
- prepareRestore(revision, legacySnapshot) {
218
- const inspected = inspectSnapshotRevision(this.cwd, this.sessionId, this.root, revision, legacySnapshot, this.sessionKey);
219
- const selected = inspected.snapshot.meta.durableBase ?? revision;
142
+ /** Prepare a retained session boundary from current canonical files; shared scopes remain live. */
143
+ prepareBoundaryRestore(checkpoint) {
144
+ if (!lstatSync(this.root, { throwIfNoEntry: false }))
145
+ throw new RevisionUnavailableError("Selected State Flow boundary storage is unavailable");
146
+ const base = captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
147
+ const files = new Map(base.files.map((file) => [file.path, file.content]));
148
+ const scopes = Object.fromEntries(SCOPES.map((scope) => {
149
+ const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
150
+ const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? this.cwd : undefined, files.get(paths.meta));
151
+ if (!stream)
152
+ throw new RevisionUnavailableError(`State Flow ${scopeLabel(scope)} scope is unavailable for retained-boundary restoration`);
153
+ return [scope, stream];
154
+ }));
155
+ const runtimePaths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
156
+ const document = parseSessionRuntime(files.get(runtimePaths.config), files.get(runtimePaths.runtime), this.cwd, this.sessionId);
157
+ if (!document)
158
+ throw new RevisionUnavailableError("State Flow session runtime is unavailable for retained-boundary restoration");
159
+ // The codecs validate persisted retention against the format maximum, not the new operator limit.
160
+ validateScopeLineage(scopes.session, "session", document.meta.lineage, MAX_HISTORY_LIMIT);
161
+ const boundary = document.meta.lineage.slice(-(this.historyLimit + 1)).find(({ id }) => id === checkpoint.boundary);
162
+ if (!boundary)
163
+ throw new RevisionUnavailableError("Selected State Flow history boundary is outside the retained temporal window");
164
+ const selectedSession = selectScopeStreamAtBoundary(scopes.session, "session", boundary, MAX_HISTORY_LIMIT);
165
+ const view = adoptTemporalStreams({
166
+ global: scopes.global,
167
+ cwd: scopes.cwd,
168
+ session: selectedSession,
169
+ }, `restore:${checkpoint.boundary}:${randomUUID()}`, this.historyLimit);
170
+ const snapshot = {
171
+ config: { enabled: checkpoint.enabled },
172
+ meta: {
173
+ step: checkpoint.step,
174
+ ...(checkpoint.bootstrap === true ? { bootstrap: true } : {}),
175
+ ...(checkpoint.specification === undefined ? {} : { specification: checkpoint.specification }),
176
+ },
177
+ };
178
+ const provenance = Object.fromEntries(SCOPES.map((scope) => {
179
+ const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
180
+ const parsed = parseScopeProvenance(files.get(paths.meta), paths.meta);
181
+ const retained = pruneArtifactProvenance(parsed, readTemporalState(view, 0, scope, this.historyLimit).artifacts);
182
+ if (scope === "session") {
183
+ // Live evidence cannot prove an earlier artifact version, even after a change-away-and-back.
184
+ for (const record of scopes.session.patches) {
185
+ if (record.transition.position <= boundary.position)
186
+ continue;
187
+ for (const path of Object.keys(record.patch.artifacts ?? {}))
188
+ delete retained[path];
189
+ }
190
+ }
191
+ return [scope, retained];
192
+ }));
220
193
  let consumed = false;
221
194
  return {
222
- snapshot: structuredClone(inspected.snapshot),
195
+ snapshot: structuredClone(snapshot),
223
196
  restore: () => {
224
197
  if (consumed)
225
198
  throw new Error("Prepared State Flow restore was already consumed");
226
199
  consumed = true;
227
- // File cohorts can expire; legacy migration and owner redirection keep their fresh-read path.
228
- return inspected.temporal && selected === revision
229
- ? this.restoreInspected(revision, inspected)
230
- : this.restore(selected, inspected.snapshot);
200
+ this.view = view;
201
+ this.base = base;
202
+ this.provenanceByScope = provenance;
203
+ this.semanticRevision = undefined;
204
+ this.savedRuntime = hashJson(createSessionRuntime(snapshot, this.cwd, this.sessionId, view.lineage, provenance.session));
205
+ this.restoredOriginPending = true;
206
+ this.absentSharedScopes.clear();
207
+ return structuredClone(snapshot);
231
208
  },
232
209
  };
233
210
  }
234
- /** Copy only a proven source session stream; shared streams come from the child's fresh live basis. */
235
- prepareFork(source, revision) {
211
+ /** Restore and canonically accept one retained boundary as a single lifecycle operation. */
212
+ restoreBoundary(checkpoint) {
213
+ const prepared = this.prepareBoundaryRestore(checkpoint);
214
+ const snapshot = prepared.restore();
215
+ return { snapshot, publication: this.acceptRestoredOrigin(snapshot) };
216
+ }
217
+ /** Copy one retained source-session boundary over the child's current shared scopes. */
218
+ prepareBoundaryFork(source, checkpoint) {
236
219
  const parent = Object.freeze({ ...source });
237
220
  if (parent.id === this.sessionId || parent.key === this.sessionKey)
238
221
  throw new Error("State Flow fork requires a distinct session identity and key");
239
- const inspect = () => inspectSnapshotRevision(this.cwd, parent.id, this.root, revision, undefined, parent.key);
240
- const inspected = inspect();
222
+ const sourceRuntime = new TemporalRuntime(this.cwd, parent, this.root, undefined, this.historyLimit);
223
+ const prepared = sourceRuntime.prepareBoundaryRestore(checkpoint);
224
+ prepared.restore();
241
225
  const snapshot = {
242
- config: structuredClone(inspected.snapshot.config),
226
+ config: structuredClone(prepared.snapshot.config),
243
227
  meta: {
244
228
  step: 0,
245
- ...(inspected.snapshot.meta.bootstrap === undefined ? {} : { bootstrap: inspected.snapshot.meta.bootstrap }),
246
- ...(inspected.snapshot.meta.remotePublication === undefined ? {} : { remotePublication: structuredClone(inspected.snapshot.meta.remotePublication) }),
229
+ ...(prepared.snapshot.meta.bootstrap === undefined ? {} : { bootstrap: prepared.snapshot.meta.bootstrap }),
247
230
  },
248
231
  };
249
232
  let consumed = false;
250
233
  return {
251
- snapshot: { ...structuredClone(snapshot), meta: { ...structuredClone(snapshot.meta), durableBase: revision } },
234
+ snapshot: structuredClone(snapshot),
252
235
  fork: () => {
253
236
  if (consumed)
254
237
  throw new Error("Prepared State Flow fork was already consumed");
255
238
  consumed = true;
256
239
  if (this.view)
257
240
  throw new Error("State Flow fork target already has session storage");
258
- // Unlike immutable Git input, a file-only source must still match its complete cohort.
259
- const current = inspected.file ? inspect() : inspected;
260
- const selected = current.temporal ?? current.file;
261
- if (!selected)
262
- throw new Error("State Flow fork requires a temporal session stream");
263
241
  const publication = this.initializeOrigin(snapshot, { allowCreateCwd: false, copy: {
264
- stream: selected.view.scopes.session,
265
- provenance: selected.provenance.session,
266
- backend: current.file ? "files" : "git",
242
+ stream: sourceRuntime.view.scopes.session,
243
+ provenance: sourceRuntime.artifactProvenance("session"),
267
244
  } });
268
- const ownedRevision = publication?.revision ?? publication?.commit;
269
- if (!publication || !ownedRevision)
245
+ if (!publication?.revision)
270
246
  throw new Error("State Flow fork requires existing shared scope storage");
271
- return { snapshot: { ...structuredClone(snapshot), meta: { ...structuredClone(snapshot.meta), durableBase: ownedRevision } }, publication };
247
+ return { snapshot: structuredClone(snapshot), publication };
272
248
  },
273
249
  };
274
250
  }
275
- restore(revision, legacySnapshot) {
276
- return this.restoreInspected(revision, inspectSnapshotRevision(this.cwd, this.sessionId, this.root, revision, legacySnapshot, this.sessionKey));
277
- }
278
- restoreInspected(revision, inspected) {
279
- if (inspected.file) {
280
- const savedRuntime = hashJson(createSessionRuntime(inspected.snapshot, this.cwd, this.sessionId, inspected.file.view.lineage, "files", inspected.file.provenance.session));
281
- this.view = inspected.file.view;
282
- this.base = inspected.file.base;
283
- this.backend = "files";
284
- this.provenanceByScope = structuredClone(inspected.file.provenance);
285
- this.semanticRevision = revision;
286
- this.savedRuntime = savedRuntime;
287
- this.absentSharedScopes.clear();
288
- return inspected.snapshot;
289
- }
290
- const loaded = inspected.temporal;
291
- const { resolved, view } = loaded;
292
- let base = captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
293
- const reference = loaded.runtime.document.meta.temporalRevision;
294
- let semanticRevision = reference === undefined || reference === "self" ? loaded.runtime.revision : reference;
295
- let publicationTarget = resolved.publicationTarget;
296
- resolved.snapshot.meta.durableBase = publicationTarget;
297
- const savedRuntime = hashJson(createSessionRuntime(resolved.snapshot, this.cwd, this.sessionId, view.lineage));
298
- resolved.snapshot.meta.pendingPublication = { commit: publicationTarget, error: "Durable publication intent is unconfirmed" };
299
- {
300
- try {
301
- if (isLocalGitRepository(this.root))
302
- delete resolved.snapshot.meta.pendingPublication;
303
- }
304
- catch {
305
- // Invalid remote/branch configuration retains unconfirmed publication for explicit retry.
306
- }
307
- }
308
- this.view = view;
309
- this.base = base;
310
- this.backend = "git";
311
- this.provenanceByScope = structuredClone(loaded.provenance);
312
- this.semanticRevision = semanticRevision;
313
- this.savedRuntime = savedRuntime;
314
- this.absentSharedScopes.clear();
315
- return resolved.snapshot;
316
- }
317
251
  initialize(snapshot, allowCreateCwd, expectedShared, newSessionOrigin = false) {
318
252
  return this.initializeOrigin(snapshot, { allowCreateCwd, expectedShared, newSessionOrigin });
319
253
  }
@@ -322,35 +256,23 @@ export class TemporalRuntime {
322
256
  const hasCwd = hasCwdMaterialization(this.cwd, this.root);
323
257
  if (!allowCreateCwd && !hasCwd)
324
258
  return undefined;
325
- const backend = copy?.backend ?? this.backend ?? (detectGitCapability() === "git" && lstatSync(join(this.root, ".git"), { throwIfNoEntry: false }) ? "git" : "files");
326
- if (!copy && backend === "git") {
327
- if (hasLegacyStateSources(this.cwd, this.sessionId, this.root, this.sessionKey)) {
328
- migrateLegacyStorageToGit(this.cwd, this.sessionId, this.root, this.sessionKey);
329
- }
330
- }
331
- else if (!copy) {
332
- if (allowCreateCwd)
333
- initializeFileStore(this.root);
334
- if (hasLegacyStateSources(this.cwd, this.sessionId, this.root, this.sessionKey)) {
335
- migrateLegacyStorageToFiles(this.cwd, this.sessionId, this.root, this.sessionKey);
336
- }
337
- }
338
- const base = backend === "git" ? captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey) : captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
259
+ if (!copy && allowCreateCwd)
260
+ initializeFileStore(this.root);
261
+ const base = captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
339
262
  const files = new Map(base.files.map((file) => [file.path, file.content]));
340
263
  if (copy) {
341
264
  const session = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
342
265
  const owned = [session.checkpoint, session.patches, session.meta, join(session.directory, "config.json"), join(session.directory, "runtime.json"), join(session.directory, "state.json")];
343
266
  const occupied = owned.some((path) => files.get(path) !== undefined);
344
- const historical = !occupied && backend === "git" && base.head ? loadTemporalRevision(this.cwd, this.sessionId, this.root, base.head, this.sessionKey) : undefined;
345
- if (occupied || historical?.runtime || historical?.scopes.session) {
267
+ if (occupied) {
346
268
  throw new Error("State Flow fork target already has session storage");
347
269
  }
348
270
  }
349
271
  if (SCOPES.some((scope) => {
350
272
  const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
351
- return files.get(join(paths.directory, "state.json")) !== undefined;
273
+ return lstatSync(join(paths.directory, "state.json"), { throwIfNoEntry: false }) !== undefined;
352
274
  }))
353
- throw new Error("Legacy State Flow storage changed during initialization; retry migration from a fresh basis");
275
+ throw new Error("Unsupported State Flow storage exists; preserve or convert it before initialization");
354
276
  const streams = Object.fromEntries(SCOPES.map((scope) => {
355
277
  const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
356
278
  return [scope, parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? this.cwd : undefined, files.get(paths.meta))];
@@ -360,19 +282,18 @@ export class TemporalRuntime {
360
282
  if (copy && !streams.global)
361
283
  throw new Error("State Flow fork requires existing shared scope storage");
362
284
  const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
363
- const existingRuntime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), this.cwd, this.sessionId, files.get(paths.meta));
285
+ const existingRuntime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), this.cwd, this.sessionId);
364
286
  if (existingRuntime && !newSessionOrigin)
365
- throw new Error("Existing session runtime requires a branch revision pointer");
287
+ throw new Error("Existing session runtime requires retained-boundary restoration");
366
288
  // Explicit start before any branch runtime is a new origin, never inheritance of a later session layer.
367
289
  if (newSessionOrigin)
368
290
  streams.session = undefined;
369
291
  if (copy)
370
292
  streams.session = copy.stream;
371
- const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID());
372
- const candidate = new TemporalRuntime(this.cwd, this.session, this.root);
373
- candidate.backend = backend;
293
+ const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID(), this.historyLimit);
294
+ const candidate = new TemporalRuntime(this.cwd, this.session, this.root, undefined, this.historyLimit);
374
295
  candidate.base = base;
375
- candidate.view = adoptTemporalStreams({ global: streams.global ?? fresh.scopes.global, cwd: streams.cwd ?? fresh.scopes.cwd, session: streams.session ?? fresh.scopes.session }, `${base.head ?? "unborn"}:${randomUUID()}`);
296
+ candidate.view = adoptTemporalStreams({ global: streams.global ?? fresh.scopes.global, cwd: streams.cwd ?? fresh.scopes.cwd, session: streams.session ?? fresh.scopes.session }, `files:${randomUUID()}`, this.historyLimit);
376
297
  const globalMeta = temporalScopePaths(this.cwd, this.sessionId, "global", this.root, this.sessionKey).meta;
377
298
  const cwdMeta = temporalScopePaths(this.cwd, this.sessionId, "cwd", this.root, this.sessionKey).meta;
378
299
  candidate.provenanceByScope = {
@@ -381,27 +302,23 @@ export class TemporalRuntime {
381
302
  session: copy ? structuredClone(copy.provenance) : streams.session === undefined ? {} : parseScopeProvenance(files.get(paths.meta), paths.meta),
382
303
  };
383
304
  if (expectedShared && ["global", "cwd"].some((scope) => !sameJson(candidate.read(0, scope), expectedShared[scope]))) {
384
- throw new Error("Legacy branch shared scopes diverged from the selected revision; migration cannot overwrite them");
305
+ throw new Error("Selected branch shared scopes diverged from the live revision");
385
306
  }
386
307
  const publication = copy ? candidate.publishForkOrigin(snapshot) : candidate.publish(snapshot, true);
387
308
  this.view = candidate.view;
388
309
  this.base = candidate.base;
389
- this.backend = backend;
390
310
  this.provenanceByScope = structuredClone(candidate.provenanceByScope);
391
311
  this.semanticRevision = candidate.semanticRevision;
392
312
  this.savedRuntime = candidate.savedRuntime;
393
313
  this.absentSharedScopes.clear();
394
314
  return publication;
395
315
  }
396
- /** Initial copy owns only the new session files, without pruning or rewriting shared provenance. */
316
+ /** Copy the private origin and apply configured retention folding, preserving live shared values/provenance. */
397
317
  publishForkOrigin(snapshot) {
398
- const runtime = createSessionRuntime(snapshot, this.cwd, this.sessionId, this.view.lineage, this.backend === "files" ? "files" : "unconfirmed", this.provenanceByScope.session);
399
- const result = this.backend === "files"
400
- ? publishTemporalStateToFiles(this.cwd, this.sessionId, this.view, ["session"], this.base, this.root, runtime, this.sessionKey, this.provenanceByScope)
401
- : publishTemporalStateToGit(this.cwd, this.sessionId, this.view, ["session"], this.base, this.root, runtime, this.sessionKey, (snapshot.meta.remotePublication?.mode ?? "transition") === "transition", this.provenanceByScope);
402
- const publication = result;
318
+ const runtime = createSessionRuntime(snapshot, this.cwd, this.sessionId, this.view.lineage, this.provenanceByScope.session);
319
+ const publication = publishTemporalStateToFiles(this.cwd, this.sessionId, this.view, SCOPES, this.base, this.root, runtime, this.sessionKey, this.provenanceByScope);
403
320
  this.base = publication.base;
404
- this.semanticRevision = publication.revision ?? publication.commit;
321
+ this.semanticRevision = publication.revision;
405
322
  this.savedRuntime = hashJson(runtime);
406
323
  return publication;
407
324
  }
@@ -414,10 +331,9 @@ export class TemporalRuntime {
414
331
  * session or runtime files also fails closed under the existing race rule.
415
332
  */
416
333
  reconcileSharedDrift(changedScopes) {
417
- const captured = this.backend === "files"
418
- ? captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey)
419
- : captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
334
+ const captured = captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
420
335
  const liveFiles = new Map(captured.files.map((file) => [file.path, file]));
336
+ const priorFiles = new Map(this.base.files.map((file) => [file.path, file]));
421
337
  const adoptable = new Set();
422
338
  for (const scope of SHARED_SCOPES) {
423
339
  const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
@@ -438,10 +354,12 @@ export class TemporalRuntime {
438
354
  const targets = [];
439
355
  const removedTargets = [];
440
356
  const absentScopes = [];
441
- const head = "head" in captured ? captured.head : undefined;
442
- const reconciliation = `${head ?? "files"}:reconcile:${randomUUID()}`;
357
+ const reconciliation = `files:reconcile:${randomUUID()}`;
443
358
  for (const scope of SHARED_SCOPES) {
444
359
  const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
360
+ // A cached reader may fold wider live tails without owning a semantic write.
361
+ if ([paths.checkpoint, paths.patches, paths.meta].every((path) => liveFiles.get(path)?.identity === priorFiles.get(path)?.identity))
362
+ continue;
445
363
  const presence = classifyScopeStream(liveFiles.get(paths.checkpoint)?.content, liveFiles.get(paths.patches)?.content, scope, scope === "cwd" ? this.cwd : undefined, liveFiles.get(paths.meta)?.content);
446
364
  if (presence.kind === "absent") {
447
365
  absentScopes.push(scope);
@@ -449,7 +367,7 @@ export class TemporalRuntime {
449
367
  if (changedScopes.has(scope) && !this.absentSharedScopes.has(scope))
450
368
  removedTargets.push(scope);
451
369
  if (!this.absentSharedScopes.has(scope)) {
452
- adopted.set(scope, freshEmptyScopeStream(scope, `${reconciliation}:${scope}:absent`));
370
+ adopted.set(scope, freshEmptyScopeStream(scope, `${reconciliation}:${scope}:absent`, this.historyLimit));
453
371
  }
454
372
  continue;
455
373
  }
@@ -471,7 +389,7 @@ export class TemporalRuntime {
471
389
  global: adopted.get("global") ?? structuredClone(this.view.scopes.global),
472
390
  cwd: adopted.get("cwd") ?? structuredClone(this.view.scopes.cwd),
473
391
  session: structuredClone(this.view.scopes.session),
474
- }, reconciliation);
392
+ }, reconciliation, this.historyLimit);
475
393
  if (removedTargets.length > 0) {
476
394
  this.view = reconciledView();
477
395
  this.base = captured;
@@ -484,18 +402,28 @@ export class TemporalRuntime {
484
402
  throw targetScopeConflict(targets);
485
403
  return { view: reconciledView(), base: captured, provenance };
486
404
  }
405
+ /** Canonically accept a prepared retained-boundary origin before lifecycle-only persistence. */
406
+ acceptRestoredOrigin(snapshot) {
407
+ if (!this.restoredOriginPending)
408
+ throw new Error("State Flow has no prepared restored origin to accept");
409
+ const publication = this.publish(snapshot, true);
410
+ if (!publication)
411
+ throw new Error("State Flow restored origin produced no canonical publication");
412
+ this.restoredOriginPending = false;
413
+ return publication;
414
+ }
487
415
  publish(snapshot, semantic = false, accepted, options = {}) {
488
416
  if (!this.view || !this.base)
489
417
  throw new Error("State Flow temporal publication is unavailable; restore or initialize before accepting a transition");
490
- // Activation and terminal policy: only the legacy transition mode publishes synchronously.
491
- const pushRemote = options.pushRemote ?? (snapshot.meta.remotePublication?.mode ?? "transition") === "transition";
492
- const provenanceScopes = SCOPES.filter((scope) => Object.entries(options.provenance?.[scope] ?? {})
493
- .some(([path, entry]) => this.provenanceByScope[scope][path] === undefined
494
- || !sameJson(this.provenanceByScope[scope][path], entry)));
418
+ if (this.restoredOriginPending && !semantic)
419
+ throw new Error("State Flow restored origin must be accepted before runtime-only persistence");
420
+ // Explicit evidence updates still own a scope CAS, even if they equal the stale cache.
421
+ const provenanceScopes = SCOPES.filter((scope) => Object.keys(options.provenance?.[scope] ?? {}).length > 0);
422
+ const runtimeOnly = !semantic && accepted === undefined && provenanceScopes.length === 0;
495
423
  let basis = this.view;
496
424
  let base = this.base;
497
425
  let basisProvenance = this.provenanceByScope;
498
- if ((semantic || provenanceScopes.length > 0) && this.semanticRevision) {
426
+ if (this.semanticRevision) {
499
427
  const changedScopes = new Set([
500
428
  ...(accepted?.transitions ?? []).map(({ scope }) => scope),
501
429
  ...provenanceScopes,
@@ -505,7 +433,7 @@ export class TemporalRuntime {
505
433
  base = reconciled.base;
506
434
  basisProvenance = reconciled.provenance;
507
435
  }
508
- const next = accepted ? advanceTemporalState(basis, accepted.transitions, accepted.id) : basis;
436
+ const next = accepted ? advanceTemporalState(basis, accepted.transitions, accepted.id, this.historyLimit) : basis;
509
437
  const nextProvenance = structuredClone(basisProvenance);
510
438
  for (const scope of SCOPES) {
511
439
  const updates = options.provenance?.[scope];
@@ -513,45 +441,21 @@ export class TemporalRuntime {
513
441
  for (const [path, entry] of Object.entries(updates))
514
442
  nextProvenance[scope][path] = structuredClone(entry);
515
443
  }
516
- for (const scope of SCOPES) {
517
- nextProvenance[scope] = pruneArtifactProvenance(nextProvenance[scope], readTemporalState(next, 0, scope).artifacts);
518
- }
444
+ if (!runtimeOnly)
445
+ for (const scope of SCOPES) {
446
+ nextProvenance[scope] = pruneArtifactProvenance(nextProvenance[scope], readTemporalState(next, 0, scope, this.historyLimit).artifacts);
447
+ }
519
448
  const provenanceChanged = !sameJson(nextProvenance, this.provenanceByScope);
520
- const scopedWrite = semantic || provenanceChanged;
521
- const runtime = createSessionRuntime(snapshot, this.cwd, this.sessionId, next.lineage, this.backend === "files" ? "files" : "unconfirmed", nextProvenance.session);
449
+ const runtime = createSessionRuntime(snapshot, this.cwd, this.sessionId, next.lineage, nextProvenance.session);
522
450
  const fingerprint = hashJson(runtime);
523
451
  if (!semantic && fingerprint === this.savedRuntime && !provenanceChanged)
524
452
  return undefined;
525
- if (this.backend === "files") {
526
- runtime.meta.temporalRevision = "self";
527
- const result = publishTemporalStateToFiles(this.cwd, this.sessionId, next, semantic ? SCOPES : [], base, this.root, runtime, this.sessionKey, nextProvenance);
528
- this.base = result.base;
529
- this.view = next;
530
- this.provenanceByScope = nextProvenance;
531
- this.savedRuntime = fingerprint;
532
- this.semanticRevision = result.revision;
533
- if (semantic)
534
- this.absentSharedScopes.clear();
535
- return { base: result.base, revision: result.revision };
536
- }
537
- if (!scopedWrite) {
538
- const current = captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
539
- const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
540
- for (const path of [paths.config, paths.runtime]) {
541
- if (current.files.find((file) => file.path === path)?.identity !== base.files.find((file) => file.path === path)?.identity) {
542
- throw new Error("Temporal State Flow runtime changed concurrently");
543
- }
544
- }
545
- base = current;
546
- }
547
- runtime.meta.temporalRevision = scopedWrite ? "self" : this.semanticRevision;
548
- const result = publishTemporalStateToGit(this.cwd, this.sessionId, next, semantic ? SCOPES : provenanceChanged ? provenanceScopes : [], base, this.root, runtime, this.sessionKey, pushRemote, nextProvenance);
453
+ const result = publishTemporalStateToFiles(this.cwd, this.sessionId, next, runtimeOnly ? [] : SCOPES, base, this.root, runtime, this.sessionKey, runtimeOnly ? undefined : nextProvenance, runtimeOnly);
549
454
  this.base = result.base;
550
455
  this.view = next;
551
456
  this.provenanceByScope = nextProvenance;
552
457
  this.savedRuntime = fingerprint;
553
- if (scopedWrite && result.commit)
554
- this.semanticRevision = result.commit;
458
+ this.semanticRevision = result.revision;
555
459
  if (semantic)
556
460
  this.absentSharedScopes.clear();
557
461
  return result;