dsh-ptc-plus 0.3.2 → 0.4.2

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 (206) hide show
  1. package/README.en.md +63 -80
  2. package/README.md +63 -80
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30827 -742
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +556 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +7 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +77 -21
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +95 -19
  51. package/docs/durability-design.md +28 -10
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/publishing.md +5 -3
  55. package/docs/runtime-reference.md +76 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +759 -0
  58. package/docs/user-bindings.en.md +66 -0
  59. package/docs/user-bindings.md +66 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification-optimization-work.md +41 -0
  62. package/docs/verification.md +71 -0
  63. package/index.js +369 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/async-local-storage-intrinsics.js +38 -0
  66. package/internal/binding-declaration-contract.js +8 -0
  67. package/internal/binding-descriptors.js +1 -0
  68. package/internal/binding-pattern-traversal.js +25 -0
  69. package/internal/binding-pattern.js +26 -29
  70. package/internal/binding-source-analysis.js +708 -0
  71. package/internal/binding-update-policy.js +72 -0
  72. package/internal/bounded-worker-round.js +56 -0
  73. package/internal/callable-definition-environment.js +42 -0
  74. package/internal/callable-source-catalog.js +129 -0
  75. package/internal/callable-source-encoding.js +47 -0
  76. package/internal/callable-source-facts.js +459 -0
  77. package/internal/callable-source-format.js +2 -0
  78. package/internal/callable-source-lowering.js +123 -0
  79. package/internal/cell-analysis-contract.js +25 -0
  80. package/internal/cell-analysis.js +253 -290
  81. package/internal/cell-completion.js +15 -0
  82. package/internal/cell-error.js +7 -0
  83. package/internal/cell-parser.js +66 -0
  84. package/internal/cell-rewriter.js +3 -677
  85. package/internal/commonjs-export-evidence.js +63 -0
  86. package/internal/commonjs-source-evidence.js +72 -0
  87. package/internal/compiler-amaro.cjs +2 -0
  88. package/internal/compiler-compression.js +12 -0
  89. package/internal/compiler-data.js +58 -0
  90. package/internal/compiler-descriptors.js +54 -0
  91. package/internal/compiler-dynamic-cache.js +48 -0
  92. package/internal/compiler-entry.js +35 -0
  93. package/internal/compiler-generated-names.js +17 -0
  94. package/internal/compiler-intrinsics.js +50 -0
  95. package/internal/compiler-module-links.js +37 -0
  96. package/internal/compiler-operations.js +61 -0
  97. package/internal/compiler-platform-factory.js +84 -0
  98. package/internal/compiler-platform.js +136 -0
  99. package/internal/compiler-record-roles.js +53 -0
  100. package/internal/compiler-region-output.js +259 -0
  101. package/internal/compiler-resource-lowering.js +103 -0
  102. package/internal/compiler-scope-facts.js +115 -0
  103. package/internal/compiler-service.js +109 -0
  104. package/internal/compiler-source-cache.js +32 -0
  105. package/internal/compiler-source-pieces.js +93 -0
  106. package/internal/compiler-source-regions.js +132 -0
  107. package/internal/compiler-storage-source.js +54 -0
  108. package/internal/compiler-text.js +15 -0
  109. package/internal/compiler-typescript.cjs +2 -0
  110. package/internal/config-spec.js +83 -27
  111. package/internal/cordis-tools-owner.js +130 -21
  112. package/internal/diagnostic.js +27 -1
  113. package/internal/direct-surface-owner.js +76 -23
  114. package/internal/dynamic-binding-evidence.js +16 -0
  115. package/internal/dynamic-environment-compiler.js +779 -0
  116. package/internal/dynamic-environment-integration.js +91 -0
  117. package/internal/dynamic-environment-runtime.js +383 -0
  118. package/internal/dynamic-native-calls.js +153 -0
  119. package/internal/dynamic-native-runtime.js +262 -0
  120. package/internal/dynamic-scope-analysis.js +90 -0
  121. package/internal/edit-transport-owner.js +27 -2
  122. package/internal/execution-seam-compat.js +193 -0
  123. package/internal/failure-reporting.js +136 -28
  124. package/internal/host-rpc.js +66 -0
  125. package/internal/isolated-worker.js +555 -0
  126. package/internal/kernel-child.js +139 -0
  127. package/internal/kernel-worker.js +1179 -233
  128. package/internal/language-semantics.js +14 -0
  129. package/internal/legacy-binding-catalog.js +48 -0
  130. package/internal/legacy-cell-analysis.js +600 -0
  131. package/internal/legacy-cell-parser.js +50 -0
  132. package/internal/legacy-cell-rewriter.js +631 -0
  133. package/internal/legacy-repl-convenience.js +291 -0
  134. package/internal/managed-module-operations.js +170 -0
  135. package/internal/module-compilation.js +322 -0
  136. package/internal/module-runtime-platform.js +19 -0
  137. package/internal/module-transform-contract.js +42 -0
  138. package/internal/native-await-declarations.js +68 -0
  139. package/internal/native-root-compilation.js +54 -0
  140. package/internal/native-root-dynamic.js +67 -0
  141. package/internal/record-utils.js +24 -15
  142. package/internal/recovery-tips.js +27 -15
  143. package/internal/rejected-cell-editor.js +13 -8
  144. package/internal/repl-convenience.js +2 -289
  145. package/internal/repl-memory-projection.js +81 -12
  146. package/internal/repl-observation-interest.js +70 -0
  147. package/internal/repl-rewrite-contract.js +6 -0
  148. package/internal/repl-scope-normalizer.js +2091 -0
  149. package/internal/repl-value-observer.js +117 -0
  150. package/internal/rpc-contract.js +52 -0
  151. package/internal/runtime-bridge-owner.js +172 -28
  152. package/internal/runtime-config.js +48 -52
  153. package/internal/runtime-contexts.js +82 -10
  154. package/internal/runtime-intrinsics.js +101 -0
  155. package/internal/runtime-messages.js +111 -0
  156. package/internal/session-cell-executor.js +402 -87
  157. package/internal/session-journal-recovery.js +684 -0
  158. package/internal/session-journal-schema.js +123 -0
  159. package/internal/session-journal.js +199 -654
  160. package/internal/session-log-view.js +42 -9
  161. package/internal/session-runtime.js +257 -95
  162. package/internal/session-state.js +247 -71
  163. package/internal/source-position-map.js +230 -71
  164. package/internal/stateful-module-compiler.js +154 -0
  165. package/internal/stateful-module-runtime.js +303 -0
  166. package/internal/stateful-root-compiler.js +600 -0
  167. package/internal/stateful-root-runtime.js +340 -0
  168. package/internal/typert-codec-compat.js +28 -0
  169. package/internal/typescript-transform.js +31 -0
  170. package/internal/user-binding-authoring.js +70 -0
  171. package/internal/user-binding-console-worker.js +244 -0
  172. package/internal/user-binding-console.js +148 -0
  173. package/internal/user-binding-draft-projection.js +335 -0
  174. package/internal/user-binding-model-context.js +31 -0
  175. package/internal/user-binding-runner.js +56 -0
  176. package/internal/user-bindings-owner.js +1065 -0
  177. package/internal/user-bindings-store.js +293 -0
  178. package/internal/user-bindings.js +361 -0
  179. package/internal/value-wire-schema.js +28 -0
  180. package/internal/value-wire.js +130 -91
  181. package/internal/worker-client.js +57 -67
  182. package/internal/worker-cwd-virtualization.js +80 -53
  183. package/internal/worker-environment.js +65 -0
  184. package/internal/worker-output-relay.js +29 -0
  185. package/internal/worker-repl-realm.js +134 -0
  186. package/internal/worker-shutdown.js +14 -0
  187. package/package.json +76 -7
  188. package/src/client-activity.js +95 -53
  189. package/src/client-authoring-view.js +580 -0
  190. package/src/client-binding-review.js +230 -0
  191. package/src/client-bindings-data.js +29 -0
  192. package/src/client-catalog.js +127 -0
  193. package/src/client-code-editor.js +84 -0
  194. package/src/client-console.js +112 -0
  195. package/src/client-copy.js +370 -0
  196. package/src/client-feature-gates.js +62 -0
  197. package/src/client-host-compat.js +150 -0
  198. package/src/client-indicator-view.js +267 -0
  199. package/src/client-repl-view.js +292 -0
  200. package/src/client-rpc.js +20 -0
  201. package/src/client-settings-view.js +221 -0
  202. package/src/client-styles.js +106 -0
  203. package/src/client-tool-view.js +108 -0
  204. package/src/client-transport.js +159 -0
  205. package/src/client-workbench.js +521 -0
  206. package/src/client.js +233 -615
@@ -2,42 +2,76 @@ import { decodeValue, encodeValue, normalizeValueWire } from './value-wire.js'
2
2
  import { normalizeDiagnostic } from './diagnostic.js'
3
3
  import { assertOwnFields, isRecord } from './record-utils.js'
4
4
  import { sessionEvents } from './session-events.js'
5
+ import { LEGACY_LANGUAGE_SEMANTICS, normalizeLanguageSemantics } from './language-semantics.js'
6
+ import { LEGACY_USER_BINDING_TRANSFORM, historicalModuleTransformForLanguage,
7
+ moduleTransformForLanguage, normalizeModuleTransform } from './module-transform-contract.js'
5
8
  import {
6
9
  LEGACY_DEFAULT_EXPORT_BINDING,
7
- LIVE_DEFAULT_EXPORT_BINDING,
10
+ LEGACY_IMPORT_EXPRESSION_BOUNDARY,
11
+ LIVE_MODULE_SEMANTICS,
8
12
  } from './repl-rewrite-contract.js'
9
-
10
- export const JOURNAL_KEY = 'dshPtcPlus'
11
- export const EDIT_TARGET_KEY = 'dshPtcPlusEdit'
12
- export const DERIVED_RUN_KEY = 'dshPtcPlusDerivedRun'
13
- export const REWRITES_KEY = 'dshPtcPlusRewrites'
14
- export const RECOVERY_BOUNDARY_KEY = 'dshPtcPlusRecoveryBoundaries'
15
- export const JOURNAL_VERSION = 4
16
- const LEGACY_JOURNAL_VERSION = 1
17
- const INTERMEDIATE_JOURNAL_VERSION = 2
18
- const PREVIOUS_JOURNAL_VERSION = 3
19
- export const RECOVERY_BOUNDARY_EVENT = 'ptc-plus/recovery-boundary'
20
-
21
- const STATUSES = new Set(['durable', 'volatile', 'discarded', 'noop'])
22
- const BINDING_MODES = new Set(['loose', 'strict'])
23
- const JOURNAL_FIELDS = new Set(['version', 'bindingPolicy', 'rewritePolicy', 'moduleSemantics', 'status', 'calls', 'operations', 'confirms', 'diagnostics', 'completion', 'volatileReason'])
24
- const PREDECESSOR_JOURNAL_FIELDS = new Set(['version', 'bindingMode', 'rewritePolicy', 'status', 'calls', 'operations', 'confirms', 'diagnostics', 'completion', 'volatileReason'])
25
- const LEGACY_JOURNAL_FIELDS = new Set([...PREDECESSOR_JOURNAL_FIELDS].filter(field => field !== 'rewritePolicy'))
26
- const BINDING_POLICY_FIELDS = new Set(['variableRedeclarations', 'functionClassRedeclarations'])
27
- const REWRITE_POLICY_FIELDS = new Set(['autoRewriteImports', 'autoStripExports', 'autoSplitRedeclarations'])
28
- const MODULE_SEMANTICS_FIELDS = new Set(['defaultExportBinding'])
29
- const DEFAULT_EXPORT_BINDINGS = new Set([
30
- LEGACY_DEFAULT_EXPORT_BINDING,
31
- LIVE_DEFAULT_EXPORT_BINDING,
32
- ])
33
- const CALL_SUCCESS_FIELDS = new Set(['global', 'member', 'args', 'ok', 'value', 'settle'])
34
- const CALL_ERROR_FIELDS = new Set(['global', 'member', 'args', 'ok', 'error', 'settle'])
35
- const OPERATION_FIELDS = new Set(['action', 'name'])
36
- const RETURN_FIELDS = new Set(['kind', 'hasValue', 'value'])
37
- const THROW_FIELDS = new Set(['kind', 'error'])
38
- const ERROR_FIELDS = new Set(['kind', 'message'])
39
- const EDIT_TARGET_FIELDS = new Set(['targetCallSeq'])
40
- const DERIVED_RUN_FIELDS = new Set(['code', 'description'])
13
+ import { userBindingsSnapshotFromMeta } from './user-bindings.js'
14
+ import {
15
+ JOURNAL_KEY,
16
+ EDIT_TARGET_KEY,
17
+ DERIVED_RUN_KEY,
18
+ REWRITES_KEY,
19
+ RECOVERY_BOUNDARY_KEY,
20
+ JOURNAL_VERSION,
21
+ LANGUAGE_SEMANTICS_JOURNAL_VERSION,
22
+ LANGUAGE_SEMANTICS_JOURNAL_FIELDS,
23
+ IMPORT_BOUNDARY_JOURNAL_VERSION,
24
+ PER_NAME_USER_BINDINGS_JOURNAL_VERSION,
25
+ PER_NAME_JOURNAL_FIELDS,
26
+ WHOLE_ENTRY_JOURNAL_FIELDS,
27
+ LIVE_USER_BINDINGS_SHADOW_POLICY,
28
+ LEGACY_USER_BINDINGS_SHADOW_POLICY,
29
+ normalizeJournalUserBindingNames,
30
+ LIVE_USER_BINDINGS_REUSE_POLICY,
31
+ LEGACY_USER_BINDINGS_REUSE_POLICY,
32
+ LEGACY_JOURNAL_VERSION,
33
+ USER_BINDING_RELATIONLESS_JOURNAL_VERSION,
34
+ FINGERPRINT_REUSE_JOURNAL_VERSION,
35
+ VERSIONED_BINDING_REUSE_JOURNAL_VERSION,
36
+ RECOVERY_BOUNDARY_EVENT,
37
+ STATUSES,
38
+ BINDING_MODES,
39
+ JOURNAL_FIELDS,
40
+ FINGERPRINT_REUSE_JOURNAL_FIELDS,
41
+ RELATIONLESS_JOURNAL_FIELDS,
42
+ PREDECESSOR_JOURNAL_FIELDS,
43
+ LEGACY_JOURNAL_FIELDS,
44
+ BINDING_POLICY_FIELDS,
45
+ REWRITE_POLICY_FIELDS,
46
+ MODULE_SEMANTICS_FIELDS,
47
+ LEGACY_MODULE_SEMANTICS_FIELDS,
48
+ IMPORT_EXPRESSION_BOUNDARIES,
49
+ DEFAULT_EXPORT_BINDINGS,
50
+ CALL_SUCCESS_FIELDS,
51
+ CALL_ERROR_FIELDS,
52
+ OPERATION_FIELDS,
53
+ RETURN_FIELDS,
54
+ THROW_FIELDS,
55
+ ERROR_FIELDS,
56
+ EDIT_TARGET_FIELDS,
57
+ DERIVED_RUN_FIELDS,
58
+ JOURNAL_VERSIONS,
59
+ USER_BINDINGS_REUSE_POLICIES,
60
+ RECOVERY_BOUNDARY_FIELDS,
61
+ REWRITE_FIELDS,
62
+ REWRITE_KINDS,
63
+ } from './session-journal-schema.js'
64
+
65
+ export {
66
+ JOURNAL_KEY,
67
+ EDIT_TARGET_KEY,
68
+ DERIVED_RUN_KEY,
69
+ REWRITES_KEY,
70
+ RECOVERY_BOUNDARY_KEY,
71
+ JOURNAL_VERSION,
72
+ LIVE_USER_BINDINGS_REUSE_POLICY,
73
+ RECOVERY_BOUNDARY_EVENT,
74
+ } from './session-journal-schema.js'
41
75
 
42
76
  function cloneJson(value) {
43
77
  if (value === undefined) return undefined
@@ -197,11 +231,71 @@ function normalizeModuleSemantics(value) {
197
231
  if (!DEFAULT_EXPORT_BINDINGS.has(value.defaultExportBinding)) {
198
232
  throw new Error('invalid dsh-ptc-plus journal default export binding semantics')
199
233
  }
200
- return Object.freeze({ defaultExportBinding: value.defaultExportBinding })
234
+ if (!IMPORT_EXPRESSION_BOUNDARIES.has(value.importExpressionBoundary)) {
235
+ throw new Error('invalid dsh-ptc-plus journal import expression boundary semantics')
236
+ }
237
+ return Object.freeze({
238
+ defaultExportBinding: value.defaultExportBinding,
239
+ importExpressionBoundary: value.importExpressionBoundary,
240
+ })
241
+ }
242
+
243
+ function migrateLegacyModuleSemantics(value) {
244
+ if (!isRecord(value)) throw new Error('invalid dsh-ptc-plus journal module semantics')
245
+ assertOwnFields(value, LEGACY_MODULE_SEMANTICS_FIELDS, 'journal module semantics')
246
+ return { ...value, importExpressionBoundary: LEGACY_IMPORT_EXPRESSION_BOUNDARY }
247
+ }
248
+
249
+ function normalizeUserBindingsFingerprint(value) {
250
+ if (value !== null && (typeof value !== 'string' || !/^[a-f0-9]{64}$/.test(value))) {
251
+ throw new Error('invalid dsh-ptc-plus journal user binding fingerprint')
252
+ }
253
+ return value
201
254
  }
202
255
 
203
256
  function migrateJournal(value, resolveLegacyConfirm) {
204
257
  if (value.version === JOURNAL_VERSION) return value
258
+ if (value.version === LANGUAGE_SEMANTICS_JOURNAL_VERSION) {
259
+ assertOwnFields(value, LANGUAGE_SEMANTICS_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
260
+ const languageSemantics = normalizeLanguageSemantics(value.languageSemantics)
261
+ return { ...value, version: JOURNAL_VERSION, moduleTransform: historicalModuleTransformForLanguage(languageSemantics) }
262
+ }
263
+ if (value.version === PER_NAME_USER_BINDINGS_JOURNAL_VERSION) {
264
+ assertOwnFields(value, PER_NAME_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
265
+ return { ...value, version: JOURNAL_VERSION, languageSemantics: LEGACY_LANGUAGE_SEMANTICS,
266
+ moduleTransform: LEGACY_USER_BINDING_TRANSFORM }
267
+ }
268
+ const shadow = { userBindingsShadowPolicy: LEGACY_USER_BINDINGS_SHADOW_POLICY, userBindingNames: null,
269
+ languageSemantics: LEGACY_LANGUAGE_SEMANTICS, moduleTransform: LEGACY_USER_BINDING_TRANSFORM }
270
+ if (value.version === IMPORT_BOUNDARY_JOURNAL_VERSION) {
271
+ assertOwnFields(value, WHOLE_ENTRY_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
272
+ return { ...value, version: JOURNAL_VERSION, ...shadow }
273
+ }
274
+ if (value.version === VERSIONED_BINDING_REUSE_JOURNAL_VERSION) {
275
+ assertOwnFields(value, WHOLE_ENTRY_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
276
+ return { ...value, version: JOURNAL_VERSION, ...shadow, moduleSemantics: migrateLegacyModuleSemantics(value.moduleSemantics) }
277
+ }
278
+ if (value.version === FINGERPRINT_REUSE_JOURNAL_VERSION) {
279
+ assertOwnFields(value, FINGERPRINT_REUSE_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
280
+ return {
281
+ ...value,
282
+ version: JOURNAL_VERSION,
283
+ moduleSemantics: migrateLegacyModuleSemantics(value.moduleSemantics),
284
+ userBindingsReusePolicy: LEGACY_USER_BINDINGS_REUSE_POLICY,
285
+ ...shadow,
286
+ }
287
+ }
288
+ if (value.version === USER_BINDING_RELATIONLESS_JOURNAL_VERSION) {
289
+ assertOwnFields(value, RELATIONLESS_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
290
+ return {
291
+ ...value,
292
+ version: JOURNAL_VERSION,
293
+ moduleSemantics: migrateLegacyModuleSemantics(value.moduleSemantics),
294
+ userBindingsFingerprint: null,
295
+ userBindingsReusePolicy: LEGACY_USER_BINDINGS_REUSE_POLICY,
296
+ ...shadow,
297
+ }
298
+ }
205
299
  const legacy = value.version === LEGACY_JOURNAL_VERSION
206
300
  assertOwnFields(
207
301
  value,
@@ -218,7 +312,13 @@ function migrateJournal(value, resolveLegacyConfirm) {
218
312
  functionClassRedeclarations: false,
219
313
  },
220
314
  rewritePolicy: legacy ? LEGACY_REWRITE_POLICY : value.rewritePolicy,
221
- moduleSemantics: { defaultExportBinding: LEGACY_DEFAULT_EXPORT_BINDING },
315
+ moduleSemantics: {
316
+ defaultExportBinding: LEGACY_DEFAULT_EXPORT_BINDING,
317
+ importExpressionBoundary: LEGACY_IMPORT_EXPRESSION_BOUNDARY,
318
+ },
319
+ userBindingsFingerprint: null,
320
+ userBindingsReusePolicy: LEGACY_USER_BINDINGS_REUSE_POLICY,
321
+ ...shadow,
222
322
  confirms: legacy
223
323
  ? normalizeLegacyConfirms(value.confirms, resolveLegacyConfirm)
224
324
  : value.confirms,
@@ -228,15 +328,23 @@ function migrateJournal(value, resolveLegacyConfirm) {
228
328
  /** Validate and detach one journal emitted by the runtime. */
229
329
  export function normalizeJournal(value, options = {}) {
230
330
  if (!isRecord(value)) throw new Error('invalid dsh-ptc-plus journal')
231
- if (![LEGACY_JOURNAL_VERSION, INTERMEDIATE_JOURNAL_VERSION, PREVIOUS_JOURNAL_VERSION, JOURNAL_VERSION].includes(value.version)
331
+ if (!JOURNAL_VERSIONS.has(value.version)
232
332
  || !STATUSES.has(value.status)) {
233
333
  throw new Error('invalid dsh-ptc-plus journal')
234
334
  }
235
335
  const migrated = migrateJournal(value, options.resolveLegacyConfirm)
236
336
  assertOwnFields(migrated, JOURNAL_FIELDS, 'dsh-ptc-plus journal')
337
+ const languageSemantics = normalizeLanguageSemantics(migrated.languageSemantics)
338
+ const moduleTransform = normalizeModuleTransform(migrated.moduleTransform, languageSemantics)
237
339
  const bindingPolicy = normalizeBindingPolicy(migrated.bindingPolicy)
238
340
  const rewritePolicy = normalizeRewritePolicy(migrated.rewritePolicy)
239
341
  const moduleSemantics = normalizeModuleSemantics(migrated.moduleSemantics)
342
+ const userBindingsFingerprint = normalizeUserBindingsFingerprint(migrated.userBindingsFingerprint)
343
+ const userBindingsReusePolicy = migrated.userBindingsReusePolicy
344
+ const userBindingNames = normalizeJournalUserBindingNames(migrated)
345
+ if (!USER_BINDINGS_REUSE_POLICIES.has(userBindingsReusePolicy)) {
346
+ throw new Error('invalid dsh-ptc-plus journal user binding reuse policy')
347
+ }
240
348
  const calls = normalizeCalls(migrated.calls)
241
349
  const operations = normalizeOperations(migrated.operations)
242
350
  const confirms = normalizeConfirms(migrated.confirms)
@@ -257,9 +365,15 @@ export function normalizeJournal(value, options = {}) {
257
365
  }
258
366
  return Object.freeze({
259
367
  version: JOURNAL_VERSION,
368
+ languageSemantics,
369
+ moduleTransform,
260
370
  bindingPolicy,
261
371
  rewritePolicy,
262
372
  moduleSemantics,
373
+ userBindingsFingerprint,
374
+ userBindingsReusePolicy,
375
+ userBindingsShadowPolicy: migrated.userBindingsShadowPolicy,
376
+ userBindingNames,
263
377
  status: migrated.status,
264
378
  calls: Object.freeze(calls),
265
379
  operations: Object.freeze(operations),
@@ -292,15 +406,53 @@ export function normalizeDerivedEditResult(meta, expectedTargetCallSeq) {
292
406
  const recoveryBoundaries = meta[RECOVERY_BOUNDARY_KEY] === undefined
293
407
  ? undefined
294
408
  : normalizeRecoveryBoundaries(meta[RECOVERY_BOUNDARY_KEY])
409
+ const userBindings = userBindingsForJournal(meta, journal)
295
410
  return Object.freeze({
296
411
  targetCallSeq: target.targetCallSeq,
297
412
  code: derived.code,
298
413
  description: derived.description,
299
414
  journal,
300
415
  ...(recoveryBoundaries === undefined ? {} : { recoveryBoundaries }),
416
+ ...(userBindings === undefined ? {} : { userBindings }),
301
417
  })
302
418
  }
303
419
 
420
+ export function userBindingsForJournal(meta, journal) {
421
+ const userBindings = userBindingsSnapshotFromMeta(meta)
422
+ if (journal.userBindingsFingerprint === null) {
423
+ if (userBindings !== undefined) {
424
+ throw new Error('dsh-ptc-plus journal does not declare user binding metadata')
425
+ }
426
+ validateUserBindingNameProviders(journal, undefined)
427
+ return undefined
428
+ }
429
+ if (userBindings === undefined) {
430
+ throw new Error('dsh-ptc-plus journal requires user binding metadata')
431
+ }
432
+ if (userBindings.fingerprint !== journal.userBindingsFingerprint) {
433
+ throw new Error('dsh-ptc-plus journal user binding fingerprint does not match metadata')
434
+ }
435
+ validateUserBindingNameProviders(journal, userBindings)
436
+ return userBindings
437
+ }
438
+
439
+ function validateUserBindingNameProviders(journal, snapshot) {
440
+ const providers = new Map((snapshot?.entries ?? []).flatMap(entry => (
441
+ (entry.scope === 'namespace' ? [entry.name] : entry.symbols).map(name => [name, entry.id])
442
+ )))
443
+ for (const fact of journal.userBindingNames ?? []) {
444
+ if (fact.state === 'provider' && providers.get(fact.name) !== fact.entryId) {
445
+ throw new Error('journal user binding name evidence has no matching provider')
446
+ }
447
+ }
448
+ if (journal.userBindingNames !== null) {
449
+ const names = new Set(journal.userBindingNames.map(fact => fact.name))
450
+ if ([...providers.keys()].some(name => !names.has(name))) {
451
+ throw new Error('journal user binding name evidence is incomplete')
452
+ }
453
+ }
454
+ }
455
+
304
456
  /** Compare only the required persisted relation for one derived edit. */
305
457
  export function derivedEditResultsEqual(leftMeta, rightMeta, expectedTargetCallSeq) {
306
458
  try {
@@ -341,7 +493,7 @@ function normalizeRecoveryBoundaryValue(value, eventSeq = undefined) {
341
493
  && (!Number.isSafeInteger(value.frontierCallSeq) || value.frontierCallSeq < 0))) {
342
494
  throw new Error('invalid dsh-ptc-plus recovery boundary')
343
495
  }
344
- assertOwnFields(value, new Set(['failedCallSeq', 'frontierCallSeq']), 'recovery boundary')
496
+ assertOwnFields(value, RECOVERY_BOUNDARY_FIELDS, 'recovery boundary')
345
497
  if (eventSeq !== undefined && (!Number.isSafeInteger(eventSeq) || eventSeq < 0)) {
346
498
  throw new Error('invalid dsh-ptc-plus recovery boundary event sequence')
347
499
  }
@@ -443,7 +595,8 @@ export function liveToolCallSeq(session, callId, toolName) {
443
595
  }
444
596
 
445
597
  /** Start a mutable journal for one live cell. */
446
- export function createJournal(confirms = [], bindingPolicy, rewritePolicy) {
598
+ export function createJournal(confirms = [], bindingPolicy, rewritePolicy, languageSemantics = LEGACY_LANGUAGE_SEMANTICS,
599
+ moduleTransform = moduleTransformForLanguage(languageSemantics)) {
447
600
  if (typeof bindingPolicy === 'string') {
448
601
  if (!BINDING_MODES.has(bindingPolicy)) throw new TypeError('invalid dsh-ptc-plus journal binding mode')
449
602
  bindingPolicy = {
@@ -451,13 +604,18 @@ export function createJournal(confirms = [], bindingPolicy, rewritePolicy) {
451
604
  functionClassRedeclarations: false,
452
605
  }
453
606
  }
607
+ languageSemantics = normalizeLanguageSemantics(languageSemantics)
454
608
  return {
455
609
  version: JOURNAL_VERSION,
610
+ languageSemantics,
611
+ moduleTransform: normalizeModuleTransform(moduleTransform, languageSemantics),
456
612
  bindingPolicy: normalizeBindingPolicy(bindingPolicy),
457
613
  rewritePolicy: normalizeRewritePolicy(rewritePolicy),
458
- moduleSemantics: normalizeModuleSemantics({
459
- defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING,
460
- }),
614
+ moduleSemantics: normalizeModuleSemantics(LIVE_MODULE_SEMANTICS),
615
+ userBindingsFingerprint: null,
616
+ userBindingsReusePolicy: LIVE_USER_BINDINGS_REUSE_POLICY,
617
+ userBindingsShadowPolicy: LIVE_USER_BINDINGS_SHADOW_POLICY,
618
+ userBindingNames: null,
461
619
  calls: [],
462
620
  operations: [],
463
621
  confirms: [...confirms],
@@ -465,15 +623,6 @@ export function createJournal(confirms = [], bindingPolicy, rewritePolicy) {
465
623
  }
466
624
  }
467
625
 
468
- function sourceForRunCall(call) {
469
- try {
470
- const args = JSON.parse(call.data.arguments)
471
- return isRecord(args) && typeof args.code === 'string' ? args.code : undefined
472
- } catch {
473
- return undefined
474
- }
475
- }
476
-
477
626
  /** Return an owned state transition; reject volatile saves and unknown named restores. */
478
627
  export function reduceStateOperations({ nodes, head, checkpoints }, operations, nodeIndex) {
479
628
  const nextCheckpoints = new Map(checkpoints)
@@ -501,607 +650,6 @@ export function reduceStateOperations({ nodes, head, checkpoints }, operations,
501
650
  return { head: nextHead, checkpoints: nextCheckpoints, restored }
502
651
  }
503
652
 
504
- function applyOperations(state, operations, nodeIndex) {
505
- const transition = reduceStateOperations(state, operations, nodeIndex)
506
- state.head = transition.head
507
- state.checkpoints = transition.checkpoints
508
- if (transition.restored) {
509
- state.trusted = true
510
- state.volatileSuffix.length = 0
511
- }
512
- }
513
-
514
- function applyRecord(state, record, invalidCallSeqs) {
515
- const { call, code, result } = record
516
- if (invalidCallSeqs.has(call.seq)) return
517
- const journal = result.journal
518
- if (journal.status === 'noop') return
519
- if (journal.status === 'discarded') {
520
- if (journal.volatileReason !== undefined) {
521
- state.trusted = false
522
- state.volatileSuffix.push({ seq: call.seq, code, reason: journal.volatileReason })
523
- }
524
- return
525
- }
526
- if (journal.status === 'volatile') {
527
- state.trusted = false
528
- state.volatileSuffix.push({ seq: call.seq, code, reason: journal.volatileReason ?? 'volatile cell' })
529
- applyOperations(state, journal.operations, undefined)
530
- return
531
- }
532
- if (!state.trusted && !state.surfaceContracted) {
533
- state.trusted = true
534
- state.volatileSuffix.length = 0
535
- }
536
- const node = Object.freeze({
537
- code,
538
- journal,
539
- callSeq: call.seq,
540
- parent: state.head,
541
- })
542
- const index = state.nodes.push(node) - 1
543
- state.head = index
544
- try {
545
- applyOperations(state, journal.operations, index)
546
- } catch (error) {
547
- state.nodes.pop()
548
- state.head = node.parent
549
- throw error
550
- }
551
- }
552
-
553
- function recordEventSeq(record) {
554
- return record.result?.eventSeq ?? record.call.seq
555
- }
556
-
557
- function foldRecords(records, invalidCallSeqs, options = {}) {
558
- const state = {
559
- nodes: [],
560
- head: undefined,
561
- checkpoints: new Map(),
562
- volatileSuffix: [],
563
- trusted: true,
564
- surfaceContracted: options.surfaceContracted === true,
565
- }
566
- for (const record of records) {
567
- applyRecord(state, record, invalidCallSeqs)
568
- }
569
- return state
570
- }
571
-
572
- function dependsOn(nodes, index, ancestor) {
573
- for (let cursor = index; cursor !== undefined; cursor = nodes[cursor]?.parent) {
574
- if (cursor === ancestor) return true
575
- }
576
- return false
577
- }
578
-
579
- /** Return executable call sequences whose provenance remains model-visible. */
580
- export function visibleExecutableCallSeqs(session) {
581
- const events = sessionEvents(session)
582
- let nodes
583
- try {
584
- nodes = session?.surface?.nodes
585
- } catch {
586
- return undefined
587
- }
588
- if (!Array.isArray(events) || !Array.isArray(nodes)) return undefined
589
- const eventBySeq = new Map()
590
- for (const event of events) eventBySeq.set(event?.seq, event)
591
- for (const seq of nodes) {
592
- if (!Number.isSafeInteger(seq) || seq < 0 || eventBySeq.get(seq) === undefined) return undefined
593
- }
594
- const visible = new Set(nodes)
595
- const resultSources = new Set()
596
- const visibleAssistantCallSeqs = new Set()
597
- const pendingAssistantCalls = new Map()
598
- let assistantCallEvidence = false
599
- for (const event of events) {
600
- if (event?.type === 'turn/start' || event?.type === 'turn/end') {
601
- pendingAssistantCalls.clear()
602
- continue
603
- }
604
- if (event?.type === 'assistant/message') {
605
- pendingAssistantCalls.clear()
606
- const content = event.data?.message?.content
607
- if (Array.isArray(content)) {
608
- for (const part of content) {
609
- if (part?.type === 'tool-call' && typeof part.id === 'string') {
610
- const isVisible = visible.has(event.seq)
611
- if (isVisible) assistantCallEvidence = true
612
- let occurrences = pendingAssistantCalls.get(part.id)
613
- if (occurrences === undefined) {
614
- occurrences = []
615
- pendingAssistantCalls.set(part.id, occurrences)
616
- }
617
- occurrences.push(isVisible)
618
- }
619
- }
620
- }
621
- continue
622
- }
623
- if (event?.type === 'tool/call' && typeof event.data?.callId === 'string') {
624
- const occurrences = pendingAssistantCalls.get(event.data.callId)
625
- if (occurrences !== undefined && occurrences.length > 0) {
626
- const isVisible = occurrences.shift()
627
- if (occurrences.length === 0) pendingAssistantCalls.delete(event.data.callId)
628
- if (isVisible && ['run_code', 'edit_run_code'].includes(event.data.name)) {
629
- visibleAssistantCallSeqs.add(event.seq)
630
- }
631
- }
632
- }
633
- if (visible.has(event?.seq)
634
- && event?.type === 'tool/result' && Array.isArray(event.sourceEventSeqs)) {
635
- for (const sourceSeq of event.sourceEventSeqs) {
636
- if (Number.isSafeInteger(sourceSeq) && sourceSeq >= 0) resultSources.add(sourceSeq)
637
- }
638
- }
639
- }
640
- const calls = new Set()
641
- for (const event of events) {
642
- if (event?.type !== 'tool/call' || !['run_code', 'edit_run_code'].includes(event.data?.name)) continue
643
- if (assistantCallEvidence
644
- ? visibleAssistantCallSeqs.has(event.seq)
645
- : (visible.has(event.seq) || resultSources.has(event.seq))) calls.add(event.seq)
646
- }
647
- return calls
648
- }
649
-
650
- function timelineRun(call, result, eventIndex, journal) {
651
- let args
652
- try {
653
- const parsed = JSON.parse(call.data.arguments)
654
- if (isRecord(parsed)) args = Object.freeze(parsed)
655
- } catch {
656
- // Invalid persisted arguments cannot support derived source facts.
657
- }
658
- return Object.freeze({
659
- index: eventIndex,
660
- callSeq: Number.isSafeInteger(call.seq) ? call.seq : undefined,
661
- args,
662
- source: typeof args?.code === 'string' ? args.code : undefined,
663
- journal,
664
- rewrites: validatedRewrites(result.data?.meta),
665
- })
666
- }
667
-
668
- function timelineDerivedRun(call, result, eventIndex, derived) {
669
- return Object.freeze({
670
- index: eventIndex,
671
- callSeq: Number.isSafeInteger(call.seq) ? call.seq : undefined,
672
- args: Object.freeze({ description: derived.description }),
673
- source: derived.code,
674
- journal: derived.journal,
675
- rewrites: validatedRewrites(result.data?.meta),
676
- })
677
- }
678
-
679
- function successfulTimelineRun(run) {
680
- return run.journal?.completion?.kind === 'return' && run.journal.status !== 'noop'
681
- }
682
-
683
- /**
684
- * Fold persisted tool events once into the call/result and edit-target timeline
685
- * shared by prompt projection and cold journal recovery.
686
- */
687
- export function foldSessionTimeline(events) {
688
- const empty = {
689
- openTurn: false,
690
- executableCalls: new Map(),
691
- calls: [],
692
- results: new Map(),
693
- boundaries: [],
694
- found: false,
695
- unavailableResultSeq: undefined,
696
- lastSuccessfulRunIndex: undefined,
697
- latestRun: undefined,
698
- editableRun: undefined,
699
- editTargets: new Map(),
700
- }
701
- if (!Array.isArray(events)) return empty
702
-
703
- const state = { ...empty }
704
- const pendingByCallId = new Map()
705
- const seenCallIds = new Set()
706
- const claimedEditTargets = new Set()
707
- const editClaims = new Map()
708
- const journalResultSeqs = new Set()
709
- let pruneReplacementWindow
710
- let scope = 0
711
-
712
- const resetTurn = (openTurn) => {
713
- state.openTurn = openTurn
714
- scope += 1
715
- pendingByCallId.clear()
716
- seenCallIds.clear()
717
- claimedEditTargets.clear()
718
- state.latestRun = undefined
719
- state.editableRun = undefined
720
- }
721
-
722
- for (let eventIndex = 0; eventIndex < events.length; eventIndex += 1) {
723
- const event = events[eventIndex]
724
- if (pruneReplacementWindow !== undefined
725
- && event?.type !== 'compaction/prune'
726
- && eventIndex !== pruneReplacementWindow.lastEventIndex + 1) {
727
- pruneReplacementWindow = undefined
728
- }
729
- if (event?.type === 'turn/start') {
730
- resetTurn(true)
731
- continue
732
- }
733
- if (event?.type === 'turn/end') {
734
- resetTurn(false)
735
- continue
736
- }
737
- if (event?.type === RECOVERY_BOUNDARY_EVENT) {
738
- state.unavailableResultSeq ??= Number.isSafeInteger(event.seq) ? event.seq : eventIndex
739
- continue
740
- }
741
- if (event?.type === 'compaction/prune' && Array.isArray(event.data?.shadowedSeqs)) {
742
- const shadowedSeqs = event.data.shadowedSeqs
743
- const seqs = new Set(shadowedSeqs)
744
- const shadowedRange = event.data.shadowedRange
745
- const rangeValid = shadowedRange === undefined
746
- || (isRecord(shadowedRange)
747
- && shadowedRange.start === shadowedSeqs[0]
748
- && shadowedRange.end === shadowedSeqs[shadowedSeqs.length - 1])
749
- const valid = shadowedSeqs.length > 0
750
- && seqs.size === shadowedSeqs.length
751
- && shadowedSeqs.every(seq => Number.isSafeInteger(seq) && seq >= 0)
752
- && shadowedSeqs.every((seq, index) => index === 0 || seq > shadowedSeqs[index - 1])
753
- && rangeValid
754
- pruneReplacementWindow = { seqs: valid ? seqs : new Set(), lastEventIndex: eventIndex }
755
- continue
756
- }
757
- if (event?.type === 'tool/call' && typeof event.data?.callId === 'string') {
758
- const executable = event.data.name === 'run_code' || event.data.name === 'edit_run_code'
759
- let entry = { event, eventIndex, scope }
760
- if (executable) {
761
- if (Number.isSafeInteger(event.seq) && event.seq >= 0 && state.executableCalls.has(event.seq)) {
762
- throw new Error('session log contains a duplicate run_code call sequence')
763
- }
764
- if (event.data.name === 'edit_run_code') {
765
- const targetCallSeq = state.editableRun?.callSeq
766
- const target = targetCallSeq !== undefined && state.editableRun.source !== undefined
767
- && !claimedEditTargets.has(targetCallSeq)
768
- ? Object.freeze({ source: state.editableRun.source, callSeq: targetCallSeq })
769
- : undefined
770
- entry = { ...entry, editTarget: target }
771
- state.editTargets.set(event.seq, target)
772
- if (target !== undefined) {
773
- claimedEditTargets.add(target.callSeq)
774
- editClaims.set(event.seq, target.callSeq)
775
- }
776
- }
777
- state.calls.push(event)
778
- if (Number.isSafeInteger(event.seq) && event.seq >= 0) {
779
- state.executableCalls.set(event.seq, entry)
780
- }
781
- }
782
- if (seenCallIds.has(event.data.callId)) pendingByCallId.set(event.data.callId, null)
783
- else {
784
- seenCallIds.add(event.data.callId)
785
- pendingByCallId.set(event.data.callId, entry)
786
- }
787
- continue
788
- }
789
- if (event?.type !== 'tool/result') continue
790
-
791
- const sourceSeq = event.sourceEventSeqs?.[0]
792
- const callId = event.data?.message?.source?.callId
793
- let entry = Number.isSafeInteger(sourceSeq) ? state.executableCalls.get(sourceSeq) : undefined
794
- let prunedReplacement = false
795
- if (entry === undefined && Number.isSafeInteger(sourceSeq)
796
- && pruneReplacementWindow?.seqs.has(sourceSeq)
797
- && eventIndex === pruneReplacementWindow.lastEventIndex + 1
798
- && Array.isArray(event.sourceEventSeqs) && event.sourceEventSeqs.length === 1
799
- && typeof callId === 'string') {
800
- const candidate = pendingByCallId.get(callId)
801
- const candidateSeq = candidate?.event?.seq
802
- const pruneSeq = events[pruneReplacementWindow.lastEventIndex]?.seq
803
- const resultEventSeq = event.seq
804
- const surfaceOp = event.surfaceOp
805
- if (candidate !== undefined && candidate !== null
806
- && (candidate.event.data.name === 'run_code' || candidate.event.data.name === 'edit_run_code')
807
- && Number.isSafeInteger(candidateSeq) && candidateSeq >= 0
808
- && sourceSeq > candidateSeq
809
- && Number.isSafeInteger(pruneSeq) && pruneSeq >= 0
810
- && Number.isSafeInteger(resultEventSeq) && resultEventSeq - pruneSeq === 1
811
- && sourceSeq < pruneSeq
812
- && (surfaceOp === undefined
813
- || (surfaceOp?.op === 'replace' && surfaceOp.start === sourceSeq && surfaceOp.end === sourceSeq))) {
814
- entry = candidate
815
- prunedReplacement = true
816
- pruneReplacementWindow.lastEventIndex = eventIndex
817
- }
818
- }
819
- if (entry === undefined && !Number.isSafeInteger(sourceSeq) && typeof callId === 'string') {
820
- entry = pendingByCallId.get(callId)
821
- }
822
- if (typeof callId === 'string') pendingByCallId.delete(callId)
823
- if (entry === null) {
824
- state.latestRun = undefined
825
- state.editableRun = undefined
826
- continue
827
- }
828
-
829
- const call = entry?.event
830
- const callSeq = call?.seq
831
- const meta = event.data?.meta
832
- let normalized
833
- if (isRecord(meta) && Object.hasOwn(meta, JOURNAL_KEY)) {
834
- if (Number.isSafeInteger(sourceSeq) && journalResultSeqs.has(sourceSeq)) {
835
- state.unavailableResultSeq ??= sourceSeq
836
- state.results.set(sourceSeq, {
837
- eventSeq: Number.isSafeInteger(event.seq) && event.seq >= 0 ? event.seq : sourceSeq,
838
- eventIndex,
839
- error: `session log contains duplicate PTC journal results for call seq ${sourceSeq}`,
840
- })
841
- state.found = true
842
- continue
843
- }
844
- if (Number.isSafeInteger(sourceSeq)) journalResultSeqs.add(sourceSeq)
845
- if (entry === undefined) {
846
- if (Number.isSafeInteger(sourceSeq)) {
847
- state.unavailableResultSeq ??= sourceSeq
848
- }
849
- continue
850
- }
851
- const resultSeq = prunedReplacement
852
- ? callSeq
853
- : Number.isSafeInteger(sourceSeq) ? sourceSeq : callSeq
854
- const raw = {
855
- meta,
856
- eventSeq: Number.isSafeInteger(event.seq) && event.seq >= 0 ? event.seq : resultSeq,
857
- eventIndex,
858
- }
859
- if (Object.hasOwn(meta, RECOVERY_BOUNDARY_KEY)) {
860
- try {
861
- state.boundaries.push(...normalizeRecoveryBoundaries(
862
- meta[RECOVERY_BOUNDARY_KEY], raw.eventSeq,
863
- ))
864
- } catch {
865
- state.unavailableResultSeq ??= callSeq
866
- }
867
- }
868
- try {
869
- if (call.data.name === 'edit_run_code') {
870
- const derived = normalizeDerivedEditResult(meta, entry.editTarget?.callSeq)
871
- normalized = { ...raw, journal: derived.journal, derived }
872
- } else if (call.data.name === 'run_code') {
873
- const rawJournal = meta[JOURNAL_KEY]
874
- const resolveLegacyConfirm = rawJournal?.version === LEGACY_JOURNAL_VERSION
875
- ? legacyCallId => {
876
- const candidates = [...state.executableCalls.values()]
877
- .filter(candidate => candidate.eventIndex < eventIndex
878
- && candidate.event.data?.name === 'run_code'
879
- && candidate.event.data.callId === legacyCallId
880
- && !state.results.has(candidate.event.seq))
881
- return candidates.length === 1 ? candidates[0].event.seq : undefined
882
- }
883
- : undefined
884
- normalized = { ...raw, journal: normalizeJournal(rawJournal, { resolveLegacyConfirm }) }
885
- }
886
- } catch (error) {
887
- normalized = { ...raw, error: error.message }
888
- }
889
- if (Number.isSafeInteger(resultSeq)) state.results.set(resultSeq, normalized)
890
- state.found = true
891
- }
892
-
893
- const claimedTarget = editClaims.get(callSeq)
894
- if (claimedTarget !== undefined) {
895
- editClaims.delete(callSeq)
896
- if (normalized?.derived === undefined) claimedEditTargets.delete(claimedTarget)
897
- }
898
- if (entry === undefined || entry.scope !== scope) continue
899
- if (call.data.name === 'edit_run_code') {
900
- if (normalized?.derived !== undefined) {
901
- const run = timelineDerivedRun(call, event, eventIndex, normalized.derived)
902
- state.latestRun = run
903
- state.editableRun = run
904
- if (successfulTimelineRun(run)) state.lastSuccessfulRunIndex = eventIndex
905
- }
906
- continue
907
- }
908
- if (call.data.name !== 'run_code') {
909
- // Unrelated native settlements do not change the current editable cell.
910
- // The edit target is captured at dispatch time and remains valid until a
911
- // new executable cell or turn boundary supersedes it.
912
- continue
913
- }
914
- const run = timelineRun(call, event, eventIndex, normalized?.journal)
915
- state.latestRun = run
916
- state.editableRun = run
917
- if (successfulTimelineRun(run)) state.lastSuccessfulRunIndex = eventIndex
918
- }
919
- return state
920
- }
921
-
922
- /** Fold the session log into the last exactly replayable frontier. */
923
- export function recoverJournal(session, currentCallSeq, options = {}) {
924
- const events = sessionEvents(session)
925
- if (!Array.isArray(events)) {
926
- return { nodes: [], head: undefined, checkpoints: new Map(), volatileSuffix: [], available: true }
927
- }
928
- const timeline = foldSessionTimeline(events)
929
- const calls = timeline.calls.filter(call => call.seq !== currentCallSeq)
930
- const { executableCalls, results, found } = timeline
931
- const visibleCalls = options?.visibleCallSeqs
932
- let unavailableSeq = timeline.unavailableResultSeq
933
- let unavailableReason = 'unavailable or malformed dsh-ptc-plus journal result'
934
- for (const [resultSeq, result] of results.entries()) {
935
- if (result?.error !== undefined && Number.isSafeInteger(resultSeq)) {
936
- unavailableSeq ??= resultSeq
937
- unavailableReason = result.error
938
- }
939
- }
940
- const extraBoundaries = options?.extraBoundaries ?? []
941
- let normalizedExtraBoundaries = []
942
- try {
943
- normalizedExtraBoundaries = normalizeRecoveryBoundaries(extraBoundaries, undefined)
944
- } catch {
945
- normalizedExtraBoundaries = []
946
- }
947
- const boundaries = [
948
- ...timeline.boundaries,
949
- ...normalizedExtraBoundaries.map(boundary => ({
950
- ...boundary,
951
- eventSeq: Number.POSITIVE_INFINITY,
952
- })),
953
- ].sort((left, right) => left.eventSeq - right.eventSeq)
954
- // An unavailable historical result contracts the replay frontier. It must
955
- // not prevent the current request from executing on the verified prefix.
956
-
957
- const confirmedNoops = new Set()
958
- const invalidResultReasons = new Map()
959
- for (const [resultSeq, { journal, eventIndex }] of results.entries()) {
960
- const candidateConfirms = []
961
- for (const callSeq of journal?.confirms ?? []) {
962
- const confirmed = executableCalls.get(callSeq)
963
- if (confirmed === undefined || confirmed.eventIndex >= eventIndex || results.has(callSeq)) {
964
- const reason = `confirmed no-op does not identify an earlier unjournaled run_code call seq ${callSeq}`
965
- invalidResultReasons.set(resultSeq, reason)
966
- unavailableSeq ??= resultSeq
967
- unavailableReason = reason
968
- break
969
- }
970
- candidateConfirms.push(callSeq)
971
- }
972
- if (!invalidResultReasons.has(resultSeq)) {
973
- for (const callSeq of candidateConfirms) confirmedNoops.add(callSeq)
974
- }
975
- }
976
- const records = []
977
- const orderedRecords = []
978
- const invalidCallSeqs = new Set()
979
- let state = foldRecords(records, invalidCallSeqs)
980
- let blockedUnavailableSeq = unavailableSeq
981
- let boundaryIndex = 0
982
- const applyBoundariesBefore = (seq) => {
983
- while (boundaryIndex < boundaries.length && boundaries[boundaryIndex].eventSeq <= seq) {
984
- const boundary = boundaries[boundaryIndex++]
985
- const failedCall = executableCalls.get(boundary.failedCallSeq)
986
- if (failedCall === undefined || failedCall.event.seq >= boundary.eventSeq) continue
987
- const failedIndex = state.nodes.findIndex(node => node.callSeq === boundary.failedCallSeq)
988
- const expectedFrontier = failedIndex < 0 ? state.head : state.nodes[failedIndex].parent
989
- const frontierIndex = boundary.frontierCallSeq === null
990
- ? undefined
991
- : state.nodes.findIndex(node => node.callSeq === boundary.frontierCallSeq)
992
- if (frontierIndex !== expectedFrontier) continue
993
- const resolvesUnavailable = blockedUnavailableSeq === boundary.failedCallSeq
994
- if (failedIndex < 0 && !resolvesUnavailable) continue
995
- for (let index = 0; index < state.nodes.length; index += 1) {
996
- if (dependsOn(state.nodes, index, failedIndex)) invalidCallSeqs.add(state.nodes[index].callSeq)
997
- }
998
- if (failedIndex < 0) {
999
- for (const record of records) {
1000
- if (record.call.seq >= boundary.failedCallSeq) invalidCallSeqs.add(record.call.seq)
1001
- }
1002
- }
1003
- state = foldRecords(records, invalidCallSeqs, {
1004
- surfaceContracted: resolvesUnavailable ? false : state.surfaceContracted,
1005
- })
1006
- if (resolvesUnavailable) {
1007
- blockedUnavailableSeq = undefined
1008
- if (unavailableSeq === boundary.failedCallSeq) unavailableSeq = undefined
1009
- }
1010
- }
1011
- }
1012
- for (const call of calls) {
1013
- const result = results.get(call.seq)
1014
- const code = call.data?.name === 'edit_run_code' ? result?.derived?.code : sourceForRunCall(call)
1015
- const record = {
1016
- call,
1017
- code,
1018
- result: invalidResultReasons.has(call.seq)
1019
- ? { ...result, journal: undefined, error: invalidResultReasons.get(call.seq) }
1020
- : result,
1021
- hidden: visibleCalls instanceof Set && !visibleCalls.has(call.seq),
1022
- }
1023
- if (call.data?.name === 'edit_run_code' && record.code === undefined && result === undefined) continue
1024
- if (result?.journal === undefined && confirmedNoops.has(call.seq)) continue
1025
- orderedRecords.push(record)
1026
- }
1027
- orderedRecords.sort((left, right) => (
1028
- recordEventSeq(left) - recordEventSeq(right) || left.call.seq - right.call.seq
1029
- ))
1030
- for (const record of orderedRecords) {
1031
- applyBoundariesBefore(recordEventSeq(record))
1032
- records.push(record)
1033
- if (record.hidden) {
1034
- blockedUnavailableSeq ??= record.call.seq
1035
- unavailableSeq ??= record.call.seq
1036
- unavailableReason = 'model-visible provenance was shadowed'
1037
- invalidCallSeqs.add(record.call.seq)
1038
- state.surfaceContracted = true
1039
- state.trusted = false
1040
- state.volatileSuffix.push({ seq: record.call.seq, code: undefined, reason: unavailableReason })
1041
- continue
1042
- }
1043
- if (blockedUnavailableSeq !== undefined && record.call.seq >= blockedUnavailableSeq) {
1044
- invalidCallSeqs.add(record.call.seq)
1045
- if (!state.volatileSuffix.some(item => item.seq === record.call.seq)) {
1046
- state.trusted = false
1047
- state.volatileSuffix.push({
1048
- seq: record.call.seq,
1049
- code: record.code,
1050
- reason: unavailableReason,
1051
- })
1052
- }
1053
- continue
1054
- }
1055
- if (record.result?.journal === undefined) {
1056
- blockedUnavailableSeq ??= record.call.seq
1057
- unavailableSeq ??= record.call.seq
1058
- unavailableReason = record.result?.error ?? 'missing dsh-ptc-plus journal result'
1059
- invalidCallSeqs.add(record.call.seq)
1060
- state.trusted = false
1061
- state.volatileSuffix.push({ seq: record.call.seq, code: record.code, reason: unavailableReason })
1062
- continue
1063
- }
1064
- try {
1065
- applyRecord(state, record, invalidCallSeqs)
1066
- } catch (error) {
1067
- unavailableSeq ??= record.call.seq
1068
- blockedUnavailableSeq ??= record.call.seq
1069
- unavailableReason = error.message
1070
- invalidCallSeqs.add(record.call.seq)
1071
- }
1072
- }
1073
- applyBoundariesBefore(Number.POSITIVE_INFINITY)
1074
- if (unavailableSeq !== undefined
1075
- && !state.volatileSuffix.some(item => item.seq === unavailableSeq)) {
1076
- state.trusted = false
1077
- state.volatileSuffix.push({
1078
- seq: unavailableSeq,
1079
- code: undefined,
1080
- reason: unavailableReason,
1081
- })
1082
- }
1083
- return {
1084
- nodes: state.nodes,
1085
- head: state.head,
1086
- checkpoints: state.checkpoints,
1087
- volatileSuffix: state.volatileSuffix,
1088
- available: found && !state.surfaceContracted && blockedUnavailableSeq === undefined,
1089
- }
1090
- }
1091
-
1092
- /** Return source nodes from the empty state to a selected durable head. */
1093
- export function pathToHead(state) {
1094
- const path = []
1095
- for (let cursor = state.head; cursor !== undefined;) {
1096
- const node = state.nodes[cursor]
1097
- if (node === undefined) throw new Error('invalid dsh-ptc-plus journal head')
1098
- path.push(node)
1099
- cursor = node.parent
1100
- }
1101
- path.reverse()
1102
- return path
1103
- }
1104
-
1105
653
  /** Merge the journal into the tool result's private metadata. */
1106
654
  export function withJournal(meta, journal) {
1107
655
  const base = isRecord(meta) ? { ...meta } : meta === undefined ? {} : { value: cloneJson(meta) }
@@ -1109,9 +657,6 @@ export function withJournal(meta, journal) {
1109
657
  return base
1110
658
  }
1111
659
 
1112
- const REWRITE_FIELDS = new Set(['kind', 'description', 'source'])
1113
- const REWRITE_KINDS = new Set(['import', 'redeclaration', 'export'])
1114
-
1115
660
  /** Validate and detach one rewrite record emitted by the runtime. */
1116
661
  export function normalizeRewrites(value) {
1117
662
  if (!Array.isArray(value)) throw new Error('invalid dsh-ptc-plus rewrites')