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
@@ -1,5 +1,9 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks'
2
- import { registerHooks } from 'node:module'
1
+ import { create as createDomain } from 'node:domain'
2
+ import { createRequire, registerHooks } from 'node:module'
3
+ import { managedModuleImport, managedRequire, readModuleImport, statefulModuleLink } from './stateful-module-runtime.js'
4
+ import { staticModuleLinkReference } from './compiler-module-links.js'
5
+ import { compileStatefulModule, createUserModuleCompilationHooks } from './stateful-module-compiler.js'
6
+ import { USER_BINDING_TRANSFORM, LEGACY_USER_BINDING_TRANSFORM } from './module-transform-contract.js'
3
7
  import { isAbsolute, resolve } from 'node:path'
4
8
  import repl from 'node:repl'
5
9
  import { PassThrough } from 'node:stream'
@@ -7,10 +11,90 @@ import { pathToFileURL } from 'node:url'
7
11
  import { formatWithOptions } from 'node:util'
8
12
  import { MessageChannel, parentPort, workerData } from 'node:worker_threads'
9
13
  import { synchronizeBuiltinEsmExports } from './builtin-esm-sync.js'
10
- import { errorDetails, messageOf } from './failure-reporting.js'
11
- import { AMBIENT_GLOBALS, DURABLE_IMPORTS, FORBIDDEN_IMPORTS } from './module-policy.js'
14
+ import { createPrivateAsyncLocalStorage } from './async-local-storage-intrinsics.js'
15
+ import { createExceptionOriginScope, errorDetails, messageOf, programBindingError } from './failure-reporting.js'
16
+ import { DURABLE_IMPORTS, FORBIDDEN_IMPORTS } from './module-policy.js'
12
17
  import { decodeValue, encodeValue } from './value-wire.js'
18
+ import { LEGACY_USER_BINDINGS_REUSE_POLICY, LIVE_USER_BINDINGS_SHADOW_POLICY, normalizeUserBindingNames } from './session-journal-schema.js'
13
19
  import { installWorkerCwdVirtualization } from './worker-cwd-virtualization.js'
20
+ import { createReplValueObserver, supportsAwaitLexicals, previewBindingValue } from './repl-value-observer.js'
21
+ import { createStatefulRootRuntime } from './stateful-root-runtime.js'
22
+ import { createNativeRootDynamic } from './native-root-dynamic.js'
23
+ import { moduleRuntimeIntrinsics } from './compiler-intrinsics.js'
24
+ import { compilerDescriptors } from './compiler-descriptors.js'
25
+ import { createCellCompletionObserver } from './cell-completion.js'
26
+ import { WORKER_SHUTDOWN_ACKNOWLEDGEMENT, WORKER_SHUTDOWN_REQUEST } from './worker-shutdown.js'
27
+ import { WORKER_REPL_OPTIONS, createWorkerControlPromise, createWorkerReplErrorHandler,
28
+ createWorkerUncaughtExceptionHandler,
29
+ disableWorkerReplDomain,
30
+ captureWorkerReplGlobals, protectWorkerReplAsyncContext, restoreWorkerReplGlobals, runInWorkerReplRealm,
31
+ workerReplContext } from './worker-repl-realm.js'
32
+ import { installProgramAmbientResolver } from './dynamic-environment-runtime.js'
33
+
34
+ const { Object, Map, Set, Proxy, Error,
35
+ mapGet, mapSet, mapHas, mapDelete, mapClear, mapSize, mapForEach,
36
+ setHas, setAdd, setDelete, setClear, setForEach,
37
+ everyArray, someArray, appendArray, copyArray, join, Reflect: privateReflect, promiseResolve, promiseReject } = moduleRuntimeIntrinsics
38
+ const PublicPromise = globalThis.Promise
39
+ const jsonStringify = globalThis.JSON.stringify
40
+ const bufferByteLength = globalThis.Buffer.byteLength
41
+ const String = globalThis.String
42
+ const process = globalThis.process
43
+ const ControlPromise = createWorkerControlPromise()
44
+ const hasProperty = privateReflect.has
45
+
46
+ function mapArray(values, visit) {
47
+ const result = []
48
+ for (let index = 0; index < values.length; index++) appendArray(result, visit(values[index], index))
49
+ return result
50
+ }
51
+
52
+ function filterArray(values, select) {
53
+ const result = []
54
+ for (let index = 0; index < values.length; index++) {
55
+ if (select(values[index], index)) appendArray(result, values[index])
56
+ }
57
+ return result
58
+ }
59
+
60
+ function mapFromArray(values, visit) {
61
+ const result = new Map()
62
+ for (let index = 0; index < values.length; index++) {
63
+ const entry = visit(values[index], index)
64
+ mapSet(result, entry[0], entry[1])
65
+ }
66
+ return result
67
+ }
68
+
69
+ function setFromArray(values) {
70
+ const result = new Set()
71
+ for (let index = 0; index < values.length; index++) setAdd(result, values[index])
72
+ return result
73
+ }
74
+
75
+ function mapKeysArray(value) {
76
+ const result = []
77
+ mapForEach(value, (_item, key) => appendArray(result, key))
78
+ return result
79
+ }
80
+
81
+ function mapEntriesArray(value) {
82
+ const result = []
83
+ mapForEach(value, (item, key) => appendArray(result, [key, item]))
84
+ return result
85
+ }
86
+
87
+ function mapValuesArray(value) {
88
+ const result = []
89
+ mapForEach(value, item => appendArray(result, item))
90
+ return result
91
+ }
92
+
93
+ function setValuesArray(value) {
94
+ const result = []
95
+ setForEach(value, item => appendArray(result, item))
96
+ return result
97
+ }
14
98
 
15
99
  if (parentPort === null) throw new Error('ptc-plus kernel worker started without a parent port')
16
100
  const { port1, port2: channel } = new MessageChannel()
@@ -18,35 +102,102 @@ const { port1, port2: channel } = new MessageChannel()
18
102
  const input = new PassThrough()
19
103
  const output = new PassThrough()
20
104
  output.resume()
105
+ const evaluationScope = createPrivateAsyncLocalStorage()
21
106
  const sessionCwd = typeof workerData?.cwd === 'string' ? workerData.cwd : undefined
22
107
  if (sessionCwd !== undefined && !isAbsolute(sessionCwd)) {
23
- throw new Error(`ptc-plus session cwd must be absolute, got ${JSON.stringify(sessionCwd)}`)
108
+ throw new Error(`ptc-plus session cwd must be absolute, got ${jsonStringify(sessionCwd)}`)
24
109
  }
110
+ const workerGlobalBaseline = captureWorkerReplGlobals()
25
111
  const server = repl.start({
26
112
  input,
27
113
  output,
28
114
  terminal: false,
29
115
  prompt: '',
30
- useGlobal: false,
116
+ ...WORKER_REPL_OPTIONS,
117
+ handleError: createWorkerReplErrorHandler(evaluationScope, (finish, error) => finish(true, error)),
31
118
  ignoreUndefined: true,
32
119
  })
33
- const context = server.context
120
+ protectWorkerReplAsyncContext(server)
121
+ const originalRequire = server.context.require
122
+ // domain.bind exposes its owner on a bound evaluator in older Node REPLs.
123
+ // Newer REPLs honor an explicitly entered domain. Capture before REPL error
124
+ // formatting can evaluate user-defined stack/name getters.
125
+ const errorDomain = disableWorkerReplDomain(server.eval.domain ?? createDomain())
126
+ errorDomain.removeAllListeners('error')
127
+ errorDomain.on('error', error => evaluationScope.getStore()?.(true, error))
128
+ const handleUncaughtException = createWorkerUncaughtExceptionHandler(evaluationScope, process)
129
+ process.on('uncaughtException', handleUncaughtException)
130
+ const context = workerReplContext(server)
131
+ restoreWorkerReplGlobals(workerGlobalBaseline, context)
132
+ const workerBaselineGlobals = setFromArray(mapKeysArray(workerGlobalBaseline))
133
+ const contextGlobal = runInWorkerReplRealm('globalThis')
134
+ const completionObserver = createCellCompletionObserver(runInWorkerReplRealm('Function'))
135
+ let replModuleRealm
136
+ const statefulRoots = createStatefulRootRuntime({
137
+ refresh: name => refreshLegacyPublication(name),
138
+ importModule: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm),
139
+ errors: runInWorkerReplRealm('({ ReferenceError, TypeError })'),
140
+ dynamicIntrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
141
+ readAmbient: name => runInWorkerReplRealm(name),
142
+ typeofAmbient: name => runInWorkerReplRealm(`typeof ${name}`),
143
+ canWriteAmbient: name => writableRootProperty(name),
144
+ writeAmbient(name, value, strict) {
145
+ const argument = name === '__ptc_value' ? '__ptc_other_value' : '__ptc_value'
146
+ runInWorkerReplRealm(`${strict ? '"use strict";' : ''}(${argument}) => (${name} = ${argument})`)(value)
147
+ },
148
+ deleteAmbient: name => runInWorkerReplRealm(`delete ${name}`),
149
+ hasAmbient: name => setHas(installedGlobals, name)
150
+ ? mapGet(installedGlobalOriginals, name).descriptor !== undefined : hasProperty(context, name),
151
+ // Request namespaces stay on globalThis; source declarations own separate
152
+ // logical storage. Provider overlays retain their existing write-through.
153
+ hasOverlay: name => setHas(installedGlobals, name) && mapGet(dynamicNamespaces, name)?.shadowable !== true,
154
+ publish(name) {
155
+ // Logical lexical identities do not redefine the global object's properties.
156
+ if (mapHas(userBindingSources, name)) recordUserBindingAssignment(name)
157
+ },
158
+ })
159
+ const nativeRootDynamic = createNativeRootDynamic({
160
+ logicalReference: (name, writable) => statefulRoots.legacyReference(name, writable),
161
+ intrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
162
+ importModule: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm),
163
+ read: name => runInWorkerReplRealm(name),
164
+ typeOf: name => runInWorkerReplRealm(`typeof ${name}`),
165
+ write(name, value, strict) {
166
+ const argument = name === '__ptc_value' ? '__ptc_other_value' : '__ptc_value'
167
+ runInWorkerReplRealm(`${strict ? '"use strict";' : ''}(${argument}) => (${name} = ${argument})`)(value)
168
+ },
169
+ remove: name => runInWorkerReplRealm(`delete ${name}`),
170
+ })
171
+ let valueObserver
34
172
  const REPL_IMPORT_CANARY = 'data:text/javascript,export default 1'
35
173
  let replParent
36
174
  const sessionReplParent = sessionCwd === undefined ? undefined : pathToFileURL(resolve(sessionCwd, 'repl')).href
37
175
  const staticAdapterParents = new Set()
176
+ const userBindingModuleParents = new Map()
177
+ const userModuleCompilation = createUserModuleCompilationHooks({
178
+ transformForParent: parent => (parent === replParent || parent === sessionReplParent) && activeExecution !== undefined
179
+ ? activeExecution.moduleTransform : undefined,
180
+ })
38
181
  registerHooks({
39
182
  resolve(specifier, context, nextResolve) {
40
183
  if (specifier === REPL_IMPORT_CANARY && replParent === undefined) replParent = context.parentURL
41
- return nextResolve(specifier, context.parentURL === replParent || staticAdapterParents.has(context.parentURL)
42
- ? { ...context, parentURL: sessionReplParent ?? replParent }
43
- : context)
184
+ return userModuleCompilation.resolve(specifier, context, (source, resolvedContext) => {
185
+ const userBindingParent = mapGet(userBindingModuleParents, resolvedContext.parentURL)
186
+ return nextResolve(source, userBindingParent !== undefined
187
+ ? { ...resolvedContext, parentURL: userBindingParent }
188
+ : resolvedContext.parentURL === replParent || setHas(staticAdapterParents, resolvedContext.parentURL)
189
+ ? { ...resolvedContext, parentURL: sessionReplParent ?? replParent }
190
+ : resolvedContext)
191
+ })
44
192
  },
193
+ load: userModuleCompilation.load,
45
194
  })
46
- const logScope = new AsyncLocalStorage()
195
+ const logScope = createPrivateAsyncLocalStorage()
196
+ const userBindingActivationScope = createPrivateAsyncLocalStorage()
47
197
  const pending = new Map()
48
198
  const installedGlobals = new Set()
49
- const PROCESS_CONTROLS = new Set(['exit', 'abort', 'kill', 'chdir'])
199
+ const installedGlobalOriginals = new Map()
200
+ const PROCESS_CONTROLS = setFromArray(['exit', 'abort', 'kill', 'chdir'])
50
201
  const CELL_FRAME_SUFFIX = '\n;'
51
202
  let filenameSequence = 0
52
203
  let activeFilename = 'ptc-plus-repl'
@@ -55,14 +206,34 @@ const CONFORMANCE_CELL = `"use strict";
55
206
  if (this !== globalThis) throw new Error('invalid REPL global receiver semantics')
56
207
  const __ptc_canary = await Promise.resolve(1)
57
208
  if (__ptc_canary !== 1) throw new Error('invalid REPL await semantics')
58
- const __ptc_import_canary = await import(${JSON.stringify(REPL_IMPORT_CANARY)})
209
+ const __ptc_import_canary = await import(${jsonStringify(REPL_IMPORT_CANARY)})
59
210
  if (__ptc_import_canary.default !== 1) throw new Error('invalid REPL import semantics')
60
211
  }`
61
212
  let activeRun
62
213
  let activeExecution
63
- let pendingVolatileReason
214
+ let suppressReplCwdObservation = false
64
215
  let nextCallId = 0
65
216
  let nextStaticAdapterId = 0
217
+ let nextUserBindingModuleId = 0
218
+ const userBindingEntries = new Map()
219
+ const userBindingNames = new Map()
220
+ const userBindingSources = new Map()
221
+ const dynamicNamespaces = new Map()
222
+ const originalDynamicNamespaceGlobals = new Map()
223
+ const dynamicNamespaceViews = new Map()
224
+ const dynamicNamespaceGlobalGetters = new Map()
225
+ let retainedLegacyUserBindingRuntime = false
226
+
227
+ installProgramAmbientResolver(name => {
228
+ if (!mapHas(dynamicNamespaces, name) && !mapHas(originalDynamicNamespaceGlobals, name)
229
+ && !mapHas(dynamicNamespaceViews, name)) return undefined
230
+ return {
231
+ get: () => dynamicNamespaceView(name),
232
+ set() { throw new TypeError(`${name} cannot be overwritten because reserved program bindings are not shadowable`) },
233
+ typeof: () => 'object',
234
+ delete: () => false,
235
+ }
236
+ })
66
237
 
67
238
  class StaticImportFailure {
68
239
  constructor(error, position) {
@@ -76,6 +247,10 @@ class CellReturn extends Error {
76
247
  super('cell returned')
77
248
  this.value = value
78
249
  }
250
+
251
+ static complete() {
252
+ evaluationScope.getStore().completed = true
253
+ }
79
254
  }
80
255
  function appendLog(...values) {
81
256
  const current = logScope.getStore()
@@ -85,14 +260,14 @@ function appendLog(...values) {
85
260
 
86
261
  function appendText(current, text) {
87
262
  if (current.open !== true || current.outputLimited) return
88
- const bytes = Buffer.byteLength(JSON.stringify(text), 'utf8') + (current.logs.length === 0 ? 0 : 1)
263
+ const bytes = bufferByteLength(jsonStringify(text), 'utf8') + (current.logs.length === 0 ? 0 : 1)
89
264
  if (current.logBytes + bytes > current.maxOutputBytes) {
90
265
  current.outputLimited = true
91
266
  channel.postMessage({ type: 'output-limit', id: current.id, logs: current.logs })
92
267
  return
93
268
  }
94
269
  current.logBytes += bytes
95
- current.logs.push(text)
270
+ appendArray(current.logs, text)
96
271
  }
97
272
 
98
273
  const consoleView = Object.freeze({
@@ -108,7 +283,8 @@ Object.defineProperty(context, 'console', { configurable: true, value: consoleVi
108
283
  function captureWrite(chunk, ...rest) {
109
284
  const current = logScope.getStore()
110
285
  if (current?.open === true) appendText(current, typeof chunk === 'string' ? chunk : String(chunk))
111
- const callback = [rest[0], rest[1]].find(value => typeof value === 'function')
286
+ const callback = typeof rest[0] === 'function' ? rest[0]
287
+ : typeof rest[1] === 'function' ? rest[1] : undefined
112
288
  if (callback !== undefined) queueMicrotask(() => callback(null))
113
289
  return true
114
290
  }
@@ -117,10 +293,7 @@ process.stderr.write = captureWrite
117
293
 
118
294
  function markVolatile(reason) {
119
295
  const current = activeExecution
120
- if (current === undefined) {
121
- pendingVolatileReason ??= reason
122
- return
123
- }
296
+ if (current === undefined) return
124
297
  if (current.durability === 'volatile') return
125
298
  current.durability = 'volatile'
126
299
  current.volatileReason ??= reason
@@ -134,10 +307,10 @@ function completionDurability(execution) {
134
307
  }
135
308
  }
136
309
 
137
- const originalRequire = context.require
138
-
139
310
  function guardProcessControls() {
140
- for (const property of PROCESS_CONTROLS) {
311
+ const properties = setValuesArray(PROCESS_CONTROLS)
312
+ for (let index = 0; index < properties.length; index += 1) {
313
+ const property = properties[index]
141
314
  const descriptor = Object.getOwnPropertyDescriptor(process, property)
142
315
  Object.defineProperty(process, property, {
143
316
  configurable: false,
@@ -151,243 +324,1002 @@ function guardProcessControls() {
151
324
  }
152
325
 
153
326
  guardProcessControls()
154
- installWorkerCwdVirtualization(sessionCwd, originalRequire)
155
- synchronizeBuiltinEsmExports()
156
- const originalGlobals = Object.fromEntries(
157
- [...AMBIENT_GLOBALS].filter(name => name !== 'require')
158
- .map(name => [name, globalThis[name]]),
159
- )
160
- Object.defineProperty(context, 'require', {
161
- configurable: true,
162
- value(specifier) {
163
- if (FORBIDDEN_IMPORTS.has(specifier)) throw new Error(`module ${specifier} is forbidden because it exposes kernel control`)
164
- if (!DURABLE_IMPORTS.has(specifier)) markVolatile(`require(${JSON.stringify(specifier)})`)
165
- return originalRequire(specifier)
166
- },
167
- })
168
-
169
- for (const [name, value] of Object.entries(originalGlobals)) {
170
- Object.defineProperty(context, name, {
171
- configurable: true,
172
- get() {
173
- markVolatile(`ambient ${name}`)
174
- return value
175
- },
176
- set(next) {
177
- markVolatile(`ambient ${name}`)
178
- Object.defineProperty(context, name, { configurable: true, writable: true, value: next })
179
- },
180
- })
181
- }
182
-
183
- const capturedOutput = Object.freeze({ write: captureWrite })
184
- const processView = new Proxy(process, {
185
- get(target, property) {
186
- if (property === 'stdout' || property === 'stderr') return capturedOutput
187
- if (property === 'cwd') {
188
- if (sessionCwd !== undefined) return () => sessionCwd
189
- markVolatile('process.cwd')
190
- return target.cwd.bind(target)
191
- }
192
- if (PROCESS_CONTROLS.has(property)) return Reflect.get(target, property, target)
193
- markVolatile(`process.${String(property)}`)
194
- const value = Reflect.get(target, property, target)
195
- return typeof value === 'function' ? value.bind(target) : value
196
- },
197
- set(target, property, value) {
198
- if (property === 'stdout' || property === 'stderr') return false
199
- markVolatile(`process.${String(property)}`)
200
- return Reflect.set(target, property, value, target)
201
- },
202
- ownKeys(target) {
203
- markVolatile('process reflection')
204
- return Reflect.ownKeys(target)
205
- },
327
+ installWorkerCwdVirtualization(sessionCwd, originalRequire, reason => {
328
+ if (suppressReplCwdObservation) {
329
+ suppressReplCwdObservation = false
330
+ return
331
+ }
332
+ markVolatile(reason)
206
333
  })
207
- Object.defineProperty(context, 'process', { configurable: true, value: processView })
208
-
209
- const mathDescriptors = Object.getOwnPropertyDescriptors(Math)
210
- mathDescriptors.random = {
211
- ...mathDescriptors.random,
212
- value: () => {
213
- markVolatile('Math.random')
214
- return Math.random()
215
- },
334
+ synchronizeBuiltinEsmExports()
335
+ const providedRequire = sessionReplParent === undefined ? originalRequire : createRequire(sessionReplParent)
336
+ function selectProvidedRequire(args) {
337
+ const specifier = args[0]
338
+ if (setHas(FORBIDDEN_IMPORTS, specifier)) throw new Error(`module ${specifier} is forbidden because it exposes kernel control`)
339
+ if (!setHas(DURABLE_IMPORTS, specifier)) markVolatile(`require(${jsonStringify(specifier)})`)
340
+ return managedRequire(activeExecution?.languageSemantics === 'legacy-v1' ? replParent : sessionReplParent ?? replParent,
341
+ activeExecution?.languageSemantics === 'legacy-v1' ? originalRequire : providedRequire)
216
342
  }
217
- const mathView = Object.defineProperties(Object.create(Object.getPrototypeOf(Math)), mathDescriptors)
218
- Object.defineProperty(context, 'Math', {
343
+ Object.defineProperty(context, 'require', {
219
344
  configurable: true,
220
- value: Object.freeze(mathView),
345
+ value: new Proxy(providedRequire, {
346
+ apply: (_, receiver, args) => privateReflect.apply(selectProvidedRequire(args), receiver, args),
347
+ construct: (_, args, newTarget) => privateReflect.construct(selectProvidedRequire(args), args, newTarget),
348
+ }),
221
349
  })
222
-
223
- function evaluate(program) {
224
- return new Promise((resolve, reject) => {
225
- // REPLServer routes runtime throws to its domain instead of the eval
226
- // callback. Cells are serialized, so temporarily replacing that one error
227
- // handler gives both syntax and runtime failures one settlement path.
228
- const domain = server._domain
229
- const prior = domain.listeners('error')
230
- domain.removeAllListeners('error')
350
+ function evaluate(program, completionSignal, asyncCompletion = false) {
351
+ return new ControlPromise((resolve, reject) => {
231
352
  let settled = false
232
- const finish = (error, value) => {
353
+ const finish = (failed, value) => {
233
354
  if (settled) return
234
355
  settled = true
235
- domain.removeListener('error', onError)
236
- for (const listener of prior) domain.on('error', listener)
237
- if (error instanceof CellReturn) {
238
- Promise.resolve(error.value).then(
356
+ if (failed && value instanceof CellReturn) {
357
+ completionObserver.settle(value.value,
239
358
  value => resolve({ hasValue: true, value }),
240
359
  reject,
241
360
  )
242
- } else if (error) reject(error)
361
+ } else if (failed) reject(value)
243
362
  else {
244
- Promise.resolve(value).then(
363
+ completionObserver.settle(value,
245
364
  value => resolve({ hasValue: value !== undefined, value }),
246
365
  reject,
247
366
  )
248
367
  }
249
368
  }
250
- const onError = error => finish(error)
251
- domain.on('error', onError)
252
369
  activeFilename = `ptc-plus-repl-${++filenameSequence}`
253
- server.eval(program + CELL_FRAME_SUFFIX, context, activeFilename, finish)
370
+ suppressReplCwdObservation = true
371
+ evaluationScope.run(finish, () => errorDomain.run(() => {
372
+ // Older REPL callbacks conflate null/undefined await rejection with a
373
+ // successful empty result. A reached EOF distinguishes those outcomes.
374
+ // A block-scoped declaration has empty completion and preserves the
375
+ // preceding non-await expression value without adding a session binding.
376
+ // Keep the final semicolon so REPL cannot guess a block is an object literal.
377
+ const suffix = completionSignal === undefined || asyncCompletion ? CELL_FRAME_SUFFIX
378
+ : `${CELL_FRAME_SUFFIX}{ let completed = this[${jsonStringify(completionSignal)}].complete(); }${CELL_FRAME_SUFFIX}`
379
+ server.eval(program + suffix, context, activeFilename, (error, value) => {
380
+ if (asyncCompletion && (error === null || error === undefined)) {
381
+ completionObserver.observe(value, value => finish(false, value), error => finish(true, error))
382
+ return
383
+ }
384
+ const failed = error !== null && error !== undefined || completionSignal !== undefined && finish.completed !== true
385
+ finish(failed, failed ? error : value)
386
+ })
387
+ }))
388
+ suppressReplCwdObservation = false
254
389
  })
255
390
  }
256
391
 
257
- function staticImportAttributes(options) {
258
- if (options === undefined) return ''
259
- const [keyword, attributes] = Object.entries(options)[0]
260
- const entries = Object.entries(attributes)
261
- .map(([key, value]) => `${JSON.stringify(key)}: ${JSON.stringify(value)}`)
262
- return ` ${keyword} { ${entries.join(', ')} }`
392
+ async function verifyEvaluation() {
393
+ await evaluate(CONFORMANCE_CELL)
394
+ const marker = new Error('PTC Plus REPL settlement probe')
395
+ Object.defineProperty(marker, 'stack', { get() { throw new Error('REPL must not format an error before settlement') } })
396
+ context.__ptc_settlement_probe__ = marker
397
+ context.__ptc_completion_probe__ = CellReturn
398
+ try {
399
+ const values = [marker, null, undefined, false, 0]
400
+ for (let valueIndex = 0; valueIndex < values.length; valueIndex += 1) {
401
+ const value = values[valueIndex]
402
+ context.__ptc_settlement_probe__ = value
403
+ const sources = ['throw __ptc_settlement_probe__', 'await Promise.reject(__ptc_settlement_probe__)']
404
+ for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
405
+ const source = sources[sourceIndex]
406
+ let caught = false
407
+ try { await evaluate(source, '__ptc_completion_probe__') } catch (error) {
408
+ if (error !== value) throw new Error('REPL did not preserve the original thrown value')
409
+ caught = true
410
+ }
411
+ if (!caught) throw new Error('REPL did not reject a failed evaluation')
412
+ }
413
+ }
414
+ let syntaxRejected = false
415
+ try { await evaluate('const =') } catch (error) { syntaxRejected = error?.name === 'SyntaxError' }
416
+ if (!syntaxRejected) throw new Error('REPL did not preserve syntax failure')
417
+ context.__ptc_settlement_probe__ = new CellReturn(promiseResolve(marker))
418
+ const returned = await evaluate('throw __ptc_settlement_probe__')
419
+ if (!returned.hasValue || returned.value !== marker) throw new Error('REPL did not preserve cell return')
420
+ await evaluate(CONFORMANCE_CELL)
421
+ await evaluate('{ const value = await Promise.resolve(42); if (value !== 42) throw new Error("invalid block await") }', '__ptc_completion_probe__')
422
+ const expression = await evaluate('42', '__ptc_completion_probe__')
423
+ if (expression.value !== 42) throw new Error('REPL did not preserve expression completion')
424
+ } finally {
425
+ delete context.__ptc_settlement_probe__
426
+ delete context.__ptc_completion_probe__
427
+ }
428
+ }
429
+
430
+ function staticImportClause(options) {
431
+ if (options === undefined) return { attributes: undefined, clause: '' }
432
+ const option = Object.entries(options)[0]
433
+ const keyword = option[0]
434
+ const attributes = option[1]
435
+ const entries = mapArray(Object.entries(attributes), entry => `${jsonStringify(entry[0])}: ${jsonStringify(entry[1])}`)
436
+ return { attributes, clause: ` ${keyword} { ${join(entries, ', ')} }` }
263
437
  }
264
438
 
265
439
  function staticAdapterSource(load) {
266
- const source = JSON.stringify(load.source)
267
- const attributes = staticImportAttributes(load.options)
268
- if (load.global === undefined) return `import ${source}${attributes};`
269
- const requirements = load.requiredExports?.map((name, index) => {
270
- const imported = name === 'default' ? 'default' : JSON.stringify(name)
440
+ const { attributes, clause } = staticImportClause(load.options)
441
+ const source = jsonStringify(statefulModuleLink(load.source, staticModuleLinkReference(load.source, attributes)))
442
+ if (load.global === undefined) return `import ${source}${clause};`
443
+ const requirements = load.requiredExports === undefined ? [] : mapArray(load.requiredExports, (name, index) => {
444
+ const imported = name === 'default' ? 'default' : jsonStringify(name)
271
445
  return `${imported} as __required_${index}__`
272
- }) ?? []
273
- return [
274
- `import * as namespace from ${source}${attributes};`,
275
- ...(requirements.length === 0 ? [] : [
276
- `export { ${requirements.join(', ')} } from ${source}${attributes};`,
277
- ]),
278
- 'export { namespace };',
279
- ].join('\n')
446
+ })
447
+ const lines = [`import * as namespace from ${source}${clause};`]
448
+ if (requirements.length > 0) appendArray(lines, `export { ${join(requirements, ', ')} } from ${source}${clause};`)
449
+ appendArray(lines, 'export { namespace };')
450
+ return join(lines, '\n')
280
451
  }
281
452
 
282
453
  async function loadStaticModule(load) {
454
+ if (load.operation === 'native-dynamic') return { namespace: nativeRootDynamic.environment(load.callableSources,
455
+ load.awaitRoot === true, filterArray(load.logicalRoots, name => statefulRoots.has(name)
456
+ && rootBindingStorage(name, moduleRuntimeIntrinsics.includes(load.nativeLexicals, name)) !== 'lexical'), load.writableRoots) }
457
+ if (load.operation === 'import') {
458
+ return { namespace: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm) }
459
+ }
283
460
  const adapter = `data:text/javascript,${encodeURIComponent(staticAdapterSource(load))}#${++nextStaticAdapterId}`
284
- staticAdapterParents.add(adapter)
461
+ setAdd(staticAdapterParents, adapter)
462
+ userModuleCompilation.mark(adapter, { compiled: true,
463
+ transform: activeExecution?.moduleTransform })
285
464
  try {
286
- const completion = await evaluate(`import(${JSON.stringify(adapter)})`)
287
- return completion.value.namespace
465
+ const completion = await evaluate(`import(${jsonStringify(adapter)})`)
466
+ if (load.global === undefined) return { namespace: undefined }
467
+ const namespace = readModuleImport(adapter, load.source, null, () => completion.value.namespace, load.options?.with)
468
+ return { namespace }
288
469
  } finally {
289
- staticAdapterParents.delete(adapter)
470
+ setDelete(staticAdapterParents, adapter)
290
471
  }
291
472
  }
292
473
 
293
- function callHost(runId, global, member, args, errorClass) {
294
- if (activeRun !== runId) return Promise.reject(new Error('PTC execution lease expired'))
474
+ function hasExecutionLease(runId) {
475
+ return runId !== undefined && activeRun === runId && logScope.getStore()?.id === runId
476
+ }
477
+
478
+ // BoundError is the error class installed by this cell's wrapper, not a name
479
+ // looked up later: the cell can replace or delete the context binding while a
480
+ // call is pending, and the call must keep its submitted error identity.
481
+ function callHost(runId, global, member, args, BoundError) {
482
+ if (!hasExecutionLease(runId)) {
483
+ return promiseReject(programBindingError('lease', 'PTC execution lease expired'))
484
+ }
485
+ const activation = userBindingActivationScope.getStore()
486
+ if (activation !== undefined) {
487
+ activation.called = true
488
+ if (activation.failed) {
489
+ markVolatile(`failed user binding ${jsonStringify(activation.id)} issued a host call`)
490
+ }
491
+ }
295
492
  const id = ++nextCallId
493
+ const valueLimits = activeExecution.valueLimits
296
494
  let settle
297
- const result = new Promise((resolve, reject) => { settle = { resolve, reject, errorClass, member } })
298
- void result.catch(() => {})
299
- pending.set(id, { ...settle, runId })
495
+ const result = new PublicPromise((resolve, reject) => { settle = { resolve, reject } })
496
+ const settled = new ControlPromise(resolve => {
497
+ moduleRuntimeIntrinsics.Reflect.apply(moduleRuntimeIntrinsics.promiseThen, result, [resolve, resolve])
498
+ })
499
+ mapSet(pending, id, { ...settle, runId, valueLimits, settled, member, BoundError })
300
500
  try {
301
501
  channel.postMessage({
302
502
  type: 'call', runId, id, global, member,
303
- args: encodeValue(args, activeExecution?.valueLimits),
503
+ args: encodeValue(args, valueLimits),
304
504
  })
305
505
  } catch (error) {
306
- pending.delete(id)
506
+ mapDelete(pending, id)
307
507
  settle.reject(error)
308
508
  }
309
509
  return result
310
510
  }
311
511
 
512
+ function dynamicNamespace(name) {
513
+ const assertLease = () => {
514
+ if (!hasExecutionLease(logScope.getStore()?.id)) {
515
+ throw programBindingError('lease', 'PTC execution lease expired')
516
+ }
517
+ }
518
+ return new Proxy(Object.create(null), {
519
+ get(_target, property) {
520
+ if (typeof property !== 'string') return undefined
521
+ assertLease()
522
+ const descriptor = mapGet(dynamicNamespaces, name)
523
+ if (descriptor === undefined || !setHas(descriptor.members, property)) return undefined
524
+ return (...args) => {
525
+ if (!hasExecutionLease(logScope.getStore()?.id)) {
526
+ return promiseReject(programBindingError('lease', 'PTC execution lease expired'))
527
+ }
528
+ const current = mapGet(dynamicNamespaces, name)
529
+ if (current === undefined || !setHas(current.members, property)) {
530
+ return promiseReject(programBindingError('capability', `unknown binding ${name}.${property}`))
531
+ }
532
+ return callHost(
533
+ logScope.getStore()?.id,
534
+ name,
535
+ property,
536
+ args.length === 0 && setHas(current.emptyObjectMembers, property) ? {} : args[0],
537
+ current.BoundError,
538
+ )
539
+ }
540
+ },
541
+ has(_target, property) {
542
+ assertLease()
543
+ const descriptor = mapGet(dynamicNamespaces, name)
544
+ return typeof property === 'string' && descriptor !== undefined && setHas(descriptor.members, property)
545
+ },
546
+ ownKeys() {
547
+ assertLease()
548
+ const descriptor = mapGet(dynamicNamespaces, name)
549
+ return descriptor === undefined ? [] : setValuesArray(descriptor.members)
550
+ },
551
+ getOwnPropertyDescriptor(_target, property) {
552
+ assertLease()
553
+ const descriptor = mapGet(dynamicNamespaces, name)
554
+ return typeof property === 'string' && descriptor !== undefined && setHas(descriptor.members, property)
555
+ ? compilerDescriptors.descriptor({ configurable: true, enumerable: true })
556
+ : undefined
557
+ },
558
+ set() { return false },
559
+ })
560
+ }
561
+
562
+ function dynamicNamespaceView(name) {
563
+ let view = mapGet(dynamicNamespaceViews, name)
564
+ if (view === undefined) {
565
+ view = dynamicNamespace(name)
566
+ mapSet(dynamicNamespaceViews, name, view)
567
+ }
568
+ return view
569
+ }
570
+
571
+ function namespaceGlobalGetter(name) {
572
+ let getter = mapGet(dynamicNamespaceGlobalGetters, name)
573
+ if (getter === undefined) {
574
+ getter = () => mapGet(dynamicNamespaces, name)?.cellView ?? dynamicNamespaceView(name)
575
+ mapSet(dynamicNamespaceGlobalGetters, name, getter)
576
+ }
577
+ return getter
578
+ }
579
+
580
+ function installNamespaceGlobal(name) {
581
+ Object.defineProperty(globalThis, name, {
582
+ configurable: true,
583
+ get: namespaceGlobalGetter(name),
584
+ })
585
+ }
586
+
587
+ function installDynamicNamespaceGlobals() {
588
+ const pendingGlobals = []
589
+ const names = mapKeysArray(dynamicNamespaces)
590
+ for (let index = 0; index < names.length; index += 1) {
591
+ const name = names[index]
592
+ if (mapHas(originalDynamicNamespaceGlobals, name)) {
593
+ installNamespaceGlobal(name)
594
+ continue
595
+ }
596
+ const original = setHas(installedGlobals, name)
597
+ ? mapGet(installedGlobalOriginals, name)
598
+ : capturedGlobalDescriptor(name)
599
+ if (setHas(workerBaselineGlobals, name)) {
600
+ throw new Error(
601
+ `program namespace ${jsonStringify(name)} cannot be bridged into Global User Binding modules because the worker global already exists`,
602
+ )
603
+ }
604
+ appendArray(pendingGlobals, [name, original.descriptor])
605
+ }
606
+ for (let index = 0; index < pendingGlobals.length; index += 1) {
607
+ const entry = pendingGlobals[index]
608
+ const name = entry[0]
609
+ const descriptor = entry[1]
610
+ mapSet(originalDynamicNamespaceGlobals, name, descriptor)
611
+ if (!setHas(installedGlobals, name)) installNamespaceGlobal(name)
612
+ }
613
+ }
614
+
615
+ function restoreDynamicNamespaceGlobals() {
616
+ const names = mapKeysArray(originalDynamicNamespaceGlobals)
617
+ for (let index = 0; index < names.length; index += 1) {
618
+ const name = names[index]
619
+ setHas(installedGlobals, name) ? installNamespaceGlobal(name) : privateReflect.deleteProperty(globalThis, name)
620
+ }
621
+ mapClear(originalDynamicNamespaceGlobals)
622
+ }
623
+
312
624
  function installBindings(message) {
313
- for (const name of installedGlobals) delete context[name]
314
- installedGlobals.clear()
625
+ const installedNames = setValuesArray(installedGlobals)
626
+ for (let index = 0; index < installedNames.length; index += 1) {
627
+ const name = installedNames[index]
628
+ const original = mapGet(installedGlobalOriginals, name)
629
+ const attachedEntry = original.userGlobalEntryId === undefined
630
+ ? undefined : mapGet(userBindingEntries, original.userGlobalEntryId)
631
+ const attachment = attachedEntry === undefined ? undefined : mapGet(attachedEntry.descriptors, name)
632
+ if (mapHas(originalDynamicNamespaceGlobals, name)) {
633
+ installNamespaceGlobal(name)
634
+ } else if (original.userGlobalEntryId !== undefined
635
+ && (mapGet(userBindingNames, name) !== original.userGlobalEntryId
636
+ || attachment !== original.attachment)) {
637
+ delete context[name]
638
+ } else if (original.descriptor === undefined) delete context[name]
639
+ else Object.defineProperty(context, name, original.descriptor)
640
+ }
641
+ setClear(installedGlobals)
642
+ mapClear(installedGlobalOriginals)
643
+ mapClear(dynamicNamespaces)
644
+ let namespaceError
315
645
 
316
- for (const namespace of message.namespaces) {
646
+ for (let namespaceIndex = 0; namespaceIndex < message.namespaces.length; namespaceIndex += 1) {
647
+ const namespace = message.namespaces[namespaceIndex]
648
+ const descriptor = namespace.errorClass
649
+ // The wrapper owns the constructor, so a pending call keeps its submitted
650
+ // error identity even after the cell shadows the context binding.
651
+ const BoundError = descriptor === undefined ? undefined : class extends Error {
652
+ constructor(member, detail, cause) {
653
+ super(detail)
654
+ this.name = descriptor.name
655
+ Object.defineProperty(this, descriptor.memberNameProperty, { enumerable: true, value: member })
656
+ if (cause !== undefined) Object.defineProperty(this, 'ptcCause', { value: cause })
657
+ }
658
+ }
317
659
  const view = Object.create(null)
318
- const emptyObjectMembers = new Set(namespace.emptyObjectMembers ?? [])
319
- for (const member of namespace.members) {
660
+ const emptyObjectMembers = setFromArray(namespace.emptyObjectMembers ?? [])
661
+ mapSet(dynamicNamespaces, namespace.global, {
662
+ members: setFromArray(namespace.members),
663
+ shadowable: namespace.shadowable === true,
664
+ emptyObjectMembers,
665
+ BoundError,
666
+ cellView: view,
667
+ })
668
+ for (let memberIndex = 0; memberIndex < namespace.members.length; memberIndex += 1) {
669
+ const member = namespace.members[memberIndex]
320
670
  Object.defineProperty(view, member, {
321
671
  enumerable: true,
322
672
  value: (...args) => callHost(
323
673
  message.id,
324
674
  namespace.global,
325
675
  member,
326
- args.length === 0 && emptyObjectMembers.has(member) ? {} : args[0],
327
- namespace.errorClass,
676
+ args.length === 0 && setHas(emptyObjectMembers, member) ? {} : args[0],
677
+ BoundError,
328
678
  ),
329
679
  })
330
680
  }
331
681
  Object.freeze(view)
332
- Object.defineProperty(context, namespace.global, { configurable: true, value: view })
333
- installedGlobals.add(namespace.global)
334
-
335
- if (namespace.errorClass !== undefined) {
336
- const descriptor = namespace.errorClass
337
- const BoundError = class extends Error {
338
- constructor(member, detail, cause) {
339
- super(detail)
340
- this.name = descriptor.name
341
- Object.defineProperty(this, descriptor.memberNameProperty, { enumerable: true, value: member })
342
- if (cause !== undefined) Object.defineProperty(this, 'ptcCause', { value: cause })
682
+ const original = capturedGlobalDescriptor(namespace.global)
683
+ mapSet(installedGlobalOriginals, namespace.global, original)
684
+ if (setHas(workerBaselineGlobals, namespace.global)) {
685
+ namespaceError ??= new Error(
686
+ `program namespace ${jsonStringify(namespace.global)} cannot be bridged into the worker realm because the global already exists`,
687
+ )
688
+ } else {
689
+ installNamespaceGlobal(namespace.global)
690
+ setAdd(installedGlobals, namespace.global)
691
+ }
692
+
693
+ if (descriptor !== undefined) {
694
+ mapSet(installedGlobalOriginals, descriptor.name, capturedGlobalDescriptor(descriptor.name))
695
+ Object.defineProperty(context, descriptor.name, { configurable: true, value: BoundError })
696
+ setAdd(installedGlobals, descriptor.name)
697
+ }
698
+ }
699
+ return namespaceError
700
+ }
701
+
702
+ function capturedGlobalDescriptor(name) {
703
+ const descriptor = mapHas(originalDynamicNamespaceGlobals, name)
704
+ ? mapGet(originalDynamicNamespaceGlobals, name)
705
+ : Object.getOwnPropertyDescriptor(context, name)
706
+ const userGlobalEntryId = mapGet(userBindingNames, name)
707
+ const entry = mapGet(userBindingEntries, userGlobalEntryId)
708
+ const installed = entry === undefined ? undefined : mapGet(entry.descriptors, name)
709
+ return {
710
+ descriptor,
711
+ ...(userGlobalEntryId !== undefined && descriptorsEqual(descriptor, installed)
712
+ ? { userGlobalEntryId, attachment: installed }
713
+ : {}),
714
+ }
715
+ }
716
+
717
+ function underlyingUserBindingDescriptor(name) {
718
+ return setHas(installedGlobals, name)
719
+ ? mapGet(installedGlobalOriginals, name).descriptor
720
+ : Object.getOwnPropertyDescriptor(context, name)
721
+ }
722
+
723
+ function recordUserBindingAssignment(name, legacy = false) {
724
+ if (!legacy || activeExecution?.userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY) {
725
+ if (!setHas(installedGlobals, name)) {
726
+ mapDelete(userBindingNames, name)
727
+ mapSet(userBindingSources, name, { state: 'local' })
728
+ }
729
+ } else {
730
+ // Historical setters record invocation before defining the property. A
731
+ // restored accessor does not erase that operation, even for the same value.
732
+ if (activeExecution !== undefined) setAdd(activeExecution.legacyAssignedUserBindingNames, name)
733
+ }
734
+ }
735
+
736
+ // A temporary private getter distinguishes native lexical storage without
737
+ // invoking provider/user getters or comparing arbitrary values. A failed read
738
+ // proves no initialized value; it must never be promoted from the static catalog.
739
+ function rootBindingStorage(name, nativeLexical = false) {
740
+ const original = Object.getOwnPropertyDescriptor(context, name)
741
+ if (original?.configurable === false) {
742
+ if (!nativeLexical && !Object.hasOwn(original, 'value')) return 'unknown'
743
+ try {
744
+ // Both an own data property and an initialized lexical can be read
745
+ // without executing an accessor. Either proves an available local name.
746
+ runInWorkerReplRealm(name, { displayErrors: false })
747
+ return nativeLexical ? 'lexical' : 'local'
748
+ } catch {
749
+ return 'unknown'
750
+ }
751
+ }
752
+ let propertyRead = false
753
+ try {
754
+ Object.defineProperty(context, name, { configurable: true, get() { propertyRead = true } })
755
+ runInWorkerReplRealm(name, { displayErrors: false })
756
+ return propertyRead ? 'property' : 'lexical'
757
+ } catch {
758
+ return 'unknown'
759
+ } finally {
760
+ if (original === undefined) delete context[name]
761
+ else Object.defineProperty(context, name, original)
762
+ }
763
+ }
764
+
765
+ function writableRootProperty(name) {
766
+ const descriptor = Object.getOwnPropertyDescriptor(context, name)
767
+ return descriptor !== undefined && (Object.hasOwn(descriptor, 'value')
768
+ ? descriptor.writable === true : typeof descriptor.set === 'function')
769
+ }
770
+
771
+ function userBindingRootStorage(name) {
772
+ if (statefulRoots.has(name)) return 'local'
773
+ const namespaces = activeExecution.importBindingNamespaces
774
+ const namespace = namespaces === undefined ? undefined : mapGet(namespaces, name)
775
+ if (namespace === undefined) return rootBindingStorage(name)
776
+ // Compiler-validated imports use native namespace slots, not public alias
777
+ // properties. Probe only the slot; reading an export could invoke user code.
778
+ return rootBindingStorage(namespace) === 'lexical' ? 'lexical' : 'unknown'
779
+ }
780
+
781
+ function reconcileUserBindingNames() {
782
+ const names = mapKeysArray(userBindingSources)
783
+ for (let index = 0; index < names.length; index += 1) {
784
+ const name = names[index]
785
+ const storage = userBindingRootStorage(name)
786
+ const descriptor = underlyingUserBindingDescriptor(name)
787
+ const id = mapGet(userBindingNames, name)
788
+ const entry = mapGet(userBindingEntries, id)
789
+ const installed = entry === undefined ? undefined : mapGet(entry.descriptors, name)
790
+ const state = storage === 'lexical' || storage === 'local' ? 'local'
791
+ : storage === 'unknown' ? 'unknown'
792
+ : descriptor === undefined ? 'absent'
793
+ : installed !== undefined && descriptorsEqual(descriptor, installed) ? 'provider' : 'local'
794
+ mapSet(userBindingSources, name, { state, ...(state === 'provider' ? { entryId: id } : {}) })
795
+ if (state !== 'provider') mapDelete(userBindingNames, name)
796
+ }
797
+ }
798
+
799
+ function removePerNameUserBindingEntry(id) {
800
+ const entry = mapGet(userBindingEntries, id)
801
+ for (let index = 0; index < entry.names.length; index += 1) {
802
+ const name = entry.names[index]
803
+ if (mapGet(userBindingNames, name) !== id) continue
804
+ const installed = mapGet(entry.descriptors, name)
805
+ mapDelete(userBindingNames, name)
806
+ if (setHas(installedGlobals, name)) {
807
+ const original = mapGet(installedGlobalOriginals, name)
808
+ if (original.attachment === installed) original.descriptor = undefined
809
+ } else if (descriptorsEqual(Object.getOwnPropertyDescriptor(context, name), installed)) delete context[name]
810
+ // Lifecycle removal permits reattachment; explicit deletion retains absent.
811
+ mapDelete(userBindingSources, name)
812
+ }
813
+ mapDelete(userBindingEntries, id)
814
+ }
815
+
816
+ async function activatePerNameUserBindings(snapshot, shadowedNames, cwd, reusePolicy, initialFailures = []) {
817
+ // Import preceding legacy attachments into the new source owner once.
818
+ const existingNames = mapEntriesArray(userBindingNames)
819
+ for (let index = 0; index < existingNames.length; index += 1) {
820
+ const name = existingNames[index][0]
821
+ const entryId = existingNames[index][1]
822
+ if (mapHas(userBindingSources, name)) continue
823
+ mapSet(userBindingSources, name, { state: 'provider', entryId })
824
+ // The legacy catalog includes actual setter writes as well as descriptor
825
+ // changes. Do not reattach a restored local accessor by descriptor equality.
826
+ if (setHas(shadowedNames, name) && !setHas(installedGlobals, name)) mapDelete(userBindingNames, name)
827
+ }
828
+ reconcileUserBindingNames()
829
+ const blockedIds = setFromArray(mapArray(initialFailures, failure => failure.id))
830
+ const desired = mapFromArray(filterArray(snapshot?.entries ?? [], entry => !setHas(blockedIds, entry.id)), entry => [entry.id, entry])
831
+ try {
832
+ if (mapSize(desired) > 0 || retainedLegacyUserBindingRuntime) installDynamicNamespaceGlobals()
833
+ } catch (error) {
834
+ const ids = mapKeysArray(userBindingEntries)
835
+ for (let index = 0; index < ids.length; index += 1) removePerNameUserBindingEntry(ids[index])
836
+ const failures = copyArray(initialFailures)
837
+ const desiredFailures = mapArray(mapKeysArray(desired), id => ({ id, error: messageOf(error) }))
838
+ for (let index = 0; index < desiredFailures.length; index += 1) appendArray(failures, desiredFailures[index])
839
+ return { activated: [], failures, error }
840
+ }
841
+ const currentEntries = mapEntriesArray(userBindingEntries)
842
+ for (let index = 0; index < currentEntries.length; index += 1) {
843
+ const id = currentEntries[index][0]
844
+ const current = currentEntries[index][1]
845
+ const next = mapGet(desired, id)
846
+ if (next === undefined || current.transform !== snapshot.transform || !(reusePolicy === LEGACY_USER_BINDINGS_REUSE_POLICY
847
+ ? next.fingerprint === current.entry.fingerprint : userBindingImplementationMatches(next, current.entry))) {
848
+ removePerNameUserBindingEntry(id)
849
+ }
850
+ }
851
+ const activated = []
852
+ const failures = copyArray(initialFailures)
853
+ const desiredEntries = mapValuesArray(desired)
854
+ for (let entryIndex = 0; entryIndex < desiredEntries.length; entryIndex += 1) {
855
+ const entry = desiredEntries[entryIndex]
856
+ const current = mapGet(userBindingEntries, entry.id)
857
+ if (current !== undefined) {
858
+ current.entry = entry
859
+ appendArray(activated, entry.id)
860
+ continue
861
+ }
862
+ const names = entry.scope === 'namespace' ? [entry.name] : entry.symbols
863
+ const previousSources = mapFromArray(names, name => [name, mapGet(userBindingSources, name)])
864
+ const installedNames = []
865
+ let evaluated
866
+ const activation = { id: entry.id, called: false, failed: false }
867
+ try {
868
+ if (entry.durability === 'volatile') markVolatile(`user binding ${jsonStringify(entry.id)}: ${entry.volatileReason ?? 'non-replayable source'}`)
869
+ evaluated = await userBindingActivationScope.run(activation, () => evaluateUserBinding(entry, cwd, snapshot.transform))
870
+ const namespace = evaluated.namespace
871
+ for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
872
+ const symbol = entry.symbols[symbolIndex]
873
+ if (!Object.hasOwn(namespace, symbol)) throw new Error(`named export ${jsonStringify(symbol)} is unavailable after evaluation`)
874
+ }
875
+ const view = Object.create(null)
876
+ for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
877
+ const symbol = entry.symbols[symbolIndex]
878
+ Object.defineProperty(view, symbol, { enumerable: true, get: () => namespace[symbol] })
879
+ }
880
+ Object.freeze(view)
881
+ const descriptors = new Map()
882
+ for (let nameIndex = 0; nameIndex < names.length; nameIndex += 1) {
883
+ const name = names[nameIndex]
884
+ const storage = userBindingRootStorage(name)
885
+ const prior = mapGet(userBindingSources, name)
886
+ if (storage === 'lexical' || storage === 'unknown') {
887
+ mapSet(userBindingSources, name, { state: storage === 'lexical' ? 'local' : 'unknown' })
888
+ } else if (prior === undefined && setHas(shadowedNames, name)) {
889
+ const descriptor = underlyingUserBindingDescriptor(name)
890
+ mapSet(userBindingSources, name, { state: descriptor === undefined ? 'unknown' : 'local' })
343
891
  }
892
+ const source = mapGet(userBindingSources, name)
893
+ if (source !== undefined && source.state !== 'provider') continue
894
+ const descriptor = Object.getOwnPropertyDescriptor(context, name)
895
+ Object.defineProperty(context, name, {
896
+ configurable: true,
897
+ enumerable: true,
898
+ get: () => entry.scope === 'namespace' ? view : namespace[name],
899
+ set(value) {
900
+ const receiver = this === contextGlobal ? context : this
901
+ Object.defineProperty(receiver, name, { configurable: true, enumerable: true, writable: true, value })
902
+ if (receiver === context) recordUserBindingAssignment(name)
903
+ },
904
+ })
905
+ appendArray(installedNames, { name, descriptor })
906
+ mapSet(descriptors, name, Object.getOwnPropertyDescriptor(context, name))
907
+ mapSet(userBindingNames, name, entry.id)
908
+ mapSet(userBindingSources, name, { state: 'provider', entryId: entry.id })
344
909
  }
345
- Object.defineProperty(context, descriptor.name, { configurable: true, value: BoundError })
346
- installedGlobals.add(descriptor.name)
910
+ mapSet(userBindingEntries, entry.id, { entry, transform: snapshot.transform, moduleUrl: evaluated.moduleUrl, names, descriptors })
911
+ if (snapshot.transform !== USER_BINDING_TRANSFORM) retainedLegacyUserBindingRuntime = true
912
+ appendArray(activated, entry.id)
913
+ } catch (error) {
914
+ activation.failed = true
915
+ if (activation.called) markVolatile(`failed user binding ${jsonStringify(entry.id)} issued a host call`)
916
+ for (let index = installedNames.length - 1; index >= 0; index--) {
917
+ const installed = installedNames[index]
918
+ mapDelete(userBindingNames, installed.name)
919
+ if (installed.descriptor === undefined) delete context[installed.name]
920
+ else Object.defineProperty(context, installed.name, installed.descriptor)
921
+ }
922
+ const previousEntries = mapEntriesArray(previousSources)
923
+ for (let previousIndex = 0; previousIndex < previousEntries.length; previousIndex += 1) {
924
+ const name = previousEntries[previousIndex][0]
925
+ const prior = previousEntries[previousIndex][1]
926
+ if (prior === undefined) mapDelete(userBindingSources, name)
927
+ else mapSet(userBindingSources, name, prior)
928
+ }
929
+ if (evaluated !== undefined) mapDelete(userBindingModuleParents, evaluated.moduleUrl)
930
+ appendArray(failures, { id: entry.id, error: messageOf(error) })
931
+ }
932
+ }
933
+ if (mapSize(userBindingEntries) === 0 && !retainedLegacyUserBindingRuntime) restoreDynamicNamespaceGlobals()
934
+ return { activated, failures }
935
+ }
936
+
937
+ function removeUserBindingEntry(id, shadowedNames) {
938
+ const previous = mapGet(userBindingEntries, id)
939
+ if (previous === undefined) return
940
+ for (let index = 0; index < previous.names.length; index += 1) {
941
+ const name = previous.names[index]
942
+ if (mapGet(userBindingNames, name) !== id) continue
943
+ mapDelete(userBindingNames, name)
944
+ if (!setHas(shadowedNames, name)) delete context[name]
945
+ }
946
+ mapDelete(userBindingEntries, id)
947
+ }
948
+
949
+ function descriptorsEqual(left, right) {
950
+ if (left === undefined || right === undefined) return left === right
951
+ return left.configurable === right.configurable
952
+ && left.enumerable === right.enumerable
953
+ && left.get === right.get
954
+ && left.set === right.set
955
+ && left.value === right.value
956
+ && left.writable === right.writable
957
+ }
958
+
959
+ function reconcileUserBindingShadows(shadowedNames) {
960
+ const entries = mapEntriesArray(userBindingEntries)
961
+ for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
962
+ const id = entries[entryIndex][0]
963
+ const entry = entries[entryIndex][1]
964
+ for (let nameIndex = 0; nameIndex < entry.names.length; nameIndex += 1) {
965
+ const name = entry.names[nameIndex]
966
+ if (mapGet(userBindingNames, name) !== id) continue
967
+ const installed = mapGet(entry.descriptors, name)
968
+ const current = Object.getOwnPropertyDescriptor(context, name)
969
+ if (!descriptorsEqual(current, installed)) setAdd(shadowedNames, name)
347
970
  }
348
971
  }
349
972
  }
350
973
 
974
+ async function evaluateUserBinding(entry, cwd, transform) {
975
+ if (typeof cwd !== 'string' || !isAbsolute(cwd)) {
976
+ throw new Error('user binding activation requires an absolute storage directory')
977
+ }
978
+ const prepared = compileStatefulModule(entry.source, {
979
+ transform,
980
+ programBindings: transform === USER_BINDING_TRANSFORM,
981
+ })
982
+ const url = `data:text/javascript,${encodeURIComponent(prepared.code)}#ptc-plus-${entry.fingerprint}-${++nextUserBindingModuleId}`
983
+ mapSet(userBindingModuleParents, url, pathToFileURL(resolve(cwd, 'bindings.json')).href)
984
+ userModuleCompilation.mark(url, { transform, compiled: true, moduleInterface: prepared.moduleInterface, sourceRegions: prepared.sourceRegions })
985
+ try {
986
+ return { namespace: transform === LEGACY_USER_BINDING_TRANSFORM ? await import(url)
987
+ : await managedModuleImport(url, url), moduleUrl: url }
988
+ } catch (error) {
989
+ mapDelete(userBindingModuleParents, url)
990
+ throw error
991
+ }
992
+ }
993
+
994
+ function userBindingImplementationMatches(left, right) {
995
+ return left.source === right.source
996
+ && left.scope === right.scope
997
+ && (left.scope !== 'namespace' || left.name === right.name)
998
+ && left.symbols.length === right.symbols.length
999
+ && everyArray(left.symbols, (symbol, index) => symbol === right.symbols[index])
1000
+ }
1001
+
1002
+ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, initialFailures = []) {
1003
+ reconcileUserBindingShadows(shadowedNames)
1004
+ const blockedIds = setFromArray(mapArray(initialFailures, failure => failure.id))
1005
+ const desired = mapFromArray(filterArray(snapshot?.entries ?? [], (entry) => {
1006
+ const names = entry.scope === 'namespace' ? [entry.name] : entry.symbols
1007
+ return !setHas(blockedIds, entry.id) && !someArray(names, name => setHas(shadowedNames, name))
1008
+ }), entry => [entry.id, entry])
1009
+ try {
1010
+ if (mapSize(desired) > 0 || retainedLegacyUserBindingRuntime) installDynamicNamespaceGlobals()
1011
+ } catch (error) {
1012
+ const ids = mapKeysArray(userBindingEntries)
1013
+ for (let index = 0; index < ids.length; index += 1) removeUserBindingEntry(ids[index], shadowedNames)
1014
+ const failures = copyArray(initialFailures)
1015
+ const desiredFailures = mapArray(mapKeysArray(desired), id => ({ id, error: messageOf(error) }))
1016
+ for (let index = 0; index < desiredFailures.length; index += 1) appendArray(failures, desiredFailures[index])
1017
+ return { activated: [], failures, error }
1018
+ }
1019
+ const existingIds = mapKeysArray(userBindingEntries)
1020
+ for (let index = 0; index < existingIds.length; index += 1) {
1021
+ const id = existingIds[index]
1022
+ const current = mapGet(userBindingEntries, id)
1023
+ const next = mapGet(desired, id)
1024
+ // Historical cells retain fingerprint-based resets, including presentation edits.
1025
+ const reusable = next !== undefined && current.transform === snapshot.transform && (reusePolicy === LEGACY_USER_BINDINGS_REUSE_POLICY
1026
+ ? next.fingerprint === current.entry.fingerprint
1027
+ : userBindingImplementationMatches(next, current.entry))
1028
+ if (!reusable) {
1029
+ removeUserBindingEntry(id, shadowedNames)
1030
+ }
1031
+ }
1032
+ const activated = []
1033
+ const failures = copyArray(initialFailures)
1034
+ const desiredEntries = mapValuesArray(desired)
1035
+ for (let entryIndex = 0; entryIndex < desiredEntries.length; entryIndex += 1) {
1036
+ const entry = desiredEntries[entryIndex]
1037
+ const current = mapGet(userBindingEntries, entry.id)
1038
+ if (current !== undefined) {
1039
+ current.entry = entry
1040
+ appendArray(activated, entry.id)
1041
+ continue
1042
+ }
1043
+ let evaluated
1044
+ const installedNames = []
1045
+ const activation = { id: entry.id, called: false, failed: false }
1046
+ try {
1047
+ if (entry.durability === 'volatile') {
1048
+ markVolatile(`user binding ${jsonStringify(entry.id)}: ${entry.volatileReason ?? 'non-replayable source'}`)
1049
+ }
1050
+ evaluated = await userBindingActivationScope.run(
1051
+ activation,
1052
+ () => evaluateUserBinding(entry, cwd, snapshot.transform),
1053
+ )
1054
+ const namespace = evaluated.namespace
1055
+ for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
1056
+ const symbol = entry.symbols[symbolIndex]
1057
+ if (!Object.hasOwn(namespace, symbol)) {
1058
+ throw new Error(`named export ${jsonStringify(symbol)} is unavailable after evaluation`)
1059
+ }
1060
+ }
1061
+ const names = []
1062
+ if (entry.scope === 'namespace') {
1063
+ const view = Object.create(null)
1064
+ for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
1065
+ const symbol = entry.symbols[symbolIndex]
1066
+ Object.defineProperty(view, symbol, {
1067
+ enumerable: true,
1068
+ get: () => namespace[symbol],
1069
+ })
1070
+ }
1071
+ Object.freeze(view)
1072
+ const descriptor = Object.getOwnPropertyDescriptor(context, entry.name)
1073
+ Object.defineProperty(context, entry.name, {
1074
+ configurable: true,
1075
+ enumerable: true,
1076
+ get: () => view,
1077
+ set(value) {
1078
+ recordUserBindingAssignment(entry.name, true)
1079
+ Object.defineProperty(context, entry.name, {
1080
+ configurable: true,
1081
+ enumerable: true,
1082
+ writable: true,
1083
+ value,
1084
+ })
1085
+ },
1086
+ })
1087
+ appendArray(installedNames, {
1088
+ name: entry.name,
1089
+ descriptor,
1090
+ })
1091
+ mapSet(userBindingNames, entry.name, entry.id)
1092
+ appendArray(names, entry.name)
1093
+ } else {
1094
+ for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
1095
+ const symbol = entry.symbols[symbolIndex]
1096
+ const descriptor = Object.getOwnPropertyDescriptor(context, symbol)
1097
+ Object.defineProperty(context, symbol, {
1098
+ configurable: true,
1099
+ enumerable: true,
1100
+ get: () => namespace[symbol],
1101
+ set(value) {
1102
+ recordUserBindingAssignment(symbol, true)
1103
+ Object.defineProperty(context, symbol, {
1104
+ configurable: true,
1105
+ enumerable: true,
1106
+ writable: true,
1107
+ value,
1108
+ })
1109
+ },
1110
+ })
1111
+ appendArray(installedNames, { name: symbol, descriptor })
1112
+ mapSet(userBindingNames, symbol, entry.id)
1113
+ appendArray(names, symbol)
1114
+ }
1115
+ }
1116
+ mapSet(userBindingEntries, entry.id, {
1117
+ entry,
1118
+ transform: snapshot.transform,
1119
+ moduleUrl: evaluated.moduleUrl,
1120
+ names,
1121
+ descriptors: mapFromArray(names, name => [name, Object.getOwnPropertyDescriptor(context, name)]),
1122
+ })
1123
+ if (snapshot.transform !== USER_BINDING_TRANSFORM) retainedLegacyUserBindingRuntime = true
1124
+ appendArray(activated, entry.id)
1125
+ } catch (error) {
1126
+ activation.failed = true
1127
+ if (activation.called) {
1128
+ markVolatile(`failed user binding ${jsonStringify(entry.id)} issued a host call`)
1129
+ }
1130
+ for (let index = installedNames.length - 1; index >= 0; index--) {
1131
+ const installed = installedNames[index]
1132
+ mapDelete(userBindingNames, installed.name)
1133
+ if (installed.descriptor === undefined) delete context[installed.name]
1134
+ else Object.defineProperty(context, installed.name, installed.descriptor)
1135
+ }
1136
+ if (evaluated !== undefined) mapDelete(userBindingModuleParents, evaluated.moduleUrl)
1137
+ removeUserBindingEntry(entry.id, shadowedNames)
1138
+ appendArray(failures, {
1139
+ id: entry.id,
1140
+ error: messageOf(error),
1141
+ })
1142
+ }
1143
+ }
1144
+ if (mapSize(userBindingEntries) === 0 && !retainedLegacyUserBindingRuntime) restoreDynamicNamespaceGlobals()
1145
+ return { activated, failures }
1146
+ }
1147
+
1148
+ async function closeExecution(execution) {
1149
+ activeRun = undefined
1150
+ execution.open = false
1151
+ const settling = []
1152
+ mapForEach(pending, call => {
1153
+ if (call.runId === execution.id) appendArray(settling, call.settled)
1154
+ })
1155
+ for (let index = 0; index < settling.length; index++) await settling[index]
1156
+ }
1157
+
1158
+ function failureOutcome(error, phase, program) {
1159
+ if (phase === 'encode') return { invalidOutput: messageOf(error) }
1160
+ const failure = error instanceof StaticImportFailure ? error.error : error
1161
+ // The V8 frame is reported in the executed text; validate it there so an
1162
+ // out-of-range frame is an explicit absence instead of a mapped position.
1163
+ const detail = errorDetails(failure, activeFilename, program)
1164
+ // A compiler-created call error has only an adapter stack. Its source fact
1165
+ // owns the operation; ordinary exceptions retain their native stack position.
1166
+ const position = error instanceof StaticImportFailure ? error.position
1167
+ : activeExecution.exceptionOrigins.sourceFailure(failure) ? undefined : detail.position
1168
+ return {
1169
+ error: detail.message,
1170
+ errorName: detail.name,
1171
+ ...(detail.toolName === undefined ? {} : { toolName: detail.toolName }),
1172
+ ...(error instanceof StaticImportFailure ? { moduleLoadFailed: true } : {}),
1173
+ ...(position === undefined ? {} : { position }),
1174
+ ...(position === undefined ? { exceptionOrigins: activeExecution.exceptionOrigins.origins(failure) } : {}),
1175
+ ...(detail.cause === undefined ? {} : { cause: detail.cause }),
1176
+ ...(detail.failureOrigin === undefined ? {} : { failureOrigin: detail.failureOrigin }),
1177
+ }
1178
+ }
1179
+
1180
+ function observeBindings(names) {
1181
+ const observation = valueObserver.observe(names)
1182
+ return { ...observation, entries: mapArray(observation.entries, entry => {
1183
+ const local = statefulRoots.localValue(entry.name)
1184
+ return local === undefined ? entry : { name: entry.name, ...previewBindingValue(local.value) }
1185
+ }) }
1186
+ }
1187
+
1188
+ function refreshLegacyPublication(name) {
1189
+ const publications = activeExecution?.nativePublications
1190
+ const publication = publications === undefined ? undefined : mapGet(publications, name)
1191
+ // A completed native declaration is visible to saved logical closures in
1192
+ // the same cell. A failed or pending initializer retains the prior source.
1193
+ if (publication === undefined) return
1194
+ const storage = rootBindingStorage(publication.import?.namespace ?? name, publication.nativeLexical)
1195
+ if (storage === 'lexical' || publication.import === undefined
1196
+ && (storage === 'local' || storage === 'property' && Object.hasOwn(context, name))) {
1197
+ mapDelete(publications, name)
1198
+ statefulRoots.publishLegacy(name, publication.writable, publication.import, storage !== 'lexical')
1199
+ }
1200
+ }
1201
+
1202
+ function sendCompletion(message, execution, userBindings, committedRedeclarations, outcome) {
1203
+ const publicationNames = execution.nativePublications === undefined
1204
+ ? [] : mapKeysArray(execution.nativePublications)
1205
+ for (let index = 0; index < publicationNames.length; index += 1) refreshLegacyPublication(publicationNames[index])
1206
+ const shadowedNames = execution.legacyAssignedUserBindingNames
1207
+ const perName = message.userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY
1208
+ if (perName) reconcileUserBindingNames()
1209
+ else reconcileUserBindingShadows(shadowedNames)
1210
+ const names = message.observeNames ?? []
1211
+ channel.postMessage({
1212
+ type: 'done',
1213
+ id: message.id,
1214
+ logs: execution.logs,
1215
+ ...completionDurability(execution),
1216
+ committedRedeclarations: setValuesArray(committedRedeclarations),
1217
+ ...(message.rootBindings === undefined ? {} : { rootBindingFacts: statefulRoots.facts() }),
1218
+ ...(perName ? { userBindingNames: normalizeUserBindingNames(mapArray(mapEntriesArray(userBindingSources), entry => ({
1219
+ name: entry[0], state: entry[1].state, ...(entry[1].state === 'provider' ? { entryId: entry[1].entryId } : {}),
1220
+ }))) } : {}),
1221
+ ...(message.userBindings === undefined ? {} : {
1222
+ activatedUserBindings: userBindings.activated,
1223
+ userBindingFailures: userBindings.failures,
1224
+ ...(perName ? {} : { shadowedUserBindings: setValuesArray(shadowedNames) }),
1225
+ }),
1226
+ ...outcome,
1227
+ observing: names.length > 0,
1228
+ })
1229
+ if (outcome.error === undefined) valueObserver.record(message.program)
1230
+ if (names.length > 0) {
1231
+ channel.postMessage({ type: 'observation', id: message.id, observation: observeBindings(names) })
1232
+ }
1233
+ }
1234
+
351
1235
  async function runCell(message) {
352
- if (activeRun !== undefined) throw new Error('kernel received overlapping cells')
1236
+ if (activeExecution !== undefined) throw new Error('kernel received overlapping cells')
353
1237
  activeRun = message.id
354
- installBindings(message)
355
1238
  const execution = {
356
1239
  id: message.id,
1240
+ exceptionOrigins: createExceptionOriginScope(),
1241
+ languageSemantics: message.languageSemantics,
1242
+ moduleTransform: message.moduleTransform,
1243
+ userBindingsShadowPolicy: message.userBindingsShadowPolicy,
1244
+ importBindingNamespaces: message.importBindingNamespaces,
1245
+ legacyAssignedUserBindingNames: new Set(),
357
1246
  logs: [],
358
1247
  open: true,
359
1248
  outputLimited: false,
360
1249
  logBytes: 2,
361
1250
  maxOutputBytes: message.maxOutputBytes,
362
1251
  valueLimits: message.valueLimits,
363
- durability: message.durability === 'volatile' || pendingVolatileReason !== undefined ? 'volatile' : 'durable',
364
- volatileReason: pendingVolatileReason,
1252
+ durability: message.durability === 'volatile' ? 'volatile' : 'durable',
1253
+ volatileReason: message.volatileReason,
365
1254
  }
366
- pendingVolatileReason = undefined
367
1255
  activeExecution = execution
368
1256
  const cellGlobals = []
369
1257
  const committedRedeclarations = new Set()
370
1258
 
371
1259
  try {
372
1260
  let completion
1261
+ let outcome
1262
+ let userBindings = { activated: [], failures: [] }
373
1263
  try {
374
1264
  completion = await logScope.run(execution, async () => {
1265
+ const namespaceError = installBindings(message)
375
1266
  Object.defineProperty(context, message.returnSignal, {
376
1267
  configurable: true,
377
1268
  value: CellReturn,
378
1269
  })
379
- cellGlobals.push(message.returnSignal)
1270
+ appendArray(cellGlobals, message.returnSignal)
380
1271
  Object.defineProperty(context, message.commitSignal, {
381
1272
  configurable: true,
382
1273
  value(name) {
383
- committedRedeclarations.add(name)
1274
+ setAdd(committedRedeclarations, name)
384
1275
  },
385
1276
  })
386
- cellGlobals.push(message.commitSignal)
387
- for (const load of message.moduleLoads ?? []) {
1277
+ appendArray(cellGlobals, message.commitSignal)
1278
+ if (message.rootRuntimeName !== undefined) {
1279
+ const nativeLexicals = setFromArray(message.rootBindings.legacyNativeLexicals)
1280
+ const legacyStorage = mapFromArray(filterArray(message.rootBindings.legacyLexicals,
1281
+ name => !statefulRoots.has(name)), name => [name, rootBindingStorage(name, setHas(nativeLexicals, name))])
1282
+ const legacyLexicals = filterArray(message.rootBindings.legacyLexicals, name => mapGet(legacyStorage, name) === 'lexical')
1283
+ const legacyNative = setFromArray(message.rootBindings.legacyNative)
1284
+ const legacyWritable = filterArray(message.rootBindings.legacyWritable, name => {
1285
+ const storage = mapGet(legacyStorage, name)
1286
+ return storage === 'lexical' || setHas(legacyNative, name)
1287
+ && (storage === 'local' || storage === 'property') && writableRootProperty(name)
1288
+ })
1289
+ Object.defineProperty(context, message.rootRuntimeName, {
1290
+ configurable: true,
1291
+ value: statefulRoots.begin({ ...message.rootBindings,
1292
+ legacyLexicals,
1293
+ legacyWritable,
1294
+ legacyObjects: filterArray(legacyWritable, name => mapGet(legacyStorage, name) !== 'lexical'),
1295
+ committed(name) {
1296
+ setDelete(committedRedeclarations, name)
1297
+ setAdd(committedRedeclarations, name)
1298
+ } }),
1299
+ })
1300
+ appendArray(cellGlobals, message.rootRuntimeName)
1301
+ }
1302
+ const activate = message.userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY
1303
+ ? activatePerNameUserBindings : activateUserBindings
1304
+ userBindings = await activate(
1305
+ message.userBindings,
1306
+ setFromArray(message.shadowedUserBindingNames ?? []),
1307
+ message.userBindingsCwd,
1308
+ message.userBindingsReusePolicy,
1309
+ message.userBindingFailures,
1310
+ )
1311
+ if (userBindings.error !== undefined) throw userBindings.error
1312
+ if (namespaceError !== undefined) throw namespaceError
1313
+ for (let failureIndex = 0; failureIndex < userBindings.failures.length; failureIndex += 1) {
1314
+ const failure = userBindings.failures[failureIndex]
1315
+ appendText(execution, `Global binding ${jsonStringify(failure.id)} was not activated: ${failure.error}`)
1316
+ }
1317
+ const moduleLoads = message.moduleLoads ?? []
1318
+ for (let loadIndex = 0; loadIndex < moduleLoads.length; loadIndex += 1) {
1319
+ const load = moduleLoads[loadIndex]
388
1320
  let namespace
389
1321
  try {
390
- namespace = await loadStaticModule(load)
1322
+ namespace = (await loadStaticModule(load)).namespace
391
1323
  } catch (error) {
392
1324
  throw new StaticImportFailure(error, load.position)
393
1325
  }
@@ -396,100 +1328,114 @@ async function runCell(message) {
396
1328
  configurable: true,
397
1329
  value: namespace,
398
1330
  })
399
- cellGlobals.push(load.global)
1331
+ appendArray(cellGlobals, load.global)
1332
+ }
1333
+ }
1334
+ // Preload failure leaves prior aliases authoritative. Once evaluation
1335
+ // starts, new slots must prove their own initialization, including TDZ.
1336
+ execution.importBindingNamespaces = message.preparedImportBindingNamespaces
1337
+ execution.nativePublications = new Map()
1338
+ for (let loadIndex = 0; loadIndex < moduleLoads.length; loadIndex += 1) {
1339
+ const load = moduleLoads[loadIndex]
1340
+ const publications = load.nativePublications ?? []
1341
+ for (let publicationIndex = 0; publicationIndex < publications.length; publicationIndex += 1) {
1342
+ const publication = publications[publicationIndex]
1343
+ mapSet(execution.nativePublications, publication.name, publication)
400
1344
  }
401
1345
  }
402
- return evaluate(message.program)
1346
+ return execution.exceptionOrigins.run(() => evaluate(message.program, message.returnSignal, message.asyncCompletion))
403
1347
  })
404
- activeRun = undefined
405
- execution.open = false
406
- const calls = [...pending.values()]
407
- .filter(call => call.runId === message.id)
408
- .map(call => new Promise(resolve => {
409
- const originalResolve = call.resolve
410
- const originalReject = call.reject
411
- call.resolve = value => { originalResolve(value); resolve() }
412
- call.reject = error => { originalReject(error); resolve() }
413
- }))
414
- if (calls.length > 0) await Promise.all(calls)
415
1348
  } catch (error) {
416
- activeRun = undefined
417
- execution.open = false
418
- const failure = error instanceof StaticImportFailure ? error.error : error
419
- const detail = errorDetails(failure, activeFilename)
420
- const position = error instanceof StaticImportFailure ? error.position : detail.position
421
- channel.postMessage({
422
- type: 'done',
423
- id: message.id,
424
- logs: execution.logs,
425
- error: detail.message,
426
- errorName: detail.name,
427
- ...(detail.toolName === undefined ? {} : { toolName: detail.toolName }),
428
- ...(error instanceof StaticImportFailure ? { moduleLoadFailed: true } : {}),
429
- ...(position === undefined ? {} : { position }),
430
- ...(detail.cause === undefined ? {} : { cause: detail.cause }),
431
- ...completionDurability(execution),
432
- committedRedeclarations: [...committedRedeclarations],
433
- })
434
- return
1349
+ outcome = failureOutcome(error, 'execute', message.program)
1350
+ } finally {
1351
+ await closeExecution(execution)
435
1352
  }
436
1353
 
437
- let response
438
- try {
439
- const encodedValue = completion.hasValue
440
- ? encodeValue(completion.value, execution.valueLimits)
441
- : undefined
442
- response = {
443
- type: 'done',
444
- id: message.id,
445
- logs: execution.logs,
446
- hasValue: completion.hasValue,
447
- ...(encodedValue === undefined ? {} : { value: encodedValue }),
448
- ...completionDurability(execution),
449
- committedRedeclarations: [...committedRedeclarations],
450
- }
451
- } catch (error) {
452
- const detail = messageOf(error)
453
- response = {
454
- type: 'done',
455
- id: message.id,
456
- logs: execution.logs,
457
- invalidOutput: detail,
458
- ...completionDurability(execution),
459
- committedRedeclarations: [...committedRedeclarations],
1354
+ if (outcome === undefined) {
1355
+ try {
1356
+ const encodedValue = completion.hasValue
1357
+ ? encodeValue(completion.value, execution.valueLimits)
1358
+ : undefined
1359
+ outcome = {
1360
+ hasValue: completion.hasValue,
1361
+ ...(encodedValue === undefined ? {} : { value: encodedValue }),
1362
+ }
1363
+ } catch (error) {
1364
+ outcome = failureOutcome(error, 'encode', message.program)
460
1365
  }
461
1366
  }
462
- channel.postMessage(response)
1367
+ sendCompletion(message, execution, userBindings, committedRedeclarations, outcome)
463
1368
  } finally {
464
- for (const name of cellGlobals) delete context[name]
1369
+ for (let index = 0; index < cellGlobals.length; index += 1) delete context[cellGlobals[index]]
465
1370
  activeRun = undefined
466
1371
  activeExecution = undefined
1372
+ execution.exceptionOrigins.close()
467
1373
  execution.open = false
468
1374
  }
469
1375
  }
470
1376
 
1377
+ // The host stops this worker cooperatively so the release below runs while the
1378
+ // realms, REPL and ports it owns are still alive.
1379
+ parentPort.on('message', (message) => {
1380
+ if (message?.type !== WORKER_SHUTDOWN_REQUEST) return
1381
+ server.close()
1382
+ input.destroy()
1383
+ output.destroy()
1384
+ errorDomain.removeAllListeners('error')
1385
+ process.removeListener('uncaughtException', handleUncaughtException)
1386
+ channel.close()
1387
+ parentPort.postMessage({ type: WORKER_SHUTDOWN_ACKNOWLEDGEMENT })
1388
+ parentPort.close()
1389
+ })
1390
+
471
1391
  channel.on('message', (message) => {
1392
+ if (message?.type === 'prepare') {
1393
+ channel.postMessage({ type: 'ready', id: message.id })
1394
+ return
1395
+ }
1396
+ if (message?.type === 'observe') {
1397
+ channel.postMessage({ type: 'observation-started', id: message.id })
1398
+ channel.postMessage({ type: 'observation', id: message.id,
1399
+ observation: observeBindings(message.names) })
1400
+ return
1401
+ }
472
1402
  if (message?.type === 'reply') {
473
- const call = pending.get(message.id)
1403
+ const call = mapGet(pending, message.id)
474
1404
  if (call === undefined || call.runId !== message.runId) return
475
- pending.delete(message.id)
476
- if (message.ok) call.resolve(decodeValue(message.value, activeExecution?.valueLimits))
477
- else if (call.errorClass === undefined) {
478
- const error = new Error(message.error)
479
- if (message.cause !== undefined) error.ptcCause = message.cause
1405
+ mapDelete(pending, message.id)
1406
+ try {
1407
+ if (message.ok) call.resolve(decodeValue(message.value, call.valueLimits))
1408
+ else if (call.BoundError === undefined) {
1409
+ const error = new Error(message.error)
1410
+ if (message.cause !== undefined) error.ptcCause = message.cause
1411
+ call.reject(error)
1412
+ } else call.reject(new call.BoundError(call.member, message.error, message.cause))
1413
+ } catch (error) {
480
1414
  call.reject(error)
481
- } else call.reject(new context[call.errorClass.name](call.member, message.error, message.cause))
1415
+ }
482
1416
  return
483
1417
  }
484
1418
  if (message?.type === 'run') void runCell(message)
485
1419
  })
486
1420
 
1421
+ let startupTimer
487
1422
  try {
488
- await evaluate(CONFORMANCE_CELL)
1423
+ await Promise.race([
1424
+ verifyEvaluation(),
1425
+ new Promise((_, reject) => {
1426
+ startupTimer = setTimeout(() => reject(new Error('REPL settlement probe timed out')), 5000)
1427
+ }),
1428
+ ])
1429
+ replModuleRealm = (await evaluate('({Promise,stringify:value=>`${value}`,importModule:(source,options)=>import(source,options)})')).value
1430
+ valueObserver = createReplValueObserver(context, {
1431
+ awaitLexicals: await supportsAwaitLexicals(context, evaluate),
1432
+ })
489
1433
  parentPort.postMessage({ type: 'ready', port: port1 }, [port1])
490
1434
  } catch (error) {
491
1435
  parentPort.postMessage({
492
1436
  type: 'startup-error',
493
- error: `Node REPL does not satisfy the PTC Plus cell framing contract: ${messageOf(error)}`,
1437
+ error: `PTC runtime prerequisite failed on Node ${process.version}: ${messageOf(error)}. No user cell executed; use a DSH-supported runtime that passes the REPL conformance probe. Editing the cell cannot fix this host failure.`,
494
1438
  })
1439
+ } finally {
1440
+ clearTimeout(startupTimer)
495
1441
  }