dsh-ptc-plus 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.en.md +65 -78
  2. package/README.md +65 -78
  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 +30596 -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 +554 -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 +6 -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 +73 -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 +25 -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 +72 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +755 -0
  58. package/docs/user-bindings.en.md +64 -0
  59. package/docs/user-bindings.md +64 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification.md +57 -0
  62. package/index.js +369 -92
  63. package/internal/ast-traversal.js +5 -2
  64. package/internal/binding-descriptors.js +1 -0
  65. package/internal/binding-pattern-traversal.js +25 -0
  66. package/internal/binding-pattern.js +26 -29
  67. package/internal/binding-source-analysis.js +391 -0
  68. package/internal/binding-update-policy.js +66 -0
  69. package/internal/bounded-worker-round.js +56 -0
  70. package/internal/callable-definition-environment.js +42 -0
  71. package/internal/callable-source-catalog.js +129 -0
  72. package/internal/callable-source-encoding.js +47 -0
  73. package/internal/callable-source-facts.js +459 -0
  74. package/internal/callable-source-format.js +2 -0
  75. package/internal/callable-source-lowering.js +123 -0
  76. package/internal/cell-analysis-contract.js +25 -0
  77. package/internal/cell-analysis.js +240 -290
  78. package/internal/cell-completion.js +15 -0
  79. package/internal/cell-error.js +7 -0
  80. package/internal/cell-parser.js +66 -0
  81. package/internal/cell-rewriter.js +3 -677
  82. package/internal/commonjs-export-evidence.js +63 -0
  83. package/internal/commonjs-source-evidence.js +72 -0
  84. package/internal/compiler-amaro.cjs +2 -0
  85. package/internal/compiler-compression.js +12 -0
  86. package/internal/compiler-data.js +58 -0
  87. package/internal/compiler-descriptors.js +54 -0
  88. package/internal/compiler-dynamic-cache.js +48 -0
  89. package/internal/compiler-entry.js +35 -0
  90. package/internal/compiler-generated-names.js +17 -0
  91. package/internal/compiler-intrinsics.js +84 -0
  92. package/internal/compiler-module-links.js +34 -0
  93. package/internal/compiler-operations.js +61 -0
  94. package/internal/compiler-platform-factory.js +84 -0
  95. package/internal/compiler-platform.js +136 -0
  96. package/internal/compiler-record-roles.js +53 -0
  97. package/internal/compiler-region-output.js +259 -0
  98. package/internal/compiler-resource-lowering.js +103 -0
  99. package/internal/compiler-scope-facts.js +115 -0
  100. package/internal/compiler-service.js +109 -0
  101. package/internal/compiler-source-cache.js +32 -0
  102. package/internal/compiler-source-pieces.js +93 -0
  103. package/internal/compiler-source-regions.js +132 -0
  104. package/internal/compiler-storage-source.js +54 -0
  105. package/internal/compiler-text.js +15 -0
  106. package/internal/compiler-typescript.cjs +2 -0
  107. package/internal/config-spec.js +83 -27
  108. package/internal/cordis-tools-owner.js +130 -21
  109. package/internal/diagnostic.js +27 -1
  110. package/internal/direct-surface-owner.js +76 -23
  111. package/internal/dynamic-binding-evidence.js +16 -0
  112. package/internal/dynamic-environment-compiler.js +779 -0
  113. package/internal/dynamic-environment-integration.js +87 -0
  114. package/internal/dynamic-environment-runtime.js +362 -0
  115. package/internal/dynamic-native-calls.js +150 -0
  116. package/internal/dynamic-native-runtime.js +248 -0
  117. package/internal/dynamic-scope-analysis.js +90 -0
  118. package/internal/edit-transport-owner.js +27 -2
  119. package/internal/execution-seam-compat.js +193 -0
  120. package/internal/failure-reporting.js +118 -18
  121. package/internal/host-rpc.js +66 -0
  122. package/internal/isolated-worker.js +548 -0
  123. package/internal/kernel-child.js +139 -0
  124. package/internal/kernel-worker.js +904 -120
  125. package/internal/language-semantics.js +14 -0
  126. package/internal/legacy-binding-catalog.js +48 -0
  127. package/internal/legacy-cell-analysis.js +600 -0
  128. package/internal/legacy-cell-parser.js +50 -0
  129. package/internal/legacy-cell-rewriter.js +631 -0
  130. package/internal/legacy-repl-convenience.js +291 -0
  131. package/internal/managed-module-operations.js +170 -0
  132. package/internal/module-compilation.js +321 -0
  133. package/internal/module-runtime-platform.js +19 -0
  134. package/internal/module-transform-contract.js +12 -0
  135. package/internal/native-await-declarations.js +68 -0
  136. package/internal/native-root-compilation.js +54 -0
  137. package/internal/native-root-dynamic.js +67 -0
  138. package/internal/recovery-tips.js +27 -15
  139. package/internal/rejected-cell-editor.js +13 -8
  140. package/internal/repl-convenience.js +2 -289
  141. package/internal/repl-memory-projection.js +81 -12
  142. package/internal/repl-observation-interest.js +70 -0
  143. package/internal/repl-rewrite-contract.js +6 -0
  144. package/internal/repl-scope-normalizer.js +2091 -0
  145. package/internal/repl-value-observer.js +114 -0
  146. package/internal/rpc-contract.js +52 -0
  147. package/internal/runtime-bridge-owner.js +172 -28
  148. package/internal/runtime-config.js +48 -52
  149. package/internal/runtime-contexts.js +82 -10
  150. package/internal/runtime-messages.js +111 -0
  151. package/internal/session-cell-executor.js +401 -87
  152. package/internal/session-journal-recovery.js +684 -0
  153. package/internal/session-journal-schema.js +106 -0
  154. package/internal/session-journal.js +184 -654
  155. package/internal/session-log-view.js +42 -9
  156. package/internal/session-runtime.js +256 -95
  157. package/internal/session-state.js +247 -71
  158. package/internal/source-position-map.js +211 -71
  159. package/internal/stateful-module-compiler.js +154 -0
  160. package/internal/stateful-module-runtime.js +303 -0
  161. package/internal/stateful-root-compiler.js +598 -0
  162. package/internal/stateful-root-runtime.js +340 -0
  163. package/internal/typert-codec-compat.js +28 -0
  164. package/internal/typescript-transform.js +29 -0
  165. package/internal/user-binding-authoring.js +70 -0
  166. package/internal/user-binding-console-worker.js +184 -0
  167. package/internal/user-binding-console.js +148 -0
  168. package/internal/user-binding-draft-projection.js +335 -0
  169. package/internal/user-binding-model-context.js +31 -0
  170. package/internal/user-binding-runner.js +56 -0
  171. package/internal/user-bindings-owner.js +1065 -0
  172. package/internal/user-bindings-store.js +293 -0
  173. package/internal/user-bindings.js +353 -0
  174. package/internal/value-wire-schema.js +28 -0
  175. package/internal/value-wire.js +29 -20
  176. package/internal/worker-client.js +57 -67
  177. package/internal/worker-environment.js +45 -0
  178. package/internal/worker-output-relay.js +29 -0
  179. package/internal/worker-shutdown.js +14 -0
  180. package/package.json +75 -7
  181. package/src/client-activity.js +88 -53
  182. package/src/client-authoring-view.js +580 -0
  183. package/src/client-binding-review.js +230 -0
  184. package/src/client-bindings-data.js +29 -0
  185. package/src/client-catalog.js +127 -0
  186. package/src/client-code-editor.js +84 -0
  187. package/src/client-console.js +112 -0
  188. package/src/client-copy.js +370 -0
  189. package/src/client-feature-gates.js +62 -0
  190. package/src/client-host-compat.js +150 -0
  191. package/src/client-indicator-view.js +267 -0
  192. package/src/client-repl-view.js +292 -0
  193. package/src/client-rpc.js +20 -0
  194. package/src/client-settings-view.js +221 -0
  195. package/src/client-styles.js +106 -0
  196. package/src/client-tool-view.js +108 -0
  197. package/src/client-transport.js +159 -0
  198. package/src/client-workbench.js +521 -0
  199. package/src/client.js +233 -615
@@ -2,42 +2,72 @@ 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'
5
6
  import {
6
7
  LEGACY_DEFAULT_EXPORT_BINDING,
7
- LIVE_DEFAULT_EXPORT_BINDING,
8
+ LEGACY_IMPORT_EXPRESSION_BOUNDARY,
9
+ LIVE_MODULE_SEMANTICS,
8
10
  } 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'])
11
+ import { userBindingsSnapshotFromMeta } from './user-bindings.js'
12
+ import {
13
+ JOURNAL_KEY,
14
+ EDIT_TARGET_KEY,
15
+ DERIVED_RUN_KEY,
16
+ REWRITES_KEY,
17
+ RECOVERY_BOUNDARY_KEY,
18
+ JOURNAL_VERSION,
19
+ IMPORT_BOUNDARY_JOURNAL_VERSION,
20
+ PER_NAME_USER_BINDINGS_JOURNAL_VERSION,
21
+ PER_NAME_JOURNAL_FIELDS,
22
+ WHOLE_ENTRY_JOURNAL_FIELDS,
23
+ LIVE_USER_BINDINGS_SHADOW_POLICY,
24
+ LEGACY_USER_BINDINGS_SHADOW_POLICY,
25
+ normalizeJournalUserBindingNames,
26
+ LIVE_USER_BINDINGS_REUSE_POLICY,
27
+ LEGACY_USER_BINDINGS_REUSE_POLICY,
28
+ LEGACY_JOURNAL_VERSION,
29
+ USER_BINDING_RELATIONLESS_JOURNAL_VERSION,
30
+ FINGERPRINT_REUSE_JOURNAL_VERSION,
31
+ VERSIONED_BINDING_REUSE_JOURNAL_VERSION,
32
+ RECOVERY_BOUNDARY_EVENT,
33
+ STATUSES,
34
+ BINDING_MODES,
35
+ JOURNAL_FIELDS,
36
+ FINGERPRINT_REUSE_JOURNAL_FIELDS,
37
+ RELATIONLESS_JOURNAL_FIELDS,
38
+ PREDECESSOR_JOURNAL_FIELDS,
39
+ LEGACY_JOURNAL_FIELDS,
40
+ BINDING_POLICY_FIELDS,
41
+ REWRITE_POLICY_FIELDS,
42
+ MODULE_SEMANTICS_FIELDS,
43
+ LEGACY_MODULE_SEMANTICS_FIELDS,
44
+ IMPORT_EXPRESSION_BOUNDARIES,
45
+ DEFAULT_EXPORT_BINDINGS,
46
+ CALL_SUCCESS_FIELDS,
47
+ CALL_ERROR_FIELDS,
48
+ OPERATION_FIELDS,
49
+ RETURN_FIELDS,
50
+ THROW_FIELDS,
51
+ ERROR_FIELDS,
52
+ EDIT_TARGET_FIELDS,
53
+ DERIVED_RUN_FIELDS,
54
+ JOURNAL_VERSIONS,
55
+ USER_BINDINGS_REUSE_POLICIES,
56
+ RECOVERY_BOUNDARY_FIELDS,
57
+ REWRITE_FIELDS,
58
+ REWRITE_KINDS,
59
+ } from './session-journal-schema.js'
60
+
61
+ export {
62
+ JOURNAL_KEY,
63
+ EDIT_TARGET_KEY,
64
+ DERIVED_RUN_KEY,
65
+ REWRITES_KEY,
66
+ RECOVERY_BOUNDARY_KEY,
67
+ JOURNAL_VERSION,
68
+ LIVE_USER_BINDINGS_REUSE_POLICY,
69
+ RECOVERY_BOUNDARY_EVENT,
70
+ } from './session-journal-schema.js'
41
71
 
42
72
  function cloneJson(value) {
43
73
  if (value === undefined) return undefined
@@ -197,11 +227,65 @@ function normalizeModuleSemantics(value) {
197
227
  if (!DEFAULT_EXPORT_BINDINGS.has(value.defaultExportBinding)) {
198
228
  throw new Error('invalid dsh-ptc-plus journal default export binding semantics')
199
229
  }
200
- return Object.freeze({ defaultExportBinding: value.defaultExportBinding })
230
+ if (!IMPORT_EXPRESSION_BOUNDARIES.has(value.importExpressionBoundary)) {
231
+ throw new Error('invalid dsh-ptc-plus journal import expression boundary semantics')
232
+ }
233
+ return Object.freeze({
234
+ defaultExportBinding: value.defaultExportBinding,
235
+ importExpressionBoundary: value.importExpressionBoundary,
236
+ })
237
+ }
238
+
239
+ function migrateLegacyModuleSemantics(value) {
240
+ if (!isRecord(value)) throw new Error('invalid dsh-ptc-plus journal module semantics')
241
+ assertOwnFields(value, LEGACY_MODULE_SEMANTICS_FIELDS, 'journal module semantics')
242
+ return { ...value, importExpressionBoundary: LEGACY_IMPORT_EXPRESSION_BOUNDARY }
243
+ }
244
+
245
+ function normalizeUserBindingsFingerprint(value) {
246
+ if (value !== null && (typeof value !== 'string' || !/^[a-f0-9]{64}$/.test(value))) {
247
+ throw new Error('invalid dsh-ptc-plus journal user binding fingerprint')
248
+ }
249
+ return value
201
250
  }
202
251
 
203
252
  function migrateJournal(value, resolveLegacyConfirm) {
204
253
  if (value.version === JOURNAL_VERSION) return value
254
+ if (value.version === PER_NAME_USER_BINDINGS_JOURNAL_VERSION) {
255
+ assertOwnFields(value, PER_NAME_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
256
+ return { ...value, version: JOURNAL_VERSION, languageSemantics: LEGACY_LANGUAGE_SEMANTICS }
257
+ }
258
+ const shadow = { userBindingsShadowPolicy: LEGACY_USER_BINDINGS_SHADOW_POLICY, userBindingNames: null,
259
+ languageSemantics: LEGACY_LANGUAGE_SEMANTICS }
260
+ if (value.version === IMPORT_BOUNDARY_JOURNAL_VERSION) {
261
+ assertOwnFields(value, WHOLE_ENTRY_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
262
+ return { ...value, version: JOURNAL_VERSION, ...shadow }
263
+ }
264
+ if (value.version === VERSIONED_BINDING_REUSE_JOURNAL_VERSION) {
265
+ assertOwnFields(value, WHOLE_ENTRY_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
266
+ return { ...value, version: JOURNAL_VERSION, ...shadow, moduleSemantics: migrateLegacyModuleSemantics(value.moduleSemantics) }
267
+ }
268
+ if (value.version === FINGERPRINT_REUSE_JOURNAL_VERSION) {
269
+ assertOwnFields(value, FINGERPRINT_REUSE_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
270
+ return {
271
+ ...value,
272
+ version: JOURNAL_VERSION,
273
+ moduleSemantics: migrateLegacyModuleSemantics(value.moduleSemantics),
274
+ userBindingsReusePolicy: LEGACY_USER_BINDINGS_REUSE_POLICY,
275
+ ...shadow,
276
+ }
277
+ }
278
+ if (value.version === USER_BINDING_RELATIONLESS_JOURNAL_VERSION) {
279
+ assertOwnFields(value, RELATIONLESS_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
280
+ return {
281
+ ...value,
282
+ version: JOURNAL_VERSION,
283
+ moduleSemantics: migrateLegacyModuleSemantics(value.moduleSemantics),
284
+ userBindingsFingerprint: null,
285
+ userBindingsReusePolicy: LEGACY_USER_BINDINGS_REUSE_POLICY,
286
+ ...shadow,
287
+ }
288
+ }
205
289
  const legacy = value.version === LEGACY_JOURNAL_VERSION
206
290
  assertOwnFields(
207
291
  value,
@@ -218,7 +302,13 @@ function migrateJournal(value, resolveLegacyConfirm) {
218
302
  functionClassRedeclarations: false,
219
303
  },
220
304
  rewritePolicy: legacy ? LEGACY_REWRITE_POLICY : value.rewritePolicy,
221
- moduleSemantics: { defaultExportBinding: LEGACY_DEFAULT_EXPORT_BINDING },
305
+ moduleSemantics: {
306
+ defaultExportBinding: LEGACY_DEFAULT_EXPORT_BINDING,
307
+ importExpressionBoundary: LEGACY_IMPORT_EXPRESSION_BOUNDARY,
308
+ },
309
+ userBindingsFingerprint: null,
310
+ userBindingsReusePolicy: LEGACY_USER_BINDINGS_REUSE_POLICY,
311
+ ...shadow,
222
312
  confirms: legacy
223
313
  ? normalizeLegacyConfirms(value.confirms, resolveLegacyConfirm)
224
314
  : value.confirms,
@@ -228,15 +318,22 @@ function migrateJournal(value, resolveLegacyConfirm) {
228
318
  /** Validate and detach one journal emitted by the runtime. */
229
319
  export function normalizeJournal(value, options = {}) {
230
320
  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)
321
+ if (!JOURNAL_VERSIONS.has(value.version)
232
322
  || !STATUSES.has(value.status)) {
233
323
  throw new Error('invalid dsh-ptc-plus journal')
234
324
  }
235
325
  const migrated = migrateJournal(value, options.resolveLegacyConfirm)
236
326
  assertOwnFields(migrated, JOURNAL_FIELDS, 'dsh-ptc-plus journal')
327
+ const languageSemantics = normalizeLanguageSemantics(migrated.languageSemantics)
237
328
  const bindingPolicy = normalizeBindingPolicy(migrated.bindingPolicy)
238
329
  const rewritePolicy = normalizeRewritePolicy(migrated.rewritePolicy)
239
330
  const moduleSemantics = normalizeModuleSemantics(migrated.moduleSemantics)
331
+ const userBindingsFingerprint = normalizeUserBindingsFingerprint(migrated.userBindingsFingerprint)
332
+ const userBindingsReusePolicy = migrated.userBindingsReusePolicy
333
+ const userBindingNames = normalizeJournalUserBindingNames(migrated)
334
+ if (!USER_BINDINGS_REUSE_POLICIES.has(userBindingsReusePolicy)) {
335
+ throw new Error('invalid dsh-ptc-plus journal user binding reuse policy')
336
+ }
240
337
  const calls = normalizeCalls(migrated.calls)
241
338
  const operations = normalizeOperations(migrated.operations)
242
339
  const confirms = normalizeConfirms(migrated.confirms)
@@ -257,9 +354,14 @@ export function normalizeJournal(value, options = {}) {
257
354
  }
258
355
  return Object.freeze({
259
356
  version: JOURNAL_VERSION,
357
+ languageSemantics,
260
358
  bindingPolicy,
261
359
  rewritePolicy,
262
360
  moduleSemantics,
361
+ userBindingsFingerprint,
362
+ userBindingsReusePolicy,
363
+ userBindingsShadowPolicy: migrated.userBindingsShadowPolicy,
364
+ userBindingNames,
263
365
  status: migrated.status,
264
366
  calls: Object.freeze(calls),
265
367
  operations: Object.freeze(operations),
@@ -292,15 +394,53 @@ export function normalizeDerivedEditResult(meta, expectedTargetCallSeq) {
292
394
  const recoveryBoundaries = meta[RECOVERY_BOUNDARY_KEY] === undefined
293
395
  ? undefined
294
396
  : normalizeRecoveryBoundaries(meta[RECOVERY_BOUNDARY_KEY])
397
+ const userBindings = userBindingsForJournal(meta, journal)
295
398
  return Object.freeze({
296
399
  targetCallSeq: target.targetCallSeq,
297
400
  code: derived.code,
298
401
  description: derived.description,
299
402
  journal,
300
403
  ...(recoveryBoundaries === undefined ? {} : { recoveryBoundaries }),
404
+ ...(userBindings === undefined ? {} : { userBindings }),
301
405
  })
302
406
  }
303
407
 
408
+ export function userBindingsForJournal(meta, journal) {
409
+ const userBindings = userBindingsSnapshotFromMeta(meta)
410
+ if (journal.userBindingsFingerprint === null) {
411
+ if (userBindings !== undefined) {
412
+ throw new Error('dsh-ptc-plus journal does not declare user binding metadata')
413
+ }
414
+ validateUserBindingNameProviders(journal, undefined)
415
+ return undefined
416
+ }
417
+ if (userBindings === undefined) {
418
+ throw new Error('dsh-ptc-plus journal requires user binding metadata')
419
+ }
420
+ if (userBindings.fingerprint !== journal.userBindingsFingerprint) {
421
+ throw new Error('dsh-ptc-plus journal user binding fingerprint does not match metadata')
422
+ }
423
+ validateUserBindingNameProviders(journal, userBindings)
424
+ return userBindings
425
+ }
426
+
427
+ function validateUserBindingNameProviders(journal, snapshot) {
428
+ const providers = new Map((snapshot?.entries ?? []).flatMap(entry => (
429
+ (entry.scope === 'namespace' ? [entry.name] : entry.symbols).map(name => [name, entry.id])
430
+ )))
431
+ for (const fact of journal.userBindingNames ?? []) {
432
+ if (fact.state === 'provider' && providers.get(fact.name) !== fact.entryId) {
433
+ throw new Error('journal user binding name evidence has no matching provider')
434
+ }
435
+ }
436
+ if (journal.userBindingNames !== null) {
437
+ const names = new Set(journal.userBindingNames.map(fact => fact.name))
438
+ if ([...providers.keys()].some(name => !names.has(name))) {
439
+ throw new Error('journal user binding name evidence is incomplete')
440
+ }
441
+ }
442
+ }
443
+
304
444
  /** Compare only the required persisted relation for one derived edit. */
305
445
  export function derivedEditResultsEqual(leftMeta, rightMeta, expectedTargetCallSeq) {
306
446
  try {
@@ -341,7 +481,7 @@ function normalizeRecoveryBoundaryValue(value, eventSeq = undefined) {
341
481
  && (!Number.isSafeInteger(value.frontierCallSeq) || value.frontierCallSeq < 0))) {
342
482
  throw new Error('invalid dsh-ptc-plus recovery boundary')
343
483
  }
344
- assertOwnFields(value, new Set(['failedCallSeq', 'frontierCallSeq']), 'recovery boundary')
484
+ assertOwnFields(value, RECOVERY_BOUNDARY_FIELDS, 'recovery boundary')
345
485
  if (eventSeq !== undefined && (!Number.isSafeInteger(eventSeq) || eventSeq < 0)) {
346
486
  throw new Error('invalid dsh-ptc-plus recovery boundary event sequence')
347
487
  }
@@ -443,7 +583,7 @@ export function liveToolCallSeq(session, callId, toolName) {
443
583
  }
444
584
 
445
585
  /** Start a mutable journal for one live cell. */
446
- export function createJournal(confirms = [], bindingPolicy, rewritePolicy) {
586
+ export function createJournal(confirms = [], bindingPolicy, rewritePolicy, languageSemantics = LEGACY_LANGUAGE_SEMANTICS) {
447
587
  if (typeof bindingPolicy === 'string') {
448
588
  if (!BINDING_MODES.has(bindingPolicy)) throw new TypeError('invalid dsh-ptc-plus journal binding mode')
449
589
  bindingPolicy = {
@@ -453,11 +593,14 @@ export function createJournal(confirms = [], bindingPolicy, rewritePolicy) {
453
593
  }
454
594
  return {
455
595
  version: JOURNAL_VERSION,
596
+ languageSemantics: normalizeLanguageSemantics(languageSemantics),
456
597
  bindingPolicy: normalizeBindingPolicy(bindingPolicy),
457
598
  rewritePolicy: normalizeRewritePolicy(rewritePolicy),
458
- moduleSemantics: normalizeModuleSemantics({
459
- defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING,
460
- }),
599
+ moduleSemantics: normalizeModuleSemantics(LIVE_MODULE_SEMANTICS),
600
+ userBindingsFingerprint: null,
601
+ userBindingsReusePolicy: LIVE_USER_BINDINGS_REUSE_POLICY,
602
+ userBindingsShadowPolicy: LIVE_USER_BINDINGS_SHADOW_POLICY,
603
+ userBindingNames: null,
461
604
  calls: [],
462
605
  operations: [],
463
606
  confirms: [...confirms],
@@ -465,15 +608,6 @@ export function createJournal(confirms = [], bindingPolicy, rewritePolicy) {
465
608
  }
466
609
  }
467
610
 
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
611
  /** Return an owned state transition; reject volatile saves and unknown named restores. */
478
612
  export function reduceStateOperations({ nodes, head, checkpoints }, operations, nodeIndex) {
479
613
  const nextCheckpoints = new Map(checkpoints)
@@ -501,607 +635,6 @@ export function reduceStateOperations({ nodes, head, checkpoints }, operations,
501
635
  return { head: nextHead, checkpoints: nextCheckpoints, restored }
502
636
  }
503
637
 
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
638
  /** Merge the journal into the tool result's private metadata. */
1106
639
  export function withJournal(meta, journal) {
1107
640
  const base = isRecord(meta) ? { ...meta } : meta === undefined ? {} : { value: cloneJson(meta) }
@@ -1109,9 +642,6 @@ export function withJournal(meta, journal) {
1109
642
  return base
1110
643
  }
1111
644
 
1112
- const REWRITE_FIELDS = new Set(['kind', 'description', 'source'])
1113
- const REWRITE_KINDS = new Set(['import', 'redeclaration', 'export'])
1114
-
1115
645
  /** Validate and detach one rewrite record emitted by the runtime. */
1116
646
  export function normalizeRewrites(value) {
1117
647
  if (!Array.isArray(value)) throw new Error('invalid dsh-ptc-plus rewrites')