@llblab/pi-kit 0.37.1 → 0.38.1

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