@openprd/cli 0.1.18 → 0.1.21

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 (118) hide show
  1. package/.openprd/changes/openprd-control-plane-v020/.openprd.yaml +2 -0
  2. package/.openprd/changes/openprd-control-plane-v020/design.md +78 -0
  3. package/.openprd/changes/openprd-control-plane-v020/proposal.md +54 -0
  4. package/.openprd/changes/openprd-control-plane-v020/specs/agent-requirements/spec.md +16 -0
  5. package/.openprd/changes/openprd-control-plane-v020/task-events.jsonl +27 -0
  6. package/.openprd/changes/openprd-control-plane-v020/tasks-002.md +35 -0
  7. package/.openprd/changes/openprd-control-plane-v020/tasks.md +427 -0
  8. package/.openprd/changes/openprd-generated-change/.openprd.yaml +2 -0
  9. package/.openprd/changes/openprd-generated-change/design.md +50 -0
  10. package/.openprd/changes/openprd-generated-change/proposal.md +37 -0
  11. package/.openprd/changes/openprd-generated-change/specs/agent-requirements/spec.md +16 -0
  12. package/.openprd/changes/openprd-generated-change/task-events.jsonl +21 -0
  13. package/.openprd/changes/openprd-generated-change/tasks.md +357 -0
  14. package/.openprd/changes/remove-session-tracking-humane-approval/.openprd.yaml +2 -0
  15. package/.openprd/changes/remove-session-tracking-humane-approval/design.md +52 -0
  16. package/.openprd/changes/remove-session-tracking-humane-approval/proposal.md +35 -0
  17. package/.openprd/changes/remove-session-tracking-humane-approval/specs/agent-requirements/spec.md +16 -0
  18. package/.openprd/changes/remove-session-tracking-humane-approval/task-events.jsonl +1 -0
  19. package/.openprd/changes/remove-session-tracking-humane-approval/tasks.md +170 -0
  20. package/.openprd/design/active/asset-spec.md +19 -14
  21. package/.openprd/design/active/direction-plan.md +19 -3
  22. package/.openprd/design/active/facts-sheet.md +16 -7
  23. package/.openprd/design/active/image-preflight.md +6 -5
  24. package/.openprd/design/active/review-studio-v020-directions/compare-plan.json +34 -0
  25. package/.openprd/design/active/review-studio-v020-directions/contact-sheet.jpg +0 -0
  26. package/.openprd/design/active/review-studio-v020-directions/crops/01.png +0 -0
  27. package/.openprd/design/active/review-studio-v020-directions/crops/02.png +0 -0
  28. package/.openprd/design/active/review-studio-v020-directions/crops/03.png +0 -0
  29. package/.openprd/design/active/review-studio-v020-directions/focus-board.template.json +69 -0
  30. package/.openprd/design/active/review-studio-v020-directions/parallel-board.template.json +45 -0
  31. package/.openprd/design/active/review-studio-v020-directions/reference-set.json +143 -0
  32. package/.openprd/design/active/review-studio-v020-directions/source.png +0 -0
  33. package/.openprd/design/active/selected-direction.md +23 -9
  34. package/.openprd/engagements/active/control-plane-architecture.json +203 -0
  35. package/.openprd/engagements/active/control-plane-intake.json +418 -0
  36. package/.openprd/engagements/active/prd.md +186 -100
  37. package/.openprd/engagements/active/review-presentation-v0018.json +176 -0
  38. package/.openprd/i18n-config.json +12 -0
  39. package/.openprd/ledger/events.jsonl +6 -0
  40. package/.openprd/ledger/heads/ebea1a71a9daa566f1c91b53.json +10 -0
  41. package/.openprd/manifest.json +21 -0
  42. package/AGENTS.md +1 -0
  43. package/README.md +22 -0
  44. package/README_EN.md +27 -0
  45. package/package.json +1 -1
  46. package/skills/openprd-frontend-design/SKILL.md +16 -0
  47. package/skills/openprd-harness/SKILL.md +1 -1
  48. package/skills/openprd-quality/SKILL.md +1 -1
  49. package/skills/openprd-requirement-intake/SKILL.md +1 -1
  50. package/skills/openprd-shared/SKILL.md +1 -1
  51. package/src/adapters/adapter-spi.js +193 -0
  52. package/src/adapters/capability-envelope.js +98 -0
  53. package/src/adapters/event-normalizer.js +55 -0
  54. package/src/adapters/index.js +4 -0
  55. package/src/adapters/install-safety.js +249 -0
  56. package/src/agent-canonical-content.js +6 -2
  57. package/src/agent-integration.js +147 -42
  58. package/src/canvas-app-client-app.js +838 -0
  59. package/src/canvas-app-client-helpers.js +1138 -0
  60. package/src/canvas-app-styles.js +898 -0
  61. package/src/canvas-app.html.js +6 -2781
  62. package/src/canvas-binding.js +414 -0
  63. package/src/canvas-codex-relay.js +430 -0
  64. package/src/canvas-i18n.js +8 -0
  65. package/src/canvas-scene.js +377 -0
  66. package/src/canvas-session-store.js +148 -0
  67. package/src/canvas-workspace.js +59 -1066
  68. package/src/cli/args.js +63 -4
  69. package/src/cli/gate-print.js +17 -0
  70. package/src/cli/quality-commands.js +18 -0
  71. package/src/cli/quality-print.js +10 -0
  72. package/src/cli/runtime-print.js +24 -0
  73. package/src/codex-hook-runner-template.mjs +87 -77
  74. package/src/codex-runtime.js +48 -5
  75. package/src/context/cache.js +245 -0
  76. package/src/context/compiler.js +438 -0
  77. package/src/context/constants.js +30 -0
  78. package/src/context/index.js +39 -0
  79. package/src/context/redaction.js +84 -0
  80. package/src/context/stable.js +69 -0
  81. package/src/context/telemetry.js +42 -0
  82. package/src/dev-standards.js +57 -0
  83. package/src/fleet.js +112 -95
  84. package/src/gates/index.js +2 -0
  85. package/src/gates/scoped-gates.js +256 -0
  86. package/src/gates/store.js +126 -0
  87. package/src/gates/workspace.js +41 -0
  88. package/src/html-artifacts.js +725 -28
  89. package/src/kernel/atomic-store.js +299 -0
  90. package/src/kernel/event-envelope.js +166 -0
  91. package/src/kernel/index.js +4 -0
  92. package/src/kernel/project-ledger.js +467 -0
  93. package/src/kernel/project-manifest.js +205 -0
  94. package/src/knowledge-v3/index.js +1 -0
  95. package/src/knowledge-v3/lifecycle.js +290 -0
  96. package/src/knowledge.js +14 -7
  97. package/src/openprd.js +71 -2
  98. package/src/review-model.js +413 -0
  99. package/src/review-presentation.js +1 -1
  100. package/src/run-harness.js +431 -37
  101. package/src/runtime/cli_runtime_README.md +28 -0
  102. package/src/runtime/errors.js +66 -0
  103. package/src/runtime/index.js +44 -0
  104. package/src/runtime/lane-schema.js +141 -0
  105. package/src/runtime/lane-store.js +279 -0
  106. package/src/runtime/task-runtime.js +449 -0
  107. package/src/runtime/workspace.js +179 -0
  108. package/src/runtime/write-set.js +206 -0
  109. package/src/session-binding.js +16 -3
  110. package/src/session-registry.js +59 -1
  111. package/src/upgrade/fleet-mutation.js +166 -0
  112. package/src/upgrade/fleet-transaction.js +398 -0
  113. package/src/upgrade/transaction-store.js +416 -0
  114. package/src/visual-compare-core.js +66 -27
  115. package/src/visual-compare.js +18 -12
  116. package/src/workspace-core.js +109 -7
  117. package/src/workspace-registry.js +39 -1
  118. package/src/workspace-workflow.js +18 -15
@@ -0,0 +1,414 @@
1
+ /*
2
+ * Core purpose
3
+ * Resolve which conversation (thread/session/workspace) a canvas belongs to
4
+ * and materialize the file-backed session directory for that binding.
5
+ *
6
+ * Inputs
7
+ * Project root plus CLI/env binding hints (--thread/--session/--workspace),
8
+ * with fallback to .openprd/harness/runtime-environment.json evidence.
9
+ *
10
+ * Outputs
11
+ * ensureCanvasSession() creates .openprd/harness/canvas-sessions/<key>/ with
12
+ * session.json, scene.json, ops.json, and library.json scaffolding.
13
+ *
14
+ * Positioning
15
+ * Used by canvas-workspace.js; keep it transport-agnostic so Codex, Claude
16
+ * Code, Cursor, and future clients can all bind by providing evidence.
17
+ */
18
+ import fs from 'node:fs/promises';
19
+ import { cjoin, exists, readJson, writeJson } from './fs-utils.js';
20
+ import { timestamp } from './time.js';
21
+ import {
22
+ CANVAS_SURFACE_BACKGROUND,
23
+ buildSessionKey,
24
+ firstPresent,
25
+ isPlainObject,
26
+ normalizeBindingTitle,
27
+ } from './canvas-session-store.js';
28
+
29
+ const CANVAS_SCHEMA_VERSION = 1;
30
+
31
+ async function readRuntimeEnvironment(projectRoot) {
32
+ const runtimePath = cjoin(projectRoot, '.openprd', 'harness', 'runtime-environment.json');
33
+ if (!(await exists(runtimePath))) {
34
+ return { path: runtimePath, data: null };
35
+ }
36
+ try {
37
+ return { path: runtimePath, data: await readJson(runtimePath) };
38
+ } catch {
39
+ return { path: runtimePath, data: null };
40
+ }
41
+ }
42
+
43
+ function flattenRuntimeEvidence(runtimeEnvironment) {
44
+ const runtime = runtimeEnvironment ?? {};
45
+ const active = runtime.active ?? runtime.activeClient ?? runtime.current ?? {};
46
+ const detection = runtime.runtimeDetection ?? {};
47
+ const detectionActive = detection.active ?? detection.current ?? {};
48
+ const session = runtime.session ?? runtime.conversation ?? detection.session ?? detectionActive.session ?? {};
49
+ const turn = runtime.turn ?? detection.turn ?? detectionActive.turn ?? {};
50
+ const evidence = runtime.evidence ?? detection.evidence ?? detectionActive.evidence ?? {};
51
+ const observed = runtime.observedCapabilities ?? detection.observedCapabilities ?? detectionActive.observedCapabilities ?? [];
52
+ return {
53
+ active,
54
+ detection,
55
+ detectionActive,
56
+ session,
57
+ turn,
58
+ evidence,
59
+ observed,
60
+ };
61
+ }
62
+
63
+ function selectRuntimeBinding(runtimeEnvironment) {
64
+ if (!isPlainObject(runtimeEnvironment)) {
65
+ return null;
66
+ }
67
+ const parts = flattenRuntimeEvidence(runtimeEnvironment);
68
+ const threadId = firstPresent(
69
+ parts.active.threadId,
70
+ parts.active.thread_id,
71
+ parts.active.conversationId,
72
+ parts.active.conversation_id,
73
+ parts.detectionActive.threadId,
74
+ parts.detectionActive.thread_id,
75
+ parts.detectionActive.conversationId,
76
+ parts.detectionActive.conversation_id,
77
+ parts.session.threadId,
78
+ parts.session.thread_id,
79
+ parts.session.conversationId,
80
+ parts.session.conversation_id,
81
+ parts.turn.threadId,
82
+ parts.turn.thread_id,
83
+ parts.evidence.threadId,
84
+ parts.evidence.thread_id,
85
+ );
86
+ if (threadId) {
87
+ const title = normalizeBindingTitle(
88
+ parts.active.threadTitle,
89
+ parts.active.thread_title,
90
+ parts.active.conversationTitle,
91
+ parts.active.conversation_title,
92
+ parts.active.title,
93
+ parts.active.name,
94
+ parts.detectionActive.threadTitle,
95
+ parts.detectionActive.thread_title,
96
+ parts.detectionActive.conversationTitle,
97
+ parts.detectionActive.conversation_title,
98
+ parts.detectionActive.title,
99
+ parts.detectionActive.name,
100
+ parts.session.threadTitle,
101
+ parts.session.thread_title,
102
+ parts.session.conversationTitle,
103
+ parts.session.conversation_title,
104
+ parts.session.title,
105
+ parts.session.name,
106
+ parts.turn.threadTitle,
107
+ parts.turn.thread_title,
108
+ parts.turn.conversationTitle,
109
+ parts.turn.conversation_title,
110
+ parts.evidence.threadTitle,
111
+ parts.evidence.thread_title,
112
+ parts.evidence.conversationTitle,
113
+ parts.evidence.conversation_title,
114
+ );
115
+ return {
116
+ kind: 'thread',
117
+ id: threadId,
118
+ title,
119
+ threadTitle: title,
120
+ source: 'runtime-environment:thread',
121
+ activeClient: firstPresent(parts.active.client, parts.active.activeClient, parts.detectionActive.activeClient, parts.detection.activeClient),
122
+ surface: firstPresent(parts.active.surface, parts.detectionActive.surface, parts.detection.surface),
123
+ confidence: firstPresent(parts.active.confidence, parts.detectionActive.confidence, parts.detection.confidence),
124
+ observedCapabilities: parts.observed,
125
+ };
126
+ }
127
+
128
+ const sessionId = firstPresent(
129
+ parts.active.sessionId,
130
+ parts.active.session_id,
131
+ parts.detectionActive.sessionId,
132
+ parts.detectionActive.session_id,
133
+ parts.session.id,
134
+ parts.session.sessionId,
135
+ parts.session.session_id,
136
+ parts.turn.sessionId,
137
+ parts.turn.session_id,
138
+ parts.evidence.sessionId,
139
+ parts.evidence.session_id,
140
+ );
141
+ if (sessionId) {
142
+ const title = normalizeBindingTitle(
143
+ parts.active.sessionTitle,
144
+ parts.active.session_title,
145
+ parts.active.conversationTitle,
146
+ parts.active.conversation_title,
147
+ parts.active.title,
148
+ parts.active.name,
149
+ parts.detectionActive.sessionTitle,
150
+ parts.detectionActive.session_title,
151
+ parts.detectionActive.conversationTitle,
152
+ parts.detectionActive.conversation_title,
153
+ parts.detectionActive.title,
154
+ parts.detectionActive.name,
155
+ parts.session.sessionTitle,
156
+ parts.session.session_title,
157
+ parts.session.conversationTitle,
158
+ parts.session.conversation_title,
159
+ parts.session.title,
160
+ parts.session.name,
161
+ parts.turn.sessionTitle,
162
+ parts.turn.session_title,
163
+ parts.turn.conversationTitle,
164
+ parts.turn.conversation_title,
165
+ parts.evidence.sessionTitle,
166
+ parts.evidence.session_title,
167
+ parts.evidence.conversationTitle,
168
+ parts.evidence.conversation_title,
169
+ );
170
+ return {
171
+ kind: 'session',
172
+ id: sessionId,
173
+ title,
174
+ sessionTitle: title,
175
+ source: 'runtime-environment:session',
176
+ activeClient: firstPresent(parts.active.client, parts.active.activeClient, parts.detectionActive.activeClient, parts.detection.activeClient),
177
+ surface: firstPresent(parts.active.surface, parts.detectionActive.surface, parts.detection.surface),
178
+ confidence: firstPresent(parts.active.confidence, parts.detectionActive.confidence, parts.detection.confidence),
179
+ observedCapabilities: parts.observed,
180
+ };
181
+ }
182
+
183
+ return null;
184
+ }
185
+
186
+ async function resolveCanvasSessionBinding(projectRoot, options = {}, env = process.env) {
187
+ const explicitThread = firstPresent(options.thread, options.conversation, options.conversationId);
188
+ if (explicitThread) {
189
+ const title = normalizeBindingTitle(options.threadTitle, options.conversationTitle, options.title);
190
+ return {
191
+ kind: 'thread',
192
+ id: explicitThread,
193
+ displayId: explicitThread,
194
+ title,
195
+ threadTitle: title,
196
+ source: 'flag:thread',
197
+ confidence: 'explicit',
198
+ sessionKey: buildSessionKey('thread', explicitThread),
199
+ };
200
+ }
201
+
202
+ const explicitSession = firstPresent(options.session, options.sessionId);
203
+ if (explicitSession) {
204
+ const title = normalizeBindingTitle(options.sessionTitle, options.conversationTitle, options.title);
205
+ return {
206
+ kind: 'session',
207
+ id: explicitSession,
208
+ displayId: explicitSession,
209
+ title,
210
+ sessionTitle: title,
211
+ source: 'flag:session',
212
+ confidence: 'explicit',
213
+ sessionKey: buildSessionKey('session', explicitSession),
214
+ };
215
+ }
216
+
217
+ const envThread = firstPresent(
218
+ env.OPENPRD_THREAD_ID,
219
+ env.OPENPRD_CONVERSATION_ID,
220
+ env.CODEX_THREAD_ID,
221
+ env.CODEX_CONVERSATION_ID,
222
+ env.CLAUDE_THREAD_ID,
223
+ env.CURSOR_THREAD_ID,
224
+ );
225
+ if (envThread) {
226
+ const title = normalizeBindingTitle(
227
+ env.OPENPRD_THREAD_TITLE,
228
+ env.OPENPRD_CONVERSATION_TITLE,
229
+ env.CODEX_THREAD_TITLE,
230
+ env.CODEX_CONVERSATION_TITLE,
231
+ env.CLAUDE_THREAD_TITLE,
232
+ env.CLAUDE_CONVERSATION_TITLE,
233
+ env.CURSOR_THREAD_TITLE,
234
+ env.CURSOR_CONVERSATION_TITLE,
235
+ );
236
+ return {
237
+ kind: 'thread',
238
+ id: envThread,
239
+ displayId: envThread,
240
+ title,
241
+ threadTitle: title,
242
+ source: 'env:thread',
243
+ confidence: 'high',
244
+ sessionKey: buildSessionKey('thread', envThread),
245
+ };
246
+ }
247
+
248
+ const envSession = firstPresent(
249
+ env.OPENPRD_SESSION_ID,
250
+ env.CODEX_SESSION_ID,
251
+ env.CLAUDE_SESSION_ID,
252
+ env.CLAUDE_CODE_SESSION_ID,
253
+ env.CLAUDE_CODE_CHILD_SESSION,
254
+ env.CURSOR_SESSION_ID,
255
+ );
256
+ if (envSession) {
257
+ const title = normalizeBindingTitle(
258
+ env.OPENPRD_SESSION_TITLE,
259
+ env.OPENPRD_CONVERSATION_TITLE,
260
+ env.CODEX_SESSION_TITLE,
261
+ env.CODEX_CONVERSATION_TITLE,
262
+ env.CLAUDE_SESSION_TITLE,
263
+ env.CLAUDE_CONVERSATION_TITLE,
264
+ env.CURSOR_SESSION_TITLE,
265
+ env.CURSOR_CONVERSATION_TITLE,
266
+ );
267
+ return {
268
+ kind: 'session',
269
+ id: envSession,
270
+ displayId: envSession,
271
+ title,
272
+ sessionTitle: title,
273
+ source: 'env:session',
274
+ confidence: 'medium-high',
275
+ sessionKey: buildSessionKey('session', envSession),
276
+ };
277
+ }
278
+
279
+ const runtime = await readRuntimeEnvironment(projectRoot);
280
+ const runtimeBinding = selectRuntimeBinding(runtime.data);
281
+ if (runtimeBinding) {
282
+ const binding = {
283
+ ...runtimeBinding,
284
+ displayId: runtimeBinding.id,
285
+ runtimePath: runtime.path,
286
+ sessionKey: buildSessionKey(runtimeBinding.kind, runtimeBinding.id),
287
+ };
288
+ const title = normalizeBindingTitle(runtimeBinding.title, runtimeBinding.threadTitle, runtimeBinding.sessionTitle, runtimeBinding.name);
289
+ if (title) {
290
+ binding.title = title;
291
+ if (binding.kind === 'thread') {
292
+ binding.threadTitle = title;
293
+ }
294
+ if (binding.kind === 'session') {
295
+ binding.sessionTitle = title;
296
+ }
297
+ }
298
+ return binding;
299
+ }
300
+
301
+ const workspaceId = shortHash(projectRoot);
302
+ return {
303
+ kind: 'workspace',
304
+ id: workspaceId,
305
+ displayId: `workspace-${workspaceId}`,
306
+ source: 'workspace-fallback',
307
+ confidence: 'low',
308
+ sessionKey: buildSessionKey('workspace', workspaceId),
309
+ note: 'No current conversation evidence was found; pass --thread or --session to avoid sharing a fallback canvas.',
310
+ };
311
+ }
312
+
313
+ function buildCanvasSessionPaths(projectRoot, binding) {
314
+ const sessionDir = cjoin(projectRoot, '.openprd', 'harness', 'canvas-sessions', binding.sessionKey);
315
+ return {
316
+ sessionDir,
317
+ sessionFile: cjoin(sessionDir, 'session.json'),
318
+ sceneFile: cjoin(sessionDir, 'scene.json'),
319
+ libraryFile: cjoin(sessionDir, 'library.json'),
320
+ opsFile: cjoin(sessionDir, 'ops.json'),
321
+ serverFile: cjoin(sessionDir, 'server.json'),
322
+ logFile: cjoin(sessionDir, 'server.log'),
323
+ assetsDir: cjoin(sessionDir, 'assets'),
324
+ exportsDir: cjoin(sessionDir, 'exports'),
325
+ relaysDir: cjoin(sessionDir, 'relays'),
326
+ };
327
+ }
328
+
329
+ function preserveCanvasBindingDisplayTitle(binding, existingBinding) {
330
+ if (!isPlainObject(binding) || !isPlainObject(existingBinding)) {
331
+ return binding;
332
+ }
333
+ if (binding.kind !== existingBinding.kind || binding.id !== existingBinding.id) {
334
+ return binding;
335
+ }
336
+ const title = normalizeBindingTitle(
337
+ binding.title,
338
+ binding.threadTitle,
339
+ binding.sessionTitle,
340
+ binding.name,
341
+ existingBinding.title,
342
+ existingBinding.threadTitle,
343
+ existingBinding.sessionTitle,
344
+ existingBinding.name,
345
+ );
346
+ if (!title) {
347
+ return binding;
348
+ }
349
+ const nextBinding = { ...binding, title };
350
+ if (nextBinding.kind === 'thread') {
351
+ nextBinding.threadTitle = title;
352
+ }
353
+ if (nextBinding.kind === 'session') {
354
+ nextBinding.sessionTitle = title;
355
+ }
356
+ return nextBinding;
357
+ }
358
+
359
+ async function ensureCanvasSession(projectRoot, options = {}, env = process.env) {
360
+ const binding = await resolveCanvasSessionBinding(projectRoot, options, env);
361
+ const paths = buildCanvasSessionPaths(projectRoot, binding);
362
+ await fs.mkdir(paths.assetsDir, { recursive: true });
363
+ await fs.mkdir(paths.exportsDir, { recursive: true });
364
+ await fs.mkdir(paths.relaysDir, { recursive: true });
365
+ const existing = await readJson(paths.sessionFile).catch(() => null);
366
+ const resolvedBinding = preserveCanvasBindingDisplayTitle(binding, existing?.binding);
367
+ const now = timestamp();
368
+ const session = {
369
+ schema: `openprd.canvas.session.v${CANVAS_SCHEMA_VERSION}`,
370
+ version: CANVAS_SCHEMA_VERSION,
371
+ sessionKey: resolvedBinding.sessionKey,
372
+ projectRoot,
373
+ binding: resolvedBinding,
374
+ paths,
375
+ createdAt: existing?.createdAt ?? now,
376
+ updatedAt: now,
377
+ };
378
+ await writeJson(paths.sessionFile, session);
379
+ if (!(await exists(paths.sceneFile))) {
380
+ await writeJson(paths.sceneFile, {
381
+ type: 'openprd.canvas.scene.v1',
382
+ elements: [],
383
+ appState: {
384
+ viewBackgroundColor: CANVAS_SURFACE_BACKGROUND,
385
+ },
386
+ files: {},
387
+ savedAt: now,
388
+ });
389
+ }
390
+ if (!(await exists(paths.opsFile))) {
391
+ await writeJson(paths.opsFile, {
392
+ type: 'openprd.canvas.ops.v1',
393
+ nextSeq: 0,
394
+ ops: [],
395
+ });
396
+ }
397
+ if (!(await exists(paths.libraryFile))) {
398
+ await writeJson(paths.libraryFile, {
399
+ type: 'excalidrawlib',
400
+ version: 2,
401
+ source: 'https://openprd.local/canvas',
402
+ libraryItems: [],
403
+ });
404
+ }
405
+ return session;
406
+ }
407
+
408
+ export {
409
+ CANVAS_SCHEMA_VERSION,
410
+ buildCanvasSessionPaths,
411
+ ensureCanvasSession,
412
+ preserveCanvasBindingDisplayTitle,
413
+ resolveCanvasSessionBinding,
414
+ };