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.
- package/README.en.md +65 -78
- package/README.md +65 -78
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +30596 -742
- package/compiler-amaro.cjs +5 -0
- package/compiler-amaro.cjs.map +6 -0
- package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
- package/compiler-assets.json +3 -0
- package/compiler-core.cjs +554 -0
- package/compiler-core.cjs.map +6 -0
- package/compiler-platform.cjs +2 -0
- package/compiler-platform.cjs.map +6 -0
- package/compiler-typescript.cjs +388 -0
- package/compiler-typescript.cjs.map +6 -0
- package/docs/README.md +6 -0
- package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
- package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
- package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
- package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
- package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
- package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
- package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
- package/docs/adr/0023-global-user-bindings.md +104 -0
- package/docs/adr/0024-repl-console-observation.md +33 -0
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
- package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
- package/docs/architecture.md +73 -21
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +95 -19
- package/docs/durability-design.md +25 -10
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +72 -25
- package/docs/semantic-validation.md +29 -0
- package/docs/stateful-binding-updates-proposal.md +755 -0
- package/docs/user-bindings.en.md +64 -0
- package/docs/user-bindings.md +64 -0
- package/docs/value-wire.md +3 -3
- package/docs/verification.md +57 -0
- package/index.js +369 -92
- package/internal/ast-traversal.js +5 -2
- package/internal/binding-descriptors.js +1 -0
- package/internal/binding-pattern-traversal.js +25 -0
- package/internal/binding-pattern.js +26 -29
- package/internal/binding-source-analysis.js +391 -0
- package/internal/binding-update-policy.js +66 -0
- package/internal/bounded-worker-round.js +56 -0
- package/internal/callable-definition-environment.js +42 -0
- package/internal/callable-source-catalog.js +129 -0
- package/internal/callable-source-encoding.js +47 -0
- package/internal/callable-source-facts.js +459 -0
- package/internal/callable-source-format.js +2 -0
- package/internal/callable-source-lowering.js +123 -0
- package/internal/cell-analysis-contract.js +25 -0
- package/internal/cell-analysis.js +240 -290
- package/internal/cell-completion.js +15 -0
- package/internal/cell-error.js +7 -0
- package/internal/cell-parser.js +66 -0
- package/internal/cell-rewriter.js +3 -677
- package/internal/commonjs-export-evidence.js +63 -0
- package/internal/commonjs-source-evidence.js +72 -0
- package/internal/compiler-amaro.cjs +2 -0
- package/internal/compiler-compression.js +12 -0
- package/internal/compiler-data.js +58 -0
- package/internal/compiler-descriptors.js +54 -0
- package/internal/compiler-dynamic-cache.js +48 -0
- package/internal/compiler-entry.js +35 -0
- package/internal/compiler-generated-names.js +17 -0
- package/internal/compiler-intrinsics.js +84 -0
- package/internal/compiler-module-links.js +34 -0
- package/internal/compiler-operations.js +61 -0
- package/internal/compiler-platform-factory.js +84 -0
- package/internal/compiler-platform.js +136 -0
- package/internal/compiler-record-roles.js +53 -0
- package/internal/compiler-region-output.js +259 -0
- package/internal/compiler-resource-lowering.js +103 -0
- package/internal/compiler-scope-facts.js +115 -0
- package/internal/compiler-service.js +109 -0
- package/internal/compiler-source-cache.js +32 -0
- package/internal/compiler-source-pieces.js +93 -0
- package/internal/compiler-source-regions.js +132 -0
- package/internal/compiler-storage-source.js +54 -0
- package/internal/compiler-text.js +15 -0
- package/internal/compiler-typescript.cjs +2 -0
- package/internal/config-spec.js +83 -27
- package/internal/cordis-tools-owner.js +130 -21
- package/internal/diagnostic.js +27 -1
- package/internal/direct-surface-owner.js +76 -23
- package/internal/dynamic-binding-evidence.js +16 -0
- package/internal/dynamic-environment-compiler.js +779 -0
- package/internal/dynamic-environment-integration.js +87 -0
- package/internal/dynamic-environment-runtime.js +362 -0
- package/internal/dynamic-native-calls.js +150 -0
- package/internal/dynamic-native-runtime.js +248 -0
- package/internal/dynamic-scope-analysis.js +90 -0
- package/internal/edit-transport-owner.js +27 -2
- package/internal/execution-seam-compat.js +193 -0
- package/internal/failure-reporting.js +118 -18
- package/internal/host-rpc.js +66 -0
- package/internal/isolated-worker.js +548 -0
- package/internal/kernel-child.js +139 -0
- package/internal/kernel-worker.js +904 -120
- package/internal/language-semantics.js +14 -0
- package/internal/legacy-binding-catalog.js +48 -0
- package/internal/legacy-cell-analysis.js +600 -0
- package/internal/legacy-cell-parser.js +50 -0
- package/internal/legacy-cell-rewriter.js +631 -0
- package/internal/legacy-repl-convenience.js +291 -0
- package/internal/managed-module-operations.js +170 -0
- package/internal/module-compilation.js +321 -0
- package/internal/module-runtime-platform.js +19 -0
- package/internal/module-transform-contract.js +12 -0
- package/internal/native-await-declarations.js +68 -0
- package/internal/native-root-compilation.js +54 -0
- package/internal/native-root-dynamic.js +67 -0
- package/internal/recovery-tips.js +27 -15
- package/internal/rejected-cell-editor.js +13 -8
- package/internal/repl-convenience.js +2 -289
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +6 -0
- package/internal/repl-scope-normalizer.js +2091 -0
- package/internal/repl-value-observer.js +114 -0
- package/internal/rpc-contract.js +52 -0
- package/internal/runtime-bridge-owner.js +172 -28
- package/internal/runtime-config.js +48 -52
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +401 -87
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +106 -0
- package/internal/session-journal.js +184 -654
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +256 -95
- package/internal/session-state.js +247 -71
- package/internal/source-position-map.js +211 -71
- package/internal/stateful-module-compiler.js +154 -0
- package/internal/stateful-module-runtime.js +303 -0
- package/internal/stateful-root-compiler.js +598 -0
- package/internal/stateful-root-runtime.js +340 -0
- package/internal/typert-codec-compat.js +28 -0
- package/internal/typescript-transform.js +29 -0
- package/internal/user-binding-authoring.js +70 -0
- package/internal/user-binding-console-worker.js +184 -0
- package/internal/user-binding-console.js +148 -0
- package/internal/user-binding-draft-projection.js +335 -0
- package/internal/user-binding-model-context.js +31 -0
- package/internal/user-binding-runner.js +56 -0
- package/internal/user-bindings-owner.js +1065 -0
- package/internal/user-bindings-store.js +293 -0
- package/internal/user-bindings.js +353 -0
- package/internal/value-wire-schema.js +28 -0
- package/internal/value-wire.js +29 -20
- package/internal/worker-client.js +57 -67
- package/internal/worker-environment.js +45 -0
- package/internal/worker-output-relay.js +29 -0
- package/internal/worker-shutdown.js +14 -0
- package/package.json +75 -7
- package/src/client-activity.js +88 -53
- package/src/client-authoring-view.js +580 -0
- package/src/client-binding-review.js +230 -0
- package/src/client-bindings-data.js +29 -0
- package/src/client-catalog.js +127 -0
- package/src/client-code-editor.js +84 -0
- package/src/client-console.js +112 -0
- package/src/client-copy.js +370 -0
- package/src/client-feature-gates.js +62 -0
- package/src/client-host-compat.js +150 -0
- package/src/client-indicator-view.js +267 -0
- package/src/client-repl-view.js +292 -0
- package/src/client-rpc.js +20 -0
- package/src/client-settings-view.js +221 -0
- package/src/client-styles.js +106 -0
- package/src/client-tool-view.js +108 -0
- package/src/client-transport.js +159 -0
- package/src/client-workbench.js +521 -0
- package/src/client.js +233 -615
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
import { isRecord } from './record-utils.js'
|
|
2
|
+
import { sessionEvents } from './session-events.js'
|
|
3
|
+
import {
|
|
4
|
+
JOURNAL_KEY,
|
|
5
|
+
RECOVERY_BOUNDARY_KEY,
|
|
6
|
+
RECOVERY_BOUNDARY_EVENT,
|
|
7
|
+
LEGACY_JOURNAL_VERSION,
|
|
8
|
+
REPL_TOOL_NAMES,
|
|
9
|
+
} from './session-journal-schema.js'
|
|
10
|
+
import {
|
|
11
|
+
normalizeDerivedEditResult,
|
|
12
|
+
normalizeJournal,
|
|
13
|
+
normalizeRecoveryBoundaries,
|
|
14
|
+
reduceStateOperations,
|
|
15
|
+
userBindingsForJournal,
|
|
16
|
+
validatedRewrites,
|
|
17
|
+
} from './session-journal.js'
|
|
18
|
+
|
|
19
|
+
function sourceForRunCall(call) {
|
|
20
|
+
try {
|
|
21
|
+
const args = JSON.parse(call.data.arguments)
|
|
22
|
+
return isRecord(args) && typeof args.code === 'string' ? args.code : undefined
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Select the boundary of a failed replay node or an unavailable recovered suffix. */
|
|
29
|
+
export function recoveryBoundaryForHistory(history, failedNode, { reset = false } = {}) {
|
|
30
|
+
if (reset) failedNode = history.nodes[0]
|
|
31
|
+
const failedCallSeq = failedNode === undefined
|
|
32
|
+
? history.available === false ? history.volatileSuffix[0]?.seq : undefined
|
|
33
|
+
: failedNode.callSeq
|
|
34
|
+
if (failedCallSeq === undefined) return undefined
|
|
35
|
+
const frontier = reset ? undefined : failedNode === undefined ? history.head : failedNode.parent
|
|
36
|
+
return {
|
|
37
|
+
failedCallSeq,
|
|
38
|
+
frontierCallSeq: frontier === undefined ? null : history.nodes[frontier]?.callSeq ?? null,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A confirmation only covers an earlier executable call with no persisted result. */
|
|
43
|
+
export function isConfirmableNoop(timeline, callSeq, resultEventIndex) {
|
|
44
|
+
const call = timeline.executableCalls.get(callSeq)
|
|
45
|
+
return call !== undefined && call.eventIndex < resultEventIndex && !timeline.results.has(callSeq)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function applyOperations(state, operations, nodeIndex) {
|
|
49
|
+
const transition = reduceStateOperations(state, operations, nodeIndex)
|
|
50
|
+
state.head = transition.head
|
|
51
|
+
state.checkpoints = transition.checkpoints
|
|
52
|
+
if (transition.restored) {
|
|
53
|
+
state.trusted = true
|
|
54
|
+
state.volatileSuffix.length = 0
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function applyRecord(state, record, invalidCallSeqs) {
|
|
59
|
+
const { call, code, result } = record
|
|
60
|
+
if (invalidCallSeqs.has(call.seq)) return
|
|
61
|
+
const journal = result.journal
|
|
62
|
+
if (journal.status === 'noop') return
|
|
63
|
+
if (journal.status === 'discarded') {
|
|
64
|
+
if (journal.volatileReason !== undefined) {
|
|
65
|
+
state.trusted = false
|
|
66
|
+
state.volatileSuffix.push({ seq: call.seq, code, reason: journal.volatileReason })
|
|
67
|
+
}
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
if (journal.status === 'volatile') {
|
|
71
|
+
state.trusted = false
|
|
72
|
+
state.volatileSuffix.push({ seq: call.seq, code, reason: journal.volatileReason ?? 'volatile cell' })
|
|
73
|
+
applyOperations(state, journal.operations, undefined)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
if (!state.trusted && !state.surfaceContracted) {
|
|
77
|
+
state.trusted = true
|
|
78
|
+
state.volatileSuffix.length = 0
|
|
79
|
+
}
|
|
80
|
+
const node = Object.freeze({
|
|
81
|
+
code,
|
|
82
|
+
journal,
|
|
83
|
+
callSeq: call.seq,
|
|
84
|
+
parent: state.head,
|
|
85
|
+
...(result.userBindings === undefined ? {} : { userBindings: result.userBindings }),
|
|
86
|
+
})
|
|
87
|
+
const index = state.nodes.push(node) - 1
|
|
88
|
+
state.head = index
|
|
89
|
+
try {
|
|
90
|
+
applyOperations(state, journal.operations, index)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
state.nodes.pop()
|
|
93
|
+
state.head = node.parent
|
|
94
|
+
throw error
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function recordEventSeq(record) {
|
|
99
|
+
return record.result?.eventSeq ?? record.call.seq
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function foldRecords(records, invalidCallSeqs, options = {}) {
|
|
103
|
+
const state = {
|
|
104
|
+
nodes: [],
|
|
105
|
+
head: undefined,
|
|
106
|
+
checkpoints: new Map(),
|
|
107
|
+
volatileSuffix: [],
|
|
108
|
+
trusted: true,
|
|
109
|
+
surfaceContracted: options.surfaceContracted === true,
|
|
110
|
+
}
|
|
111
|
+
for (const record of records) {
|
|
112
|
+
applyRecord(state, record, invalidCallSeqs)
|
|
113
|
+
}
|
|
114
|
+
return state
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function dependsOn(nodes, index, ancestor) {
|
|
118
|
+
for (let cursor = index; cursor !== undefined; cursor = nodes[cursor]?.parent) {
|
|
119
|
+
if (cursor === ancestor) return true
|
|
120
|
+
}
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Return executable call sequences whose provenance remains model-visible. */
|
|
125
|
+
export function visibleExecutableCallSeqs(session) {
|
|
126
|
+
const events = sessionEvents(session)
|
|
127
|
+
let nodes
|
|
128
|
+
try {
|
|
129
|
+
nodes = session?.surface?.nodes
|
|
130
|
+
} catch {
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
|
133
|
+
if (!Array.isArray(events) || !Array.isArray(nodes)) return undefined
|
|
134
|
+
const eventBySeq = new Map()
|
|
135
|
+
for (const event of events) eventBySeq.set(event?.seq, event)
|
|
136
|
+
for (const seq of nodes) {
|
|
137
|
+
if (!Number.isSafeInteger(seq) || seq < 0 || eventBySeq.get(seq) === undefined) return undefined
|
|
138
|
+
}
|
|
139
|
+
const visible = new Set(nodes)
|
|
140
|
+
const resultSources = new Set()
|
|
141
|
+
const visibleAssistantCallSeqs = new Set()
|
|
142
|
+
const pendingAssistantCalls = new Map()
|
|
143
|
+
let assistantCallEvidence = false
|
|
144
|
+
for (const event of events) {
|
|
145
|
+
if (event?.type === 'turn/start' || event?.type === 'turn/end') {
|
|
146
|
+
pendingAssistantCalls.clear()
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
if (event?.type === 'assistant/message') {
|
|
150
|
+
pendingAssistantCalls.clear()
|
|
151
|
+
const content = event.data?.message?.content
|
|
152
|
+
if (Array.isArray(content)) {
|
|
153
|
+
for (const part of content) {
|
|
154
|
+
if (part?.type === 'tool-call' && typeof part.id === 'string') {
|
|
155
|
+
const isVisible = visible.has(event.seq)
|
|
156
|
+
if (isVisible) assistantCallEvidence = true
|
|
157
|
+
let occurrences = pendingAssistantCalls.get(part.id)
|
|
158
|
+
if (occurrences === undefined) {
|
|
159
|
+
occurrences = []
|
|
160
|
+
pendingAssistantCalls.set(part.id, occurrences)
|
|
161
|
+
}
|
|
162
|
+
occurrences.push(isVisible)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
if (event?.type === 'tool/call' && typeof event.data?.callId === 'string') {
|
|
169
|
+
const occurrences = pendingAssistantCalls.get(event.data.callId)
|
|
170
|
+
if (occurrences !== undefined && occurrences.length > 0) {
|
|
171
|
+
const isVisible = occurrences.shift()
|
|
172
|
+
if (occurrences.length === 0) pendingAssistantCalls.delete(event.data.callId)
|
|
173
|
+
if (isVisible && REPL_TOOL_NAMES.has(event.data.name)) {
|
|
174
|
+
visibleAssistantCallSeqs.add(event.seq)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (visible.has(event?.seq)
|
|
179
|
+
&& event?.type === 'tool/result' && Array.isArray(event.sourceEventSeqs)) {
|
|
180
|
+
for (const sourceSeq of event.sourceEventSeqs) {
|
|
181
|
+
if (Number.isSafeInteger(sourceSeq) && sourceSeq >= 0) resultSources.add(sourceSeq)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const calls = new Set()
|
|
186
|
+
for (const event of events) {
|
|
187
|
+
if (event?.type !== 'tool/call' || !REPL_TOOL_NAMES.has(event.data?.name)) continue
|
|
188
|
+
if (assistantCallEvidence
|
|
189
|
+
? visibleAssistantCallSeqs.has(event.seq)
|
|
190
|
+
: (visible.has(event.seq) || resultSources.has(event.seq))) calls.add(event.seq)
|
|
191
|
+
}
|
|
192
|
+
return calls
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function timelineRun(call, result, eventIndex, journal) {
|
|
196
|
+
let args
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(call.data.arguments)
|
|
199
|
+
if (isRecord(parsed)) args = Object.freeze(parsed)
|
|
200
|
+
} catch {
|
|
201
|
+
// Invalid persisted arguments cannot support derived source facts.
|
|
202
|
+
}
|
|
203
|
+
return Object.freeze({
|
|
204
|
+
index: eventIndex,
|
|
205
|
+
callSeq: Number.isSafeInteger(call.seq) ? call.seq : undefined,
|
|
206
|
+
args,
|
|
207
|
+
source: typeof args?.code === 'string' ? args.code : undefined,
|
|
208
|
+
journal,
|
|
209
|
+
rewrites: validatedRewrites(result.data?.meta),
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function timelineDerivedRun(call, result, eventIndex, derived) {
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
index: eventIndex,
|
|
216
|
+
callSeq: Number.isSafeInteger(call.seq) ? call.seq : undefined,
|
|
217
|
+
args: Object.freeze({ description: derived.description }),
|
|
218
|
+
source: derived.code,
|
|
219
|
+
journal: derived.journal,
|
|
220
|
+
rewrites: validatedRewrites(result.data?.meta),
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function successfulTimelineRun(run) {
|
|
225
|
+
return run.journal?.completion?.kind === 'return' && run.journal.status !== 'noop'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function pruneWindowForEvent(event, eventIndex) {
|
|
229
|
+
const shadowedSeqs = event.data.shadowedSeqs
|
|
230
|
+
const seqs = new Set(shadowedSeqs)
|
|
231
|
+
const shadowedRange = event.data.shadowedRange
|
|
232
|
+
const rangeValid = shadowedRange === undefined
|
|
233
|
+
|| (isRecord(shadowedRange)
|
|
234
|
+
&& shadowedRange.start === shadowedSeqs[0]
|
|
235
|
+
&& shadowedRange.end === shadowedSeqs[shadowedSeqs.length - 1])
|
|
236
|
+
const valid = shadowedSeqs.length > 0
|
|
237
|
+
&& seqs.size === shadowedSeqs.length
|
|
238
|
+
&& shadowedSeqs.every(seq => Number.isSafeInteger(seq) && seq >= 0)
|
|
239
|
+
&& shadowedSeqs.every((seq, index) => index === 0 || seq > shadowedSeqs[index - 1])
|
|
240
|
+
&& rangeValid
|
|
241
|
+
return { seqs: valid ? seqs : new Set(), lastEventIndex: eventIndex }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function continuesPruneWindow(window, event, eventIndex) {
|
|
245
|
+
return event?.type === 'compaction/prune' || eventIndex === window.lastEventIndex + 1
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function identifiesPrunedResult(window, event, eventIndex) {
|
|
249
|
+
const sourceSeq = event.sourceEventSeqs?.[0]
|
|
250
|
+
return Number.isSafeInteger(sourceSeq)
|
|
251
|
+
&& window?.seqs.has(sourceSeq)
|
|
252
|
+
&& eventIndex === window.lastEventIndex + 1
|
|
253
|
+
&& Array.isArray(event.sourceEventSeqs) && event.sourceEventSeqs.length === 1
|
|
254
|
+
&& typeof event.data?.message?.source?.callId === 'string'
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function replacesPendingCall(candidate, event, pruneSeq) {
|
|
258
|
+
if (candidate === undefined || candidate === null) return false
|
|
259
|
+
const candidateSeq = candidate.event.seq
|
|
260
|
+
const sourceSeq = event.sourceEventSeqs[0]
|
|
261
|
+
const surfaceOp = event.surfaceOp
|
|
262
|
+
const orderedIdentity = REPL_TOOL_NAMES.has(candidate.event.data.name)
|
|
263
|
+
&& Number.isSafeInteger(candidateSeq) && candidateSeq >= 0
|
|
264
|
+
&& sourceSeq > candidateSeq
|
|
265
|
+
&& Number.isSafeInteger(pruneSeq) && pruneSeq >= 0
|
|
266
|
+
&& Number.isSafeInteger(event.seq) && event.seq - pruneSeq === 1
|
|
267
|
+
&& sourceSeq < pruneSeq
|
|
268
|
+
const exactReplacement = surfaceOp === undefined
|
|
269
|
+
|| (surfaceOp?.op === 'replace' && surfaceOp.start === sourceSeq && surfaceOp.end === sourceSeq)
|
|
270
|
+
return orderedIdentity && exactReplacement
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Fold persisted tool events once into the call/result and edit-target timeline
|
|
275
|
+
* shared by prompt projection and cold journal recovery.
|
|
276
|
+
*/
|
|
277
|
+
export function foldSessionTimeline(events) {
|
|
278
|
+
const empty = {
|
|
279
|
+
openTurn: false,
|
|
280
|
+
executableCalls: new Map(),
|
|
281
|
+
calls: [],
|
|
282
|
+
results: new Map(),
|
|
283
|
+
boundaries: [],
|
|
284
|
+
found: false,
|
|
285
|
+
unavailableResultSeq: undefined,
|
|
286
|
+
lastSuccessfulRunIndex: undefined,
|
|
287
|
+
latestRun: undefined,
|
|
288
|
+
editableRun: undefined,
|
|
289
|
+
editTargets: new Map(),
|
|
290
|
+
}
|
|
291
|
+
if (!Array.isArray(events)) return empty
|
|
292
|
+
|
|
293
|
+
const state = { ...empty }
|
|
294
|
+
const pendingByCallId = new Map()
|
|
295
|
+
const seenCallIds = new Set()
|
|
296
|
+
const claimedEditTargets = new Set()
|
|
297
|
+
const editClaims = new Map()
|
|
298
|
+
const journalResultSeqs = new Set()
|
|
299
|
+
let pruneReplacementWindow
|
|
300
|
+
let scope = 0
|
|
301
|
+
|
|
302
|
+
const resetTurn = (openTurn) => {
|
|
303
|
+
state.openTurn = openTurn
|
|
304
|
+
scope += 1
|
|
305
|
+
pendingByCallId.clear()
|
|
306
|
+
seenCallIds.clear()
|
|
307
|
+
claimedEditTargets.clear()
|
|
308
|
+
state.latestRun = undefined
|
|
309
|
+
state.editableRun = undefined
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (let eventIndex = 0; eventIndex < events.length; eventIndex += 1) {
|
|
313
|
+
const event = events[eventIndex]
|
|
314
|
+
if (pruneReplacementWindow !== undefined
|
|
315
|
+
&& !continuesPruneWindow(pruneReplacementWindow, event, eventIndex)) {
|
|
316
|
+
pruneReplacementWindow = undefined
|
|
317
|
+
}
|
|
318
|
+
if (event?.type === 'turn/start') {
|
|
319
|
+
resetTurn(true)
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
322
|
+
if (event?.type === 'turn/end') {
|
|
323
|
+
resetTurn(false)
|
|
324
|
+
continue
|
|
325
|
+
}
|
|
326
|
+
if (event?.type === RECOVERY_BOUNDARY_EVENT) {
|
|
327
|
+
state.unavailableResultSeq ??= Number.isSafeInteger(event.seq) ? event.seq : eventIndex
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
if (event?.type === 'compaction/prune' && Array.isArray(event.data?.shadowedSeqs)) {
|
|
331
|
+
pruneReplacementWindow = pruneWindowForEvent(event, eventIndex)
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
if (event?.type === 'tool/call' && typeof event.data?.callId === 'string') {
|
|
335
|
+
const executable = REPL_TOOL_NAMES.has(event.data.name)
|
|
336
|
+
let entry = { event, eventIndex, scope }
|
|
337
|
+
if (executable) {
|
|
338
|
+
if (Number.isSafeInteger(event.seq) && event.seq >= 0 && state.executableCalls.has(event.seq)) {
|
|
339
|
+
// A sequence collision disproves both sources, but not the earlier
|
|
340
|
+
// verified frontier. Keep its position for persistent contraction.
|
|
341
|
+
const previous = state.executableCalls.get(event.seq)
|
|
342
|
+
previous.ambiguous = true
|
|
343
|
+
state.unavailableResultSeq = Math.min(state.unavailableResultSeq ?? event.seq, event.seq)
|
|
344
|
+
state.found = true
|
|
345
|
+
state.latestRun = undefined
|
|
346
|
+
state.editableRun = undefined
|
|
347
|
+
pendingByCallId.set(previous.event.data.callId, null)
|
|
348
|
+
pendingByCallId.set(event.data.callId, null)
|
|
349
|
+
seenCallIds.add(event.data.callId)
|
|
350
|
+
continue
|
|
351
|
+
}
|
|
352
|
+
if (event.data.name === 'edit_run_code') {
|
|
353
|
+
const targetCallSeq = state.editableRun?.callSeq
|
|
354
|
+
const target = targetCallSeq !== undefined && state.editableRun.source !== undefined
|
|
355
|
+
&& !claimedEditTargets.has(targetCallSeq)
|
|
356
|
+
? Object.freeze({ source: state.editableRun.source, callSeq: targetCallSeq })
|
|
357
|
+
: undefined
|
|
358
|
+
entry = { ...entry, editTarget: target }
|
|
359
|
+
state.editTargets.set(event.seq, target)
|
|
360
|
+
if (target !== undefined) {
|
|
361
|
+
claimedEditTargets.add(target.callSeq)
|
|
362
|
+
editClaims.set(event.seq, target.callSeq)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
state.calls.push(event)
|
|
366
|
+
if (Number.isSafeInteger(event.seq) && event.seq >= 0) {
|
|
367
|
+
state.executableCalls.set(event.seq, entry)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (seenCallIds.has(event.data.callId)) pendingByCallId.set(event.data.callId, null)
|
|
371
|
+
else {
|
|
372
|
+
seenCallIds.add(event.data.callId)
|
|
373
|
+
pendingByCallId.set(event.data.callId, entry)
|
|
374
|
+
}
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
if (event?.type !== 'tool/result') continue
|
|
378
|
+
|
|
379
|
+
const sourceSeq = event.sourceEventSeqs?.[0]
|
|
380
|
+
const callId = event.data?.message?.source?.callId
|
|
381
|
+
let entry = Number.isSafeInteger(sourceSeq) ? state.executableCalls.get(sourceSeq) : undefined
|
|
382
|
+
let prunedReplacement = false
|
|
383
|
+
if (entry === undefined && identifiesPrunedResult(pruneReplacementWindow, event, eventIndex)) {
|
|
384
|
+
const candidate = pendingByCallId.get(callId)
|
|
385
|
+
const pruneSeq = events[pruneReplacementWindow.lastEventIndex]?.seq
|
|
386
|
+
if (replacesPendingCall(candidate, event, pruneSeq)) {
|
|
387
|
+
entry = candidate
|
|
388
|
+
prunedReplacement = true
|
|
389
|
+
pruneReplacementWindow.lastEventIndex = eventIndex
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (entry === undefined && !Number.isSafeInteger(sourceSeq) && typeof callId === 'string') {
|
|
393
|
+
entry = pendingByCallId.get(callId)
|
|
394
|
+
}
|
|
395
|
+
if (typeof callId === 'string') pendingByCallId.delete(callId)
|
|
396
|
+
if (entry === null) {
|
|
397
|
+
state.latestRun = undefined
|
|
398
|
+
state.editableRun = undefined
|
|
399
|
+
continue
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const call = entry?.event
|
|
403
|
+
if (entry?.ambiguous) {
|
|
404
|
+
state.latestRun = undefined
|
|
405
|
+
state.editableRun = undefined
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
const callSeq = call?.seq
|
|
409
|
+
const meta = event.data?.meta
|
|
410
|
+
let normalized
|
|
411
|
+
if (isRecord(meta) && Object.hasOwn(meta, JOURNAL_KEY)) {
|
|
412
|
+
if (Number.isSafeInteger(sourceSeq) && journalResultSeqs.has(sourceSeq)) {
|
|
413
|
+
state.unavailableResultSeq ??= sourceSeq
|
|
414
|
+
state.results.set(sourceSeq, {
|
|
415
|
+
eventSeq: Number.isSafeInteger(event.seq) && event.seq >= 0 ? event.seq : sourceSeq,
|
|
416
|
+
eventIndex,
|
|
417
|
+
error: `session log contains duplicate PTC journal results for call seq ${sourceSeq}`,
|
|
418
|
+
})
|
|
419
|
+
state.found = true
|
|
420
|
+
continue
|
|
421
|
+
}
|
|
422
|
+
if (Number.isSafeInteger(sourceSeq)) journalResultSeqs.add(sourceSeq)
|
|
423
|
+
if (entry === undefined) {
|
|
424
|
+
if (Number.isSafeInteger(sourceSeq)) {
|
|
425
|
+
state.unavailableResultSeq ??= sourceSeq
|
|
426
|
+
}
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
const resultSeq = prunedReplacement
|
|
430
|
+
? callSeq
|
|
431
|
+
: Number.isSafeInteger(sourceSeq) ? sourceSeq : callSeq
|
|
432
|
+
const raw = {
|
|
433
|
+
meta,
|
|
434
|
+
eventSeq: Number.isSafeInteger(event.seq) && event.seq >= 0 ? event.seq : resultSeq,
|
|
435
|
+
eventIndex,
|
|
436
|
+
}
|
|
437
|
+
if (Object.hasOwn(meta, RECOVERY_BOUNDARY_KEY)) {
|
|
438
|
+
try {
|
|
439
|
+
state.boundaries.push(...normalizeRecoveryBoundaries(
|
|
440
|
+
meta[RECOVERY_BOUNDARY_KEY], raw.eventSeq,
|
|
441
|
+
))
|
|
442
|
+
} catch {
|
|
443
|
+
state.unavailableResultSeq ??= callSeq
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
if (call.data.name === 'edit_run_code') {
|
|
448
|
+
const derived = normalizeDerivedEditResult(meta, entry.editTarget?.callSeq)
|
|
449
|
+
normalized = {
|
|
450
|
+
...raw,
|
|
451
|
+
journal: derived.journal,
|
|
452
|
+
derived,
|
|
453
|
+
...(derived.userBindings === undefined ? {} : { userBindings: derived.userBindings }),
|
|
454
|
+
}
|
|
455
|
+
} else if (call.data.name === 'run_code') {
|
|
456
|
+
const rawJournal = meta[JOURNAL_KEY]
|
|
457
|
+
const resolveLegacyConfirm = rawJournal?.version === LEGACY_JOURNAL_VERSION
|
|
458
|
+
? legacyCallId => {
|
|
459
|
+
const candidates = [...state.executableCalls.values()]
|
|
460
|
+
.filter(candidate => candidate.eventIndex < eventIndex
|
|
461
|
+
&& candidate.event.data?.name === 'run_code'
|
|
462
|
+
&& candidate.event.data.callId === legacyCallId
|
|
463
|
+
&& !state.results.has(candidate.event.seq))
|
|
464
|
+
return candidates.length === 1 ? candidates[0].event.seq : undefined
|
|
465
|
+
}
|
|
466
|
+
: undefined
|
|
467
|
+
const journal = normalizeJournal(rawJournal, { resolveLegacyConfirm })
|
|
468
|
+
const userBindings = userBindingsForJournal(meta, journal)
|
|
469
|
+
normalized = {
|
|
470
|
+
...raw,
|
|
471
|
+
journal,
|
|
472
|
+
...(userBindings === undefined ? {} : { userBindings }),
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
normalized = { ...raw, error: error.message }
|
|
477
|
+
}
|
|
478
|
+
if (Number.isSafeInteger(resultSeq)) state.results.set(resultSeq, normalized)
|
|
479
|
+
state.found = true
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const claimedTarget = editClaims.get(callSeq)
|
|
483
|
+
if (claimedTarget !== undefined) {
|
|
484
|
+
editClaims.delete(callSeq)
|
|
485
|
+
if (normalized?.derived === undefined) claimedEditTargets.delete(claimedTarget)
|
|
486
|
+
}
|
|
487
|
+
if (entry === undefined || entry.scope !== scope) continue
|
|
488
|
+
if (call.data.name === 'edit_run_code') {
|
|
489
|
+
if (normalized?.derived !== undefined) {
|
|
490
|
+
const run = timelineDerivedRun(call, event, eventIndex, normalized.derived)
|
|
491
|
+
state.latestRun = run
|
|
492
|
+
state.editableRun = run
|
|
493
|
+
if (successfulTimelineRun(run)) state.lastSuccessfulRunIndex = eventIndex
|
|
494
|
+
}
|
|
495
|
+
continue
|
|
496
|
+
}
|
|
497
|
+
if (call.data.name !== 'run_code') {
|
|
498
|
+
// Unrelated native settlements do not change the current editable cell.
|
|
499
|
+
// The edit target is captured at dispatch time and remains valid until a
|
|
500
|
+
// new executable cell or turn boundary supersedes it.
|
|
501
|
+
continue
|
|
502
|
+
}
|
|
503
|
+
const run = timelineRun(call, event, eventIndex, normalized?.journal)
|
|
504
|
+
state.latestRun = run
|
|
505
|
+
state.editableRun = run
|
|
506
|
+
if (successfulTimelineRun(run)) state.lastSuccessfulRunIndex = eventIndex
|
|
507
|
+
}
|
|
508
|
+
return state
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Fold the session log into the last exactly replayable frontier. */
|
|
512
|
+
export function recoverJournal(session, currentCallSeq, options = {}) {
|
|
513
|
+
const events = sessionEvents(session)
|
|
514
|
+
if (!Array.isArray(events)) {
|
|
515
|
+
return { nodes: [], head: undefined, checkpoints: new Map(), volatileSuffix: [], available: true }
|
|
516
|
+
}
|
|
517
|
+
const timeline = foldSessionTimeline(events)
|
|
518
|
+
const calls = timeline.calls.filter(call => call.seq !== currentCallSeq)
|
|
519
|
+
const { executableCalls, results, found } = timeline
|
|
520
|
+
const visibleCalls = options?.visibleCallSeqs
|
|
521
|
+
const invalidCallSeqs = new Set()
|
|
522
|
+
let unavailableBoundary
|
|
523
|
+
const registerUnavailable = ({ seq, reason, code }, recoveryState) => {
|
|
524
|
+
if (unavailableBoundary === undefined || seq <= unavailableBoundary.seq) {
|
|
525
|
+
unavailableBoundary = { seq, reason }
|
|
526
|
+
}
|
|
527
|
+
if (recoveryState === undefined) return
|
|
528
|
+
invalidCallSeqs.add(seq)
|
|
529
|
+
recoveryState.trusted = false
|
|
530
|
+
if (!recoveryState.volatileSuffix.some(item => item.seq === seq)) {
|
|
531
|
+
recoveryState.volatileSuffix.push({ seq, code, reason })
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (timeline.unavailableResultSeq !== undefined) {
|
|
535
|
+
registerUnavailable({
|
|
536
|
+
seq: timeline.unavailableResultSeq,
|
|
537
|
+
reason: 'unavailable or malformed dsh-ptc-plus journal result',
|
|
538
|
+
})
|
|
539
|
+
}
|
|
540
|
+
for (const [resultSeq, result] of results.entries()) {
|
|
541
|
+
if (result?.error !== undefined && Number.isSafeInteger(resultSeq)) {
|
|
542
|
+
registerUnavailable({ seq: resultSeq, reason: result.error })
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const extraBoundaries = options?.extraBoundaries ?? []
|
|
546
|
+
let normalizedExtraBoundaries = []
|
|
547
|
+
try {
|
|
548
|
+
normalizedExtraBoundaries = normalizeRecoveryBoundaries(extraBoundaries, undefined)
|
|
549
|
+
} catch {
|
|
550
|
+
normalizedExtraBoundaries = []
|
|
551
|
+
}
|
|
552
|
+
const boundaries = [
|
|
553
|
+
...timeline.boundaries,
|
|
554
|
+
...normalizedExtraBoundaries.map(boundary => ({
|
|
555
|
+
...boundary,
|
|
556
|
+
eventSeq: Number.POSITIVE_INFINITY,
|
|
557
|
+
})),
|
|
558
|
+
].sort((left, right) => left.eventSeq - right.eventSeq)
|
|
559
|
+
// An unavailable historical result contracts the replay frontier. It must
|
|
560
|
+
// not prevent the current request from executing on the verified prefix.
|
|
561
|
+
|
|
562
|
+
const confirmedNoops = new Set()
|
|
563
|
+
const invalidResultReasons = new Map()
|
|
564
|
+
for (const [resultSeq, { journal, eventIndex }] of results.entries()) {
|
|
565
|
+
const candidateConfirms = []
|
|
566
|
+
for (const callSeq of journal?.confirms ?? []) {
|
|
567
|
+
if (!isConfirmableNoop(timeline, callSeq, eventIndex)) {
|
|
568
|
+
const reason = `confirmed no-op does not identify an earlier unjournaled run_code call seq ${callSeq}`
|
|
569
|
+
invalidResultReasons.set(resultSeq, reason)
|
|
570
|
+
registerUnavailable({ seq: resultSeq, reason })
|
|
571
|
+
break
|
|
572
|
+
}
|
|
573
|
+
candidateConfirms.push(callSeq)
|
|
574
|
+
}
|
|
575
|
+
if (!invalidResultReasons.has(resultSeq)) {
|
|
576
|
+
for (const callSeq of candidateConfirms) confirmedNoops.add(callSeq)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const records = []
|
|
580
|
+
const orderedRecords = []
|
|
581
|
+
let state = foldRecords(records, invalidCallSeqs)
|
|
582
|
+
let boundaryIndex = 0
|
|
583
|
+
const applyBoundariesBefore = (seq) => {
|
|
584
|
+
while (boundaryIndex < boundaries.length && boundaries[boundaryIndex].eventSeq <= seq) {
|
|
585
|
+
const boundary = boundaries[boundaryIndex++]
|
|
586
|
+
const failedCall = executableCalls.get(boundary.failedCallSeq)
|
|
587
|
+
if (failedCall === undefined || failedCall.event.seq >= boundary.eventSeq) continue
|
|
588
|
+
const failedIndex = state.nodes.findIndex(node => node.callSeq === boundary.failedCallSeq)
|
|
589
|
+
const expectedFrontier = failedIndex < 0 ? state.head : state.nodes[failedIndex].parent
|
|
590
|
+
const frontierIndex = boundary.frontierCallSeq === null
|
|
591
|
+
? undefined
|
|
592
|
+
: state.nodes.findIndex(node => node.callSeq === boundary.frontierCallSeq)
|
|
593
|
+
if (frontierIndex !== expectedFrontier) continue
|
|
594
|
+
const resolvesUnavailable = unavailableBoundary?.seq === boundary.failedCallSeq
|
|
595
|
+
if (failedIndex < 0 && !resolvesUnavailable) continue
|
|
596
|
+
for (let index = 0; index < state.nodes.length; index += 1) {
|
|
597
|
+
if (dependsOn(state.nodes, index, failedIndex)) invalidCallSeqs.add(state.nodes[index].callSeq)
|
|
598
|
+
}
|
|
599
|
+
if (failedIndex < 0) {
|
|
600
|
+
for (const record of records) {
|
|
601
|
+
if (record.call.seq >= boundary.failedCallSeq) invalidCallSeqs.add(record.call.seq)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
state = foldRecords(records, invalidCallSeqs, {
|
|
605
|
+
surfaceContracted: resolvesUnavailable ? false : state.surfaceContracted,
|
|
606
|
+
})
|
|
607
|
+
if (resolvesUnavailable) {
|
|
608
|
+
unavailableBoundary = undefined
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
for (const call of calls) {
|
|
613
|
+
const result = results.get(call.seq)
|
|
614
|
+
const code = call.data?.name === 'edit_run_code' ? result?.derived?.code : sourceForRunCall(call)
|
|
615
|
+
const record = {
|
|
616
|
+
call,
|
|
617
|
+
code,
|
|
618
|
+
result,
|
|
619
|
+
hidden: visibleCalls instanceof Set && !visibleCalls.has(call.seq),
|
|
620
|
+
}
|
|
621
|
+
if (call.data?.name === 'edit_run_code' && record.code === undefined && result === undefined) continue
|
|
622
|
+
if (result?.journal === undefined && confirmedNoops.has(call.seq)) continue
|
|
623
|
+
orderedRecords.push(record)
|
|
624
|
+
}
|
|
625
|
+
orderedRecords.sort((left, right) => (
|
|
626
|
+
recordEventSeq(left) - recordEventSeq(right) || left.call.seq - right.call.seq
|
|
627
|
+
))
|
|
628
|
+
for (const record of orderedRecords) {
|
|
629
|
+
applyBoundariesBefore(recordEventSeq(record))
|
|
630
|
+
records.push(record)
|
|
631
|
+
if (record.hidden) {
|
|
632
|
+
state.surfaceContracted = true
|
|
633
|
+
registerUnavailable({
|
|
634
|
+
seq: record.call.seq,
|
|
635
|
+
reason: 'model-visible provenance was shadowed',
|
|
636
|
+
}, state)
|
|
637
|
+
continue
|
|
638
|
+
}
|
|
639
|
+
if (unavailableBoundary !== undefined && record.call.seq >= unavailableBoundary.seq) {
|
|
640
|
+
registerUnavailable({
|
|
641
|
+
seq: record.call.seq,
|
|
642
|
+
code: record.code,
|
|
643
|
+
reason: unavailableBoundary.reason,
|
|
644
|
+
}, state)
|
|
645
|
+
continue
|
|
646
|
+
}
|
|
647
|
+
const invalidReason = invalidResultReasons.get(record.call.seq)
|
|
648
|
+
if (record.result?.journal === undefined || invalidReason !== undefined) {
|
|
649
|
+
registerUnavailable({
|
|
650
|
+
seq: record.call.seq,
|
|
651
|
+
code: record.code,
|
|
652
|
+
reason: invalidReason ?? record.result?.error ?? 'missing dsh-ptc-plus journal result',
|
|
653
|
+
}, state)
|
|
654
|
+
continue
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
applyRecord(state, record, invalidCallSeqs)
|
|
658
|
+
} catch (error) {
|
|
659
|
+
registerUnavailable({ seq: record.call.seq, code: record.code, reason: error.message }, state)
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
applyBoundariesBefore(Number.POSITIVE_INFINITY)
|
|
663
|
+
if (unavailableBoundary !== undefined) registerUnavailable(unavailableBoundary, state)
|
|
664
|
+
return {
|
|
665
|
+
nodes: state.nodes,
|
|
666
|
+
head: state.head,
|
|
667
|
+
checkpoints: state.checkpoints,
|
|
668
|
+
volatileSuffix: state.volatileSuffix,
|
|
669
|
+
available: found && !state.surfaceContracted && unavailableBoundary === undefined,
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** Return source nodes from the empty state to a selected durable head. */
|
|
674
|
+
export function pathToHead(state) {
|
|
675
|
+
const path = []
|
|
676
|
+
for (let cursor = state.head; cursor !== undefined;) {
|
|
677
|
+
const node = state.nodes[cursor]
|
|
678
|
+
if (node === undefined) throw new Error('invalid dsh-ptc-plus journal head')
|
|
679
|
+
path.push(node)
|
|
680
|
+
cursor = node.parent
|
|
681
|
+
}
|
|
682
|
+
path.reverse()
|
|
683
|
+
return path
|
|
684
|
+
}
|