@llblab/pi-kit 0.37.1 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +30 -14
  7. package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
  8. package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
  9. package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
  60. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
  61. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
  62. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
  63. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
  64. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
  65. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
  66. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
  67. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
  68. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
  69. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
  70. package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
  71. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
  72. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
  73. package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
  74. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
  75. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
  76. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
  77. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
  78. package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
  79. package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
  80. package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
  81. package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
  82. package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
  83. package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
  84. package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
  85. package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
  86. package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
  87. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
  88. package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
  89. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
  90. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
  91. package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
  92. package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
  93. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
  94. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
  95. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
  96. package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
  97. package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
  98. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
  99. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
  100. package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
  101. package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
  102. package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
  103. package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
  104. package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
  105. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
  106. package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
  107. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
  108. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
  109. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
  110. package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
  111. package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
  112. package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
  113. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
  114. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
  115. package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
  116. package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
  117. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
  118. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
  119. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
  120. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
  121. package/node_modules/@llblab/pi-state-flow/package.json +6 -1
  122. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
  123. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
  124. package/package.json +2 -2
@@ -2,7 +2,8 @@ import { resolve } from "node:path";
2
2
  import { parseArtifactProvenanceRegistry } from "./artifact.js";
3
3
  import { MAX_HISTORY_LIMIT } from "./history.js";
4
4
  import { canonicalJson, isJsonValue, isObject } from "./json.js";
5
- import { validateTemporalLineage } from "./temporal.js";
5
+ import { captureTemporalWitnesses, validateTemporalLineage } from "./temporal.js";
6
+ import { captureScopeSelection } from "./selection.js";
6
7
  const MAX_RESTORED_STEP = Number.MAX_SAFE_INTEGER - 1;
7
8
  const MAX_LEGACY_VALIDATION_ATTEMPT = 7;
8
9
  /** Missing operational capability is not evidence that a checkpoint target is invalid. */
@@ -23,26 +24,70 @@ export function readCheckpointMode(value, inactiveMode = "passive") {
23
24
  return Object.hasOwn(value, "enabled") || !isStateFlowMode(value.mode) ? undefined : value.mode;
24
25
  return typeof value.enabled === "boolean" ? value.enabled ? "active" : inactiveMode : undefined;
25
26
  }
27
+ /** Capture closed caller-owned run fields, omitting only undefined optional in-memory fields. */
28
+ export function captureLifecycleSnapshot(value) {
29
+ if (!isObject(value) || Object.keys(value).sort().join(",") !== "config,meta" || !isObject(value.config)
30
+ || Object.keys(value.config).join(",") !== "mode" || !isObject(value.meta)
31
+ || Object.keys(value.meta).some((key) => !["step", "specification", "bootstrap", "validation"].includes(key))) {
32
+ throw new Error("Selected State Flow lifecycle update contains non-owned fields");
33
+ }
34
+ const meta = Object.fromEntries(Object.entries(value.meta).filter(([, field]) => field !== undefined));
35
+ const supplied = { config: value.config, meta };
36
+ if (!isJsonValue(supplied))
37
+ throw new Error("Invalid State Flow lifecycle snapshot");
38
+ const normalized = { config: { mode: isStateFlowMode(value.config.mode) ? value.config.mode : "off" }, meta: restoredMeta(meta) };
39
+ if (meta.bootstrap === false)
40
+ normalized.meta.bootstrap = false;
41
+ if (meta.step === Number.MAX_SAFE_INTEGER)
42
+ normalized.meta.step = Number.MAX_SAFE_INTEGER;
43
+ if (canonicalJson(normalized) !== canonicalJson(supplied))
44
+ throw new Error("Invalid State Flow lifecycle snapshot");
45
+ return structuredClone(normalized);
46
+ }
26
47
  /** Failed Active selection retains its policy and fences inference until explicit recovery or an inactive choice. */
27
48
  export function isActiveRestorationBlocked(snapshot) {
28
49
  return snapshot.config.mode === "active" && snapshot.meta.validation?.attempt === 0;
29
50
  }
51
+ /** Retained private identity proof, never private values or acquisition permission. */
52
+ export function capturePrivateCausalBasis(value) {
53
+ if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "causalWitnesses,lineage")
54
+ throw new Error("Invalid State Flow private causal basis");
55
+ const lineage = value.lineage;
56
+ validateTemporalLineage(lineage, MAX_HISTORY_LIMIT);
57
+ const causalWitnesses = captureTemporalWitnesses(lineage, value.causalWitnesses, MAX_HISTORY_LIMIT);
58
+ const witnessed = new Set(causalWitnesses.map(witness => witness.transition.id));
59
+ if (lineage.slice(1).some(boundary => !witnessed.has(boundary.id)))
60
+ throw new Error("Insufficient State Flow private causal basis witnesses");
61
+ return structuredClone({ lineage, causalWitnesses });
62
+ }
30
63
  export function validateSessionRuntime(value, cwd, sessionId) {
64
+ validateRuntimeVersion(value, cwd, sessionId, 1);
65
+ }
66
+ /** Shared syntax checks do not widen native admission or prove private-stream acquisition. */
67
+ function validateRuntimeVersion(value, cwd, sessionId, expectedVersion) {
31
68
  if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
32
69
  || !isObject(value.config) || !isObject(value.meta))
33
70
  throw new Error("Invalid State Flow session runtime envelope");
34
71
  const { version, identity, lineage, artifacts, temporal: _temporalScope, ...fields } = value.meta;
72
+ if (!Object.hasOwn(value.meta, "version") || version !== expectedVersion)
73
+ throw new Error("Unsupported State Flow runtime provenance format");
35
74
  if (artifacts !== undefined)
36
75
  parseArtifactProvenanceRegistry(artifacts, "State Flow session artifact provenance");
37
- if (version !== 1)
38
- throw new Error("Unsupported State Flow runtime provenance format");
76
+ if (expectedVersion === 2 && (!Object.hasOwn(value.meta, "privateAcquisition") || !["unacquired", "deferred", "acquired"].includes(value.meta.privateAcquisition))) {
77
+ throw new Error("Invalid State Flow private acquisition provenance");
78
+ }
79
+ if (Object.hasOwn(value.meta, "privateCausalBasis")) {
80
+ if (expectedVersion !== 2 || value.meta.privateAcquisition !== "acquired")
81
+ throw new Error("Invalid State Flow private causal basis provenance");
82
+ capturePrivateCausalBasis(value.meta.privateCausalBasis);
83
+ }
39
84
  if (!isObject(identity) || Object.keys(identity).sort().join(",") !== "cwd,sessionId"
40
85
  || identity.cwd !== resolve(cwd) || identity.sessionId !== sessionId || sessionId.trim().length === 0 || sessionId !== sessionId.trim()) {
41
86
  throw new Error("State Flow runtime scope identity mismatch");
42
87
  }
43
88
  validateTemporalLineage(lineage, MAX_HISTORY_LIMIT);
44
89
  const known = new Set(["step", "specification", "validation", "bootstrap"]);
45
- if (Object.keys(fields).some((key) => ["state", "contract", "working", "response"].includes(key))) {
90
+ if (Object.keys(fields).some((key) => ["state", "strategy", "tactics", "contract", "working", "response"].includes(key))) {
46
91
  throw new Error("Semantic state does not belong in State Flow runtime metadata");
47
92
  }
48
93
  const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
@@ -54,7 +99,12 @@ export function validateSessionRuntime(value, cwd, sessionId) {
54
99
  normalized.meta.bootstrap = false;
55
100
  if (runtimeFields.step === Number.MAX_SAFE_INTEGER)
56
101
  normalized.meta.step = Number.MAX_SAFE_INTEGER;
57
- if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: value.config, meta: runtimeFields })) {
102
+ if (expectedVersion === 2 && !Object.hasOwn(value.config, "scopes"))
103
+ throw new Error("State Flow lifecycle v2 requires explicit scopes");
104
+ const scopes = expectedVersion === 2 ? captureScopeSelection(value.config.scopes) : undefined;
105
+ const normalizedConfig = scopes === undefined ? normalized.config : { ...normalized.config, scopes };
106
+ const suppliedConfig = scopes === undefined ? value.config : { ...value.config, scopes };
107
+ if (canonicalJson({ config: normalizedConfig, meta: normalized.meta }) !== canonicalJson({ config: suppliedConfig, meta: runtimeFields })) {
58
108
  throw new Error("Invalid State Flow runtime configuration or counters");
59
109
  }
60
110
  }
@@ -86,10 +136,7 @@ export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
86
136
  let runtime;
87
137
  try {
88
138
  const settings = JSON.parse(config);
89
- const mode = isObject(settings) && Object.keys(settings).length === 1 ? readCheckpointMode(settings, "passive") : undefined;
90
- if (mode === undefined)
91
- throw new Error("Invalid State Flow runtime configuration or counters");
92
- runtime = { config: { mode }, meta: JSON.parse(runtimeSource) };
139
+ runtime = { config: legacySessionConfig(settings), meta: JSON.parse(runtimeSource) };
93
140
  }
94
141
  catch (error) {
95
142
  if (error instanceof SyntaxError)
@@ -100,6 +147,48 @@ export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
100
147
  const { temporal: _legacyTemporal, ...runtimeMeta } = runtime.meta;
101
148
  return { config: { mode: runtime.config.mode }, meta: runtimeMeta };
102
149
  }
150
+ function legacySessionConfig(settings) {
151
+ const mode = isObject(settings) && Object.keys(settings).length === 1 ? readCheckpointMode(settings, "passive") : undefined;
152
+ if (mode === undefined)
153
+ throw new Error("Invalid State Flow runtime configuration or counters");
154
+ return { mode };
155
+ }
156
+ /** Detached lifecycle preflight only; current native readers/writers still admit version 1 exclusively. */
157
+ export function prepareSessionPolicyMigration(config, runtimeSource, cwd, sessionId, legacySelection) {
158
+ if (config === undefined && runtimeSource === undefined)
159
+ return undefined;
160
+ if (config === undefined || runtimeSource === undefined)
161
+ throw new Error("Incomplete State Flow config/runtime pair");
162
+ const metadata = parsePolicyDocument(runtimeSource);
163
+ if (!isObject(metadata) || !Object.hasOwn(metadata, "version") || (metadata.version !== 1 && metadata.version !== 2))
164
+ throw new Error("Unsupported State Flow runtime provenance format");
165
+ const sourceVersion = metadata.version;
166
+ const settings = parsePolicyDocument(config);
167
+ if (sourceVersion === 1 && (Object.hasOwn(metadata, "privateAcquisition") || Object.hasOwn(metadata, "causalWitnesses") || Object.hasOwn(metadata, "privateCausalBasis") || (isObject(settings) && Object.hasOwn(settings, "scopes")))) {
168
+ throw new Error("Target State Flow policy fields in legacy lifecycle format");
169
+ }
170
+ const sourceValue = { config: sourceVersion === 1 ? legacySessionConfig(settings) : settings, meta: metadata };
171
+ validateRuntimeVersion(sourceValue, cwd, sessionId, sourceVersion);
172
+ const scopes = captureScopeSelection(sourceVersion === 1 ? legacySelection : settings.scopes);
173
+ const runtime = {
174
+ config: { ...sourceValue.config, scopes },
175
+ meta: { ...metadata, version: 2, ...(sourceVersion === 1 ? { privateAcquisition: "acquired" } : {}) },
176
+ };
177
+ validateRuntimeVersion(runtime, cwd, sessionId, 2);
178
+ return {
179
+ sourceVersion, targetVersion: 2, requiresConversion: sourceVersion === 1,
180
+ source: { config, runtime: runtimeSource }, runtime,
181
+ target: { config: `${canonicalJson(runtime.config)}\n`, runtime: `${canonicalJson(runtime.meta)}\n` },
182
+ };
183
+ }
184
+ function parsePolicyDocument(source) {
185
+ try {
186
+ return JSON.parse(source);
187
+ }
188
+ catch {
189
+ throw new Error("State Flow session policy contains invalid JSON");
190
+ }
191
+ }
103
192
  function restoredStep(value) {
104
193
  return typeof value === "number"
105
194
  && Number.isSafeInteger(value)
@@ -187,6 +276,37 @@ export function parseRetainedPiCheckpoint(value, inactiveMode = "passive") {
187
276
  ...(typeof value.specification === "string" ? { specification: value.specification } : {}),
188
277
  };
189
278
  }
279
+ /** Native policy value preflight; no retained boundary, fork or acquisition authority is created. */
280
+ export function prepareRetainedPolicyMigration(value, legacySelection, inactiveMode = "passive") {
281
+ if (!isObject(value))
282
+ throw new Error("Invalid State Flow policy checkpoint");
283
+ const legacy = !Object.hasOwn(value, "version");
284
+ let declaredVersion;
285
+ try {
286
+ if (!legacy)
287
+ declaredVersion = value.version;
288
+ }
289
+ catch {
290
+ throw new Error("Invalid State Flow policy checkpoint");
291
+ }
292
+ if (!legacy && declaredVersion !== 2)
293
+ throw new Error("Unsupported State Flow native policy version");
294
+ const sourceVersion = legacy ? "legacy" : 2;
295
+ if (sourceVersion === "legacy" && Object.hasOwn(value, "scopes"))
296
+ throw new Error("Target State Flow policy fields in legacy native checkpoint");
297
+ if (!isJsonValue(value))
298
+ throw new Error("Invalid State Flow policy checkpoint");
299
+ const { version: _version, scopes, ...lifecycle } = value;
300
+ if (sourceVersion === 2 && (!Object.hasOwn(value, "scopes") || !isStateFlowMode(lifecycle.mode)
301
+ || Object.hasOwn(lifecycle, "enabled") || Object.hasOwn(lifecycle, "disabled"))) {
302
+ throw new Error("Invalid State Flow lifecycle v2 policy");
303
+ }
304
+ const checkpoint = parseRetainedPiCheckpoint(lifecycle, inactiveMode);
305
+ return {
306
+ sourceVersion, targetVersion: 2, requiresConversion: sourceVersion === "legacy",
307
+ checkpoint: { ...checkpoint, version: 2, scopes: captureScopeSelection(sourceVersion === "legacy" ? legacySelection : scopes) },
308
+ };
309
+ }
190
310
  export function migrationFailure(data, error, mode = "passive") {
191
311
  const meta = restoredMeta(data.meta, data);
192
312
  meta.validation = {
@@ -1,10 +1,11 @@
1
1
  import { type ArtifactCompilationUpdate, type ArtifactModelHints, type ArtifactRegistry } from "./artifact.ts";
2
2
  import { type JsonObject } from "./json.ts";
3
+ import { type RetainedModules } from "./semantic-module.ts";
3
4
  /** Runtime defaults for documented semantic planes; stored objects may omit them or retain other fields. */
4
5
  export type MaterializedState = JsonObject & {
5
6
  intents: JsonObject;
6
- contract: JsonObject;
7
- working: JsonObject;
7
+ strategy: JsonObject;
8
+ tactics: JsonObject;
8
9
  artifacts: ArtifactRegistry;
9
10
  response: string;
10
11
  lazy: JsonObject;
@@ -12,20 +13,24 @@ export type MaterializedState = JsonObject & {
12
13
  /** Sparse semantic state: documented planes may be absent. Disk codecs select only known fields. */
13
14
  export type SemanticState = JsonObject & Partial<{
14
15
  intents: JsonObject;
15
- contract: JsonObject;
16
- working: JsonObject;
16
+ strategy: JsonObject;
17
+ tactics: JsonObject;
17
18
  artifacts: ArtifactRegistry;
18
19
  response: string;
19
20
  lazy: JsonObject;
20
21
  }>;
21
22
  export type ScopedSemanticStates = Record<StateScope, SemanticState>;
23
+ /** Present owners only; a selection-aware caller must validate the exact acquired key set. */
24
+ export type ScopeStateValues = Partial<Record<StateScope, SemanticState>>;
25
+ /** Value admission only: owners must already be a captured policy, never inferred from missing data. */
26
+ export declare function assertScopeStateValues(states: ScopeStateValues, owners: readonly StateScope[]): void;
22
27
  /** Compatibility name for callers that still treat materialized state as a document. */
23
28
  export type StateDocument = MaterializedState;
24
29
  /** Model patch shape; artifact entries may be compiler outputs before trusted metadata is attached. */
25
30
  export interface StatePatch extends JsonObject {
26
31
  artifacts: JsonObject;
27
- contract: JsonObject;
28
- working: JsonObject;
32
+ strategy: JsonObject;
33
+ tactics: JsonObject;
29
34
  intents: JsonObject;
30
35
  response: string;
31
36
  lazy: JsonObject;
@@ -33,9 +38,10 @@ export interface StatePatch extends JsonObject {
33
38
  export type StateScope = "global" | "cwd" | "session";
34
39
  /** A model-authored patch for one scope. Response is captured by the runtime in session state. */
35
40
  export interface ScopePatch {
41
+ [module: `x_${string}`]: JsonObject | undefined;
36
42
  artifacts?: JsonObject;
37
- contract?: JsonObject;
38
- working?: JsonObject;
43
+ strategy?: JsonObject;
44
+ tactics?: JsonObject;
39
45
  intents?: JsonObject;
40
46
  lazy?: JsonObject;
41
47
  }
@@ -60,6 +66,8 @@ export interface ScopedStates {
60
66
  cwd: MaterializedState;
61
67
  session: MaterializedState;
62
68
  }
69
+ /** Temporary root-plane reservation; nested domain data is not an alternate semantic namespace. */
70
+ export declare function assertNoRetiredSemanticPlanes(value: JsonObject): void;
63
71
  export declare function emptyState(): MaterializedState;
64
72
  export declare function isMaterializedState(value: unknown): value is MaterializedState;
65
73
  /** Missing planes are valid storage, not missing authority. Present values retain their type checks. */
@@ -72,18 +80,22 @@ export declare function overlayStates(...scopes: readonly JsonObject[]): Materia
72
80
  /** Default-bearing SDK compatibility view; model transport uses sparse SemanticState instead. */
73
81
  export type ModelState = JsonObject & {
74
82
  intents: JsonObject;
75
- contract: JsonObject;
76
- working: JsonObject;
83
+ strategy: JsonObject;
84
+ tactics: JsonObject;
77
85
  artifacts: ArtifactRegistry;
78
86
  response: string;
79
87
  };
80
88
  /** Select only owned top-level fields, preserving nested data and replay deletion markers. */
81
- export declare function selectSemanticFields(value: JsonObject): JsonObject;
89
+ export declare function selectSemanticFields(value: JsonObject, modules?: RetainedModules): JsonObject;
90
+ /** v4 admits only declared object planes; null is a replay deletion, never stored content. */
91
+ export declare function validateExternalPlanes(value: JsonObject, modules: RetainedModules, patch?: boolean): void;
92
+ /** Normalize only core planes: opaque external bodies are not empty-branch policy subjects. */
93
+ export declare function pruneSemanticState(value: JsonObject): JsonObject;
82
94
  /** Read only documented, present planes; empty responses carry no semantic value. */
83
- export declare function projectSemanticState(state: JsonObject): SemanticState;
95
+ export declare function projectSemanticState(state: JsonObject, modules?: RetainedModules): SemanticState;
84
96
  /** Preserve deletion meaning in visible history without exposing ignored fields or empty responses. */
85
- export declare function projectSemanticPatch(patch: JsonObject): JsonObject;
97
+ export declare function projectSemanticPatch(patch: JsonObject, modules?: RetainedModules): JsonObject;
86
98
  /** Explicit hot reads filter hidden bodies/bookkeeping without normalizing legacy semantic data. */
87
- export declare function projectStateForRead(state: SemanticState, artifactHints?: ArtifactModelHints): SemanticState;
99
+ export declare function projectStateForRead(state: SemanticState, artifactHints?: ArtifactModelHints, modules?: RetainedModules): SemanticState;
88
100
  /** Ordinary model context omits empty object fields; exact reads and replay remain observational. */
89
101
  export declare function projectStateForModel(state: SemanticState, artifactHints?: ArtifactModelHints): SemanticState;
@@ -1,16 +1,32 @@
1
- import { isArtifactRegistry, projectArtifactsForModel, updateArtifactRegistry, } from "./artifact.js";
2
- import { applyPatch, isObject, pruneEmptyObjects } from "./json.js";
1
+ import { isArtifactRegistry, updateArtifactRegistry, } from "./artifact.js";
2
+ import { applyPatch, isJsonValue, isObject, pruneEmptyObjects } from "./json.js";
3
+ import { projectPlaneContext, SEMANTIC_PLANES } from "./planes.js";
4
+ import { validateModuleObject, validateRetainedModules } from "./semantic-module.js";
5
+ /** Value admission only: owners must already be a captured policy, never inferred from missing data. */
6
+ export function assertScopeStateValues(states, owners) {
7
+ if (!isObject(states) || Object.keys(states).length !== owners.length
8
+ || Object.keys(states).some((scope) => !owners.includes(scope))
9
+ || owners.some((scope) => !Object.hasOwn(states, scope)))
10
+ throw new Error("Selected State Flow values must match their owners exactly");
11
+ for (const scope of owners)
12
+ if (!isJsonValue(states[scope]) || !isSemanticState(states[scope]))
13
+ throw new Error(`Invalid selected State Flow ${scope} value`);
14
+ }
15
+ /** Temporary root-plane reservation; nested domain data is not an alternate semantic namespace. */
16
+ export function assertNoRetiredSemanticPlanes(value) {
17
+ for (const plane of ["contract", "working"]) {
18
+ if (Object.hasOwn(value, plane))
19
+ throw new Error(`Retired State Flow semantic plane: ${plane}`);
20
+ }
21
+ }
3
22
  export function emptyState() {
4
- return { intents: {}, contract: {}, working: {}, artifacts: {}, response: "", lazy: {} };
23
+ return { intents: {}, strategy: {}, tactics: {}, artifacts: {}, response: "", lazy: {} };
5
24
  }
6
25
  export function isMaterializedState(value) {
7
26
  return isObject(value)
27
+ && !Object.hasOwn(value, "contract") && !Object.hasOwn(value, "working")
8
28
  && isArtifactRegistry(value.artifacts)
9
- && isObject(value.contract)
10
- && isObject(value.working)
11
- && isObject(value.intents)
12
- && typeof value.response === "string"
13
- && isObject(value.lazy);
29
+ && SEMANTIC_PLANES.every(({ module }) => module.validate(value[module.id]));
14
30
  }
15
31
  /** Missing planes are valid storage, not missing authority. Present values retain their type checks. */
16
32
  export function isSemanticState(value) {
@@ -27,36 +43,62 @@ export function updateMaterializedArtifacts(state, updates, removed = []) {
27
43
  /** Overlay lower-to-higher scopes without mutating any scope document. */
28
44
  export function overlayStates(...scopes) {
29
45
  return scopes.reduce((effective, scope) => {
46
+ assertNoRetiredSemanticPlanes(scope);
30
47
  return applyPatch(effective, scope);
31
48
  }, emptyState());
32
49
  }
33
50
  /** Select only owned top-level fields, preserving nested data and replay deletion markers. */
34
- export function selectSemanticFields(value) {
35
- return structuredClone(Object.fromEntries(Object.keys(emptyState())
51
+ export function selectSemanticFields(value, modules) {
52
+ assertNoRetiredSemanticPlanes(value);
53
+ if (modules !== undefined)
54
+ validateExternalPlanes(value, modules, true);
55
+ return structuredClone(Object.fromEntries([...Object.keys(emptyState()), ...Object.keys(modules ?? {})]
36
56
  .filter((key) => Object.hasOwn(value, key))
37
57
  .map((key) => [key, value[key]])));
38
58
  }
59
+ /** v4 admits only declared object planes; null is a replay deletion, never stored content. */
60
+ export function validateExternalPlanes(value, modules, patch = false) {
61
+ validateRetainedModules(modules);
62
+ const core = emptyState();
63
+ for (const [key, field] of Object.entries(value)) {
64
+ if (Object.hasOwn(core, key))
65
+ continue;
66
+ if (!Object.hasOwn(modules, key) || !(patch && field === null || isObject(field))) {
67
+ throw new Error(`Undeclared or invalid State Flow external plane: ${key}`);
68
+ }
69
+ if (field !== null)
70
+ validateModuleObject(field);
71
+ }
72
+ }
73
+ /** Normalize only core planes: opaque external bodies are not empty-branch policy subjects. */
74
+ export function pruneSemanticState(value) {
75
+ return Object.fromEntries(Object.entries(value).flatMap(([key, field]) => {
76
+ if (key.startsWith("x_"))
77
+ return [[key, field]];
78
+ const pruned = pruneEmptyObjects({ [key]: field });
79
+ return Object.entries(pruned);
80
+ }));
81
+ }
39
82
  /** Read only documented, present planes; empty responses carry no semantic value. */
40
- export function projectSemanticState(state) {
41
- const projected = selectSemanticFields(state);
83
+ export function projectSemanticState(state, modules) {
84
+ const projected = selectSemanticFields(state, modules);
42
85
  if (projected.response === "")
43
86
  delete projected.response;
44
87
  return projected;
45
88
  }
46
89
  /** Preserve deletion meaning in visible history without exposing ignored fields or empty responses. */
47
- export function projectSemanticPatch(patch) {
48
- const projected = selectSemanticFields(patch);
90
+ export function projectSemanticPatch(patch, modules) {
91
+ const projected = selectSemanticFields(patch, modules);
49
92
  if (projected.response === "")
50
93
  projected.response = null;
51
94
  return projected;
52
95
  }
53
96
  /** Explicit hot reads filter hidden bodies/bookkeeping without normalizing legacy semantic data. */
54
- export function projectStateForRead(state, artifactHints = {}) {
97
+ export function projectStateForRead(state, artifactHints = {}, modules) {
55
98
  const { lazy: _hidden, ...visible } = state;
56
- const projected = projectSemanticState(visible);
57
- if (projected.artifacts)
58
- projected.artifacts = projectArtifactsForModel(projected.artifacts, artifactHints);
59
- return projected;
99
+ const selected = projectSemanticState(visible, modules);
100
+ const core = projectPlaneContext(selected, artifactHints).state;
101
+ return { ...core, ...Object.fromEntries(Object.keys(modules ?? {}).filter(id => Object.hasOwn(selected, id)).map(id => [id, structuredClone(selected[id])])) };
60
102
  }
61
103
  /** Ordinary model context omits empty object fields; exact reads and replay remain observational. */
62
104
  export function projectStateForModel(state, artifactHints = {}) {
@@ -1,7 +1,8 @@
1
1
  import type { ArtifactInvalidationReason } from "./artifact.ts";
2
2
  import type { RecentTransitionWindow } from "./history.ts";
3
3
  import { type Snapshot } from "./snapshot.ts";
4
- import { type SemanticState, type ScopedStates, type StateScope } from "./state.ts";
4
+ import { type SemanticState, type ScopeStateValues, type StateScope } from "./state.ts";
5
+ import { type ScopeSelection } from "./selection.ts";
5
6
  import type { ScopeRevisions, TransitionBoundary } from "./temporal.ts";
6
7
  export declare const STATUS_KEY = "state-flow";
7
8
  export type Colorize = (color: "accent" | "dim", text: string) => string;
@@ -15,23 +16,27 @@ export interface StatusDiagnostics {
15
16
  repositoryRoot: string;
16
17
  cwdScopeKey: string;
17
18
  sessionScopeKey: string;
18
- scopeStates: ScopedStates;
19
- /** Overlay raw scopes before defaults so absent scalar planes cannot mask lower scopes. */
19
+ /** Omission retains all-three compatibility; a subset must be explicit, never inferred from missing data. */
20
+ selection?: ScopeSelection;
21
+ /** Captured policy may request Session while only shared owners are observable. Never a materialization grant. */
22
+ requestedSelection?: ScopeSelection;
23
+ scopeStates: ScopeStateValues;
24
+ /** Legacy overlay override. Explicit selected observations derive only from scopeStates. */
20
25
  effectiveState?: SemanticState;
21
26
  recent: RecentTransitionWindow;
22
27
  historyLimit: number;
23
28
  temporal?: {
24
29
  head: TransitionBoundary;
25
30
  historyDepth: number;
26
- tailCounts: Record<StateScope, number>;
27
- revisions: ScopeRevisions;
31
+ tailCounts: Partial<Record<StateScope, number>>;
32
+ revisions: Partial<ScopeRevisions>;
28
33
  };
29
34
  staleArtifacts: readonly StaleArtifactDiagnostic[];
30
35
  durableStateError?: string;
31
36
  publicationError?: string;
32
37
  }
33
- export declare function formatScopeRevisionVector(revisions: ScopeRevisions): string;
34
- export declare function compactStatus(snapshot: Snapshot, _revisions: ScopeRevisions, colorize: Colorize): string | undefined;
35
- /** Operator-only footprint: serialized plane sizes and intent-owned share of top-level working/lazy entries. */
36
- export declare function scopeMemoryLines(states: ScopedStates): string[];
38
+ export declare function formatScopeRevisionVector(revisions: Partial<ScopeRevisions>, selection?: ScopeSelection): string;
39
+ export declare function compactStatus(snapshot: Snapshot, _revisions: Partial<ScopeRevisions>, colorize: Colorize): string | undefined;
40
+ /** Operator-only footprint: serialized plane sizes and intent-owned share of top-level tactics/lazy entries. */
41
+ export declare function scopeMemoryLines(states: ScopeStateValues, selection?: ScopeSelection): string[];
37
42
  export declare function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostics): string;
@@ -1,10 +1,24 @@
1
1
  import { ownedTopLevelKeys } from "./ownership.js";
2
2
  import { conciseDiagnostic } from "./protocol.js";
3
3
  import { isActiveRestorationBlocked } from "./snapshot.js";
4
- import { overlayStates, projectSemanticState } from "./state.js";
4
+ import { assertScopeStateValues, overlayStates, projectSemanticState } from "./state.js";
5
+ import { applyPatch, isObject } from "./json.js";
6
+ import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
5
7
  export const STATUS_KEY = "state-flow";
6
- export function formatScopeRevisionVector(revisions) {
7
- return `g${revisions.global}c${revisions.cwd}s${revisions.session}`;
8
+ function assertScopeNumbers(values, selection, label) {
9
+ if (!isObject(values) || Object.keys(values).length !== selection.length
10
+ || Object.keys(values).some(scope => !selection.includes(scope))
11
+ || selection.some(scope => !Object.hasOwn(values, scope)))
12
+ throw new Error(`Invalid State Flow selected ${label}`);
13
+ for (const scope of selection)
14
+ if (!Number.isSafeInteger(values[scope]) || values[scope] < 0)
15
+ throw new Error(`Invalid State Flow selected ${label}`);
16
+ }
17
+ export function formatScopeRevisionVector(revisions, selection = DEFAULT_SCOPE_SELECTION) {
18
+ const selected = captureScopeSelection(selection);
19
+ assertScopeNumbers(revisions, selected, "revision vector");
20
+ const prefix = { global: "g", cwd: "c", session: "s" };
21
+ return selected.map(scope => `${prefix[scope]}${revisions[scope]}`).join("");
8
22
  }
9
23
  export function compactStatus(snapshot, _revisions, colorize) {
10
24
  const { mode } = snapshot.config;
@@ -13,26 +27,28 @@ export function compactStatus(snapshot, _revisions, colorize) {
13
27
  return `${colorize("accent", "state-flow")} ${colorize("dim", isActiveRestorationBlocked(snapshot) ? "active (blocked)" : mode)}`;
14
28
  }
15
29
  function countArtifacts(states, scope) {
16
- return Object.keys(states[scope].artifacts).length;
30
+ return Object.keys(states[scope].artifacts ?? {}).length;
17
31
  }
18
- const STATUS_PLANES = ["intents", "contract", "working", "artifacts", "response", "lazy"];
32
+ const STATUS_PLANES = ["intents", "strategy", "tactics", "artifacts", "response", "lazy"];
19
33
  const encoder = new TextEncoder();
20
34
  function isPresentPlane(value) {
21
35
  if (value === undefined || value === "")
22
36
  return false;
23
37
  return typeof value !== "object" || value === null || Object.keys(value).length > 0;
24
38
  }
25
- /** Operator-only footprint: serialized plane sizes and intent-owned share of top-level working/lazy entries. */
26
- export function scopeMemoryLines(states) {
39
+ /** Operator-only footprint: serialized plane sizes and intent-owned share of top-level tactics/lazy entries. */
40
+ export function scopeMemoryLines(states, selection = DEFAULT_SCOPE_SELECTION) {
41
+ const selected = captureScopeSelection(selection);
42
+ assertScopeStateValues(states, selected);
27
43
  const lines = [];
28
- for (const scope of ["global", "cwd", "session"]) {
44
+ for (const scope of selected) {
29
45
  const state = states[scope];
30
46
  const sizes = STATUS_PLANES.filter((plane) => isPresentPlane(state[plane]))
31
47
  .map((plane) => `${plane} ${encoder.encode(JSON.stringify(state[plane])).length} B`);
32
48
  if (sizes.length === 0)
33
49
  continue;
34
50
  const owned = ownedTopLevelKeys(scope, state);
35
- const shares = ["working", "lazy"].flatMap((plane) => {
51
+ const shares = ["tactics", "lazy"].flatMap((plane) => {
36
52
  const value = state[plane];
37
53
  const keys = value !== null && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
38
54
  return keys.length === 0 ? [] : [`${plane} ${keys.filter((key) => owned[plane].has(key)).length}/${keys.length}`];
@@ -42,36 +58,57 @@ export function scopeMemoryLines(states) {
42
58
  return lines.length ? ["Scope memory:", ...lines] : [];
43
59
  }
44
60
  export function detailedStatus(snapshot, diagnostics) {
61
+ const selection = diagnostics.selection;
62
+ const explicit = selection !== undefined;
63
+ const selected = captureScopeSelection(explicit ? selection : DEFAULT_SCOPE_SELECTION);
64
+ const requested = diagnostics.requestedSelection === undefined ? selected : captureScopeSelection(diagnostics.requestedSelection);
45
65
  const available = diagnostics.temporal !== undefined && diagnostics.durableStateError === undefined;
46
- const materialized = !available ? undefined : diagnostics.effectiveState ?? projectSemanticState(overlayStates(diagnostics.scopeStates.global, diagnostics.scopeStates.cwd, diagnostics.scopeStates.session));
66
+ if (available) {
67
+ if (selected.some(scope => !requested.includes(scope)))
68
+ throw new Error("State Flow materialization exceeds requested scopes");
69
+ assertScopeStateValues(diagnostics.scopeStates, selected);
70
+ assertScopeNumbers(diagnostics.temporal.revisions, selected, "revision vector");
71
+ assertScopeNumbers(diagnostics.temporal.tailCounts, selected, "tail counts");
72
+ }
73
+ // A selected observation derives its overlay from exact owners, never a stale all-owner override.
74
+ const materialized = !available ? undefined : explicit
75
+ ? selected.reduce((state, scope) => applyPatch(state, projectSemanticState(diagnostics.scopeStates[scope])), {})
76
+ : diagnostics.effectiveState ?? projectSemanticState(overlayStates(...selected.map(scope => diagnostics.scopeStates[scope])));
47
77
  // Only top-level plane boundaries gain whitespace; nested user JSON stays unchanged.
48
78
  const stateJson = materialized === undefined ? undefined : JSON.stringify(materialized, null, 2).replace(/,\n(?= ")/g, ",\n\n");
49
- const invalidated = available ? String(diagnostics.staleArtifacts.length) : "unavailable";
50
- const invalidationLines = diagnostics.staleArtifacts.length === 0
51
- ? []
52
- : [
53
- "Pending artifact invalidations:",
54
- ...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
55
- ];
79
+ const stale = explicit ? !available ? [] : diagnostics.staleArtifacts.flatMap(notice => {
80
+ const scope = notice.scope;
81
+ return selected.includes(scope) ? [{ scope, path: notice.path, reason: notice.reason }] : [];
82
+ }) : diagnostics.staleArtifacts;
83
+ const invalidated = available ? String(stale.length) : "unavailable";
84
+ const invalidationLines = stale.length === 0 ? []
85
+ : ["Pending artifact invalidations:", ...stale.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`)];
56
86
  const temporal = available ? diagnostics.temporal : undefined;
57
87
  const temporalLines = temporal === undefined
58
88
  ? [`Temporal materialization unavailable: ${conciseDiagnostic(diagnostics.durableStateError ?? "no selected branch runtime")}`,
59
89
  `Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`]
60
- : [`Scope revisions: ${formatScopeRevisionVector(temporal.revisions)}`,
90
+ : [`Scope revisions: ${formatScopeRevisionVector(temporal.revisions, selected)}`,
61
91
  `Runtime metadata: step #${snapshot.meta.step}${snapshot.meta.bootstrap ? "; bootstrap" : ""}`,
62
92
  `Hot history: offsets 0..${temporal.historyDepth}; maximum depth ${diagnostics.historyLimit}`];
63
93
  const artifacts = (scope) => countArtifacts(diagnostics.scopeStates, scope);
64
- const hasArtifacts = available && ["global", "cwd", "session"].some((scope) => artifacts(scope) > 0);
94
+ const hasArtifacts = available && selected.some((scope) => artifacts(scope) > 0);
95
+ const scopeKeys = [
96
+ ...(selected.includes("cwd") ? [`CWD ${diagnostics.cwdScopeKey}`] : []),
97
+ ...(selected.includes("session") ? [`session ${diagnostics.sessionScopeKey}`] : []),
98
+ ];
99
+ const artifactLabels = { global: "global", cwd: "CWD", session: "session" };
65
100
  return [
66
101
  `Mode: ${snapshot.config.mode}`,
102
+ ...(explicit ? [`Selected scopes: ${requested.join(", ")}`] : []),
103
+ ...(available && selected.length !== requested.length ? [`Materialized scopes: ${selected.join(", ")}`] : []),
67
104
  ...(isActiveRestorationBlocked(snapshot) ? ["Inference blocked: Active memory restoration failed. Use /state-flow-active for current memory or select Passive/Off for native context."] : []),
68
105
  `Repository: ${diagnostics.repositoryRoot}`,
69
- `Scope keys: CWD ${diagnostics.cwdScopeKey}; session ${diagnostics.sessionScopeKey}`,
106
+ ...(scopeKeys.length ? [`Scope keys: ${scopeKeys.join("; ")}`] : []),
70
107
  ...temporalLines,
71
108
  ...(diagnostics.publicationError === undefined ? [] : [`Memory writes paused after mode change: ${conciseDiagnostic(diagnostics.publicationError)}`]),
72
- ...(hasArtifacts ? [`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; pending invalidations ${invalidated}`] : []),
109
+ ...(hasArtifacts ? [`Artifacts: ${selected.map(scope => `${artifactLabels[scope]} ${artifacts(scope)}`).join("; ")}; pending invalidations ${invalidated}`] : []),
73
110
  ...invalidationLines,
74
- ...(available ? scopeMemoryLines(diagnostics.scopeStates) : []),
111
+ ...(available ? scopeMemoryLines(diagnostics.scopeStates, selected) : []),
75
112
  ...(stateJson === undefined
76
113
  ? ["Effective memory: unavailable"]
77
114
  : ["Effective memory:", "", stateJson]),