@hraness/direct 0.7.5

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +436 -0
  3. package/dist/core/index.js +162 -0
  4. package/dist/index-1csg00w4.js +1167 -0
  5. package/dist/index-6mdfd2ey.js +464 -0
  6. package/dist/index-7n1h75n6.js +616 -0
  7. package/dist/index.js +232 -0
  8. package/dist/react.js +32 -0
  9. package/dist/testing/index.js +1069 -0
  10. package/dist/tooling/bombadil.js +2117 -0
  11. package/dist/tooling/browser-verification-entry.js +1499 -0
  12. package/dist/tooling/bundle-boundary.js +119 -0
  13. package/dist/web.js +605 -0
  14. package/package.json +179 -0
  15. package/skills/direct/AGENTS.md +13 -0
  16. package/skills/direct/SKILL.md +49 -0
  17. package/skills/direct/agents/openai.yaml +4 -0
  18. package/skills/direct/references/adoption.md +131 -0
  19. package/skills/direct/references/install.md +91 -0
  20. package/skills/direct/references/verification.md +247 -0
  21. package/src/core/coverage.ts +336 -0
  22. package/src/core/definition.ts +378 -0
  23. package/src/core/effects.ts +88 -0
  24. package/src/core/fixture.ts +185 -0
  25. package/src/core/ids.ts +77 -0
  26. package/src/core/index.ts +13 -0
  27. package/src/core/json-value.ts +7 -0
  28. package/src/core/json.ts +593 -0
  29. package/src/core/query.ts +230 -0
  30. package/src/core/reason.ts +16 -0
  31. package/src/core/resource.ts +10 -0
  32. package/src/core/result.ts +19 -0
  33. package/src/core/runtime.ts +229 -0
  34. package/src/core/scenario.ts +149 -0
  35. package/src/core/store.ts +784 -0
  36. package/src/index.ts +51 -0
  37. package/src/react.ts +54 -0
  38. package/src/testing/activity.ts +228 -0
  39. package/src/testing/coverage-binding.ts +99 -0
  40. package/src/testing/evidence.ts +59 -0
  41. package/src/testing/index.ts +22 -0
  42. package/src/testing/manifest.ts +559 -0
  43. package/src/testing/probe.ts +446 -0
  44. package/src/testing/scripted-transport.ts +775 -0
  45. package/src/testing/session.ts +525 -0
  46. package/src/tooling/bombadil-campaign.ts +288 -0
  47. package/src/tooling/bombadil-internal.d.ts +46 -0
  48. package/src/tooling/bombadil-runner.ts +1424 -0
  49. package/src/tooling/bombadil.ts +27 -0
  50. package/src/tooling/browser-verification-entry.ts +32 -0
  51. package/src/tooling/browser-verification.ts +916 -0
  52. package/src/tooling/bundle-boundary.ts +159 -0
  53. package/src/web/browser-bridge.ts +296 -0
  54. package/src/web/browser.ts +277 -0
  55. package/src/web/fetch-firewall.ts +251 -0
  56. package/src/web.ts +27 -0
@@ -0,0 +1,559 @@
1
+ import type { DirectDefinition } from "../core/definition.js";
2
+ import {
3
+ createCoverageCatalogSnapshot,
4
+ parseCoverageCatalogSnapshot,
5
+ type CoverageCatalogSnapshot,
6
+ } from "../core/coverage.js";
7
+ import { parseScenarioId, type ScenarioId } from "../core/ids.js";
8
+ import {
9
+ parseJsonValue,
10
+ parseTaggedStableHash,
11
+ stableHash,
12
+ STABLE_HASH_ALGORITHM,
13
+ tagStableHash,
14
+ type JsonLimits,
15
+ type TaggedStableHash,
16
+ } from "../core/json.js";
17
+ import type { JsonValue } from "../core/json-value.js";
18
+ import {
19
+ FIXTURE_QUERY_KEY,
20
+ SCENARIO_QUERY_KEY,
21
+ type ActiveDirect,
22
+ } from "../core/query.js";
23
+ import { renderUnknownReason } from "../core/reason.js";
24
+ import { err, isRecord, ok, type Result } from "../core/result.js";
25
+ import { MAX_DIRECT_SCENARIOS } from "../core/scenario.js";
26
+
27
+ export const DIRECT_SESSION_MANIFEST_SCHEMA = "direct.session-manifest/v1" as const;
28
+ export const DIRECT_CATALOG_HASH_ALGORITHM = STABLE_HASH_ALGORITHM;
29
+ const DIRECT_SESSION_MANIFEST_JSON_LIMITS = Object.freeze({
30
+ maxDepth: 64,
31
+ maxNodes: 100_000,
32
+ maxStringBytes: 16_777_216,
33
+ }) satisfies JsonLimits;
34
+
35
+ export type DirectCatalogHash = TaggedStableHash;
36
+ export type DirectSelectionHash = TaggedStableHash;
37
+
38
+ export interface DirectSessionManifestQueries {
39
+ readonly scenario: typeof SCENARIO_QUERY_KEY;
40
+ readonly fixture: typeof FIXTURE_QUERY_KEY;
41
+ }
42
+
43
+ export interface DirectSessionManifestScenario {
44
+ readonly id: ScenarioId;
45
+ readonly title: string;
46
+ readonly description: string | null;
47
+ readonly route: string;
48
+ }
49
+
50
+ export interface DirectSessionManifestActive {
51
+ readonly source: "scenario" | "fixture";
52
+ readonly scenario: ScenarioId;
53
+ readonly route: string;
54
+ readonly activationHash: TaggedStableHash;
55
+ /**
56
+ * Consistency fingerprint binding the public selection to activationHash.
57
+ * It detects drift; it is not an authenticity or security proof.
58
+ */
59
+ readonly selectionHash: DirectSelectionHash;
60
+ }
61
+
62
+ /**
63
+ * The exact JSON-safe discovery surface for one Direct session.
64
+ *
65
+ * It deliberately excludes worlds, logical runtimes, product actions, and
66
+ * assertions. Agents can discover the public catalog without gaining another
67
+ * path to product state or authority.
68
+ */
69
+ export interface DirectSessionManifest {
70
+ readonly schema: typeof DIRECT_SESSION_MANIFEST_SCHEMA;
71
+ /** Deterministic drift fingerprint, not a security or authenticity proof. */
72
+ readonly catalogHash: DirectCatalogHash;
73
+ readonly queries: DirectSessionManifestQueries;
74
+ readonly defaultScenario: ScenarioId;
75
+ readonly active: DirectSessionManifestActive;
76
+ /** Authored scenario order is preserved. */
77
+ readonly scenarios: readonly DirectSessionManifestScenario[];
78
+ readonly coverage: CoverageCatalogSnapshot;
79
+ }
80
+
81
+ export type DirectSessionManifestErrorCode =
82
+ | "activation-hash-mismatch"
83
+ | "catalog-hash-mismatch"
84
+ | "duplicate-scenario"
85
+ | "invalid-catalog-hash"
86
+ | "invalid-manifest"
87
+ | "invalid-selection-hash"
88
+ | "route-mismatch"
89
+ | "selection-hash-mismatch"
90
+ | "unknown-coverage-scenario"
91
+ | "unknown-scenario";
92
+
93
+ export interface DirectSessionManifestError {
94
+ readonly code: DirectSessionManifestErrorCode;
95
+ readonly message: string;
96
+ }
97
+
98
+ const MANIFEST_KEYS = new Set([
99
+ "active",
100
+ "catalogHash",
101
+ "coverage",
102
+ "defaultScenario",
103
+ "queries",
104
+ "scenarios",
105
+ "schema",
106
+ ]);
107
+ const QUERY_KEYS = new Set(["fixture", "scenario"]);
108
+ const ACTIVE_KEYS = new Set([
109
+ "activationHash",
110
+ "route",
111
+ "scenario",
112
+ "selectionHash",
113
+ "source",
114
+ ]);
115
+ const SCENARIO_KEYS = new Set([
116
+ "description",
117
+ "id",
118
+ "route",
119
+ "title",
120
+ ]);
121
+
122
+ function manifestError(
123
+ code: DirectSessionManifestErrorCode,
124
+ message: string,
125
+ ): DirectSessionManifestError {
126
+ return Object.freeze({ code, message });
127
+ }
128
+
129
+ function exactKeys(
130
+ input: Readonly<Record<string, unknown>>,
131
+ expected: ReadonlySet<string>,
132
+ label: string,
133
+ ): void {
134
+ for (const key of Object.keys(input)) {
135
+ if (!expected.has(key)) throw new Error(`Unknown ${label} key: ${key}`);
136
+ }
137
+ for (const key of expected) {
138
+ if (!Object.hasOwn(input, key)) throw new Error(`Missing ${label} key: ${key}`);
139
+ }
140
+ }
141
+
142
+ function hasControlCharacters(value: string): boolean {
143
+ for (const character of value) {
144
+ const code = character.charCodeAt(0);
145
+ if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) {
146
+ return true;
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+
152
+ function validText(value: string, maximum: number): boolean {
153
+ return (
154
+ value.trim().length > 0
155
+ && value.length <= maximum
156
+ && !hasControlCharacters(value)
157
+ );
158
+ }
159
+
160
+ function validRoute(value: string): boolean {
161
+ if (value.trim().length === 0 || value.length > 256) return false;
162
+ for (const character of value) {
163
+ const code = character.charCodeAt(0);
164
+ if (code < 32 || code === 127) return false;
165
+ }
166
+ return true;
167
+ }
168
+
169
+ function parseTaggedHash(value: unknown, label: string): TaggedStableHash {
170
+ const parsed = parseTaggedStableHash(value);
171
+ if (!parsed.ok) throw new Error(`${label}: ${parsed.error.message}`);
172
+ return parsed.value;
173
+ }
174
+
175
+ interface DirectCatalogPayload {
176
+ readonly queries: DirectSessionManifestQueries;
177
+ readonly defaultScenario: ScenarioId;
178
+ readonly scenarios: readonly DirectSessionManifestScenario[];
179
+ readonly coverage: CoverageCatalogSnapshot;
180
+ }
181
+
182
+ type DirectSelectionPayload = Omit<DirectSessionManifestActive, "selectionHash">;
183
+
184
+ function selectionHash(
185
+ payload: DirectSelectionPayload,
186
+ ): Result<DirectSelectionHash, DirectSessionManifestError> {
187
+ const hashed = stableHash(payload, DIRECT_SESSION_MANIFEST_JSON_LIMITS);
188
+ if (!hashed.ok) {
189
+ return err(manifestError("invalid-manifest", hashed.error.message));
190
+ }
191
+ return ok(tagStableHash(hashed.value));
192
+ }
193
+
194
+ function catalogHash(
195
+ payload: DirectCatalogPayload,
196
+ ): Result<DirectCatalogHash, DirectSessionManifestError> {
197
+ const hashed = stableHash(payload, DIRECT_SESSION_MANIFEST_JSON_LIMITS);
198
+ if (!hashed.ok) {
199
+ return err(manifestError("invalid-manifest", hashed.error.message));
200
+ }
201
+ return ok(tagStableHash(hashed.value));
202
+ }
203
+
204
+ function parseManifestUnchecked(
205
+ input: unknown,
206
+ ): Result<DirectSessionManifest, DirectSessionManifestError> {
207
+ const parsedJson = parseJsonValue(input, DIRECT_SESSION_MANIFEST_JSON_LIMITS);
208
+ if (!parsedJson.ok || !isRecord(parsedJson.value)) {
209
+ return err(manifestError(
210
+ "invalid-manifest",
211
+ parsedJson.ok
212
+ ? "Direct session manifest must be an object"
213
+ : parsedJson.error.message,
214
+ ));
215
+ }
216
+ const candidate = parsedJson.value;
217
+ exactKeys(candidate, MANIFEST_KEYS, "Direct session manifest");
218
+ if (candidate.schema !== DIRECT_SESSION_MANIFEST_SCHEMA) {
219
+ throw new Error(
220
+ `Direct session manifest schema must be ${DIRECT_SESSION_MANIFEST_SCHEMA}`,
221
+ );
222
+ }
223
+
224
+ if (!isRecord(candidate.queries)) {
225
+ throw new Error("Direct session manifest queries must be an object");
226
+ }
227
+ exactKeys(candidate.queries, QUERY_KEYS, "Direct session manifest queries");
228
+ if (
229
+ candidate.queries.scenario !== SCENARIO_QUERY_KEY
230
+ || candidate.queries.fixture !== FIXTURE_QUERY_KEY
231
+ ) {
232
+ throw new Error("Direct session manifest query keys do not match Direct");
233
+ }
234
+ const queries: DirectSessionManifestQueries = Object.freeze({
235
+ scenario: SCENARIO_QUERY_KEY,
236
+ fixture: FIXTURE_QUERY_KEY,
237
+ });
238
+
239
+ const defaultScenario = parseScenarioId(candidate.defaultScenario);
240
+ if (!defaultScenario.ok) {
241
+ throw new Error(`Invalid default scenario: ${defaultScenario.error.message}`);
242
+ }
243
+
244
+ if (!Array.isArray(candidate.scenarios)) {
245
+ throw new Error("Direct session manifest scenarios must be an array");
246
+ }
247
+ if (candidate.scenarios.length > MAX_DIRECT_SCENARIOS) {
248
+ throw new Error(
249
+ `Direct session manifests support at most ${String(MAX_DIRECT_SCENARIOS)} scenarios`,
250
+ );
251
+ }
252
+ const scenarios: DirectSessionManifestScenario[] = [];
253
+ const byId = new Map<ScenarioId, DirectSessionManifestScenario>();
254
+ for (const [index, rawScenario] of candidate.scenarios.entries()) {
255
+ if (!isRecord(rawScenario)) {
256
+ throw new Error(`Direct session manifest scenario ${String(index)} must be an object`);
257
+ }
258
+ exactKeys(
259
+ rawScenario,
260
+ SCENARIO_KEYS,
261
+ `Direct session manifest scenario ${String(index)}`,
262
+ );
263
+ const id = parseScenarioId(rawScenario.id);
264
+ if (!id.ok) {
265
+ throw new Error(
266
+ `Invalid Direct session manifest scenario ${String(index)}: ${id.error.message}`,
267
+ );
268
+ }
269
+ if (byId.has(id.value)) {
270
+ return err(manifestError(
271
+ "duplicate-scenario",
272
+ `Duplicate Direct session manifest scenario: ${id.value}`,
273
+ ));
274
+ }
275
+ if (typeof rawScenario.title !== "string" || !validText(rawScenario.title, 160)) {
276
+ throw new Error(
277
+ `Direct session manifest scenario ${id.value} title must contain 1-160 visible characters`,
278
+ );
279
+ }
280
+ if (
281
+ rawScenario.description !== null
282
+ && (
283
+ typeof rawScenario.description !== "string"
284
+ || !validText(rawScenario.description, 2_000)
285
+ )
286
+ ) {
287
+ throw new Error(
288
+ `Direct session manifest scenario ${id.value} description must be null or contain 1-2000 visible characters`,
289
+ );
290
+ }
291
+ if (typeof rawScenario.route !== "string" || !validRoute(rawScenario.route)) {
292
+ throw new Error(
293
+ `Direct session manifest scenario ${id.value} route must contain 1-256 visible characters`,
294
+ );
295
+ }
296
+ const scenario: DirectSessionManifestScenario = Object.freeze({
297
+ id: id.value,
298
+ title: rawScenario.title,
299
+ description: rawScenario.description,
300
+ route: rawScenario.route,
301
+ });
302
+ scenarios.push(scenario);
303
+ byId.set(id.value, scenario);
304
+ }
305
+ const frozenScenarios = Object.freeze(scenarios);
306
+
307
+ if (!byId.has(defaultScenario.value)) {
308
+ return err(manifestError(
309
+ "unknown-scenario",
310
+ `Direct session manifest default scenario is missing: ${defaultScenario.value}`,
311
+ ));
312
+ }
313
+
314
+ if (!isRecord(candidate.active)) {
315
+ throw new Error("Direct session manifest active selection must be an object");
316
+ }
317
+ exactKeys(candidate.active, ACTIVE_KEYS, "Direct session manifest active selection");
318
+ if (candidate.active.source !== "scenario" && candidate.active.source !== "fixture") {
319
+ throw new Error("Direct session manifest active source must be scenario or fixture");
320
+ }
321
+ const activeScenario = parseScenarioId(candidate.active.scenario);
322
+ if (!activeScenario.ok) {
323
+ throw new Error(`Invalid active scenario: ${activeScenario.error.message}`);
324
+ }
325
+ const activeDefinition = byId.get(activeScenario.value);
326
+ if (activeDefinition === undefined) {
327
+ return err(manifestError(
328
+ "unknown-scenario",
329
+ `Direct session manifest active scenario is missing: ${activeScenario.value}`,
330
+ ));
331
+ }
332
+ if (typeof candidate.active.route !== "string" || !validRoute(candidate.active.route)) {
333
+ throw new Error("Direct session manifest active route is invalid");
334
+ }
335
+ if (candidate.active.route !== activeDefinition.route) {
336
+ return err(manifestError(
337
+ "route-mismatch",
338
+ `Direct session manifest active route does not match scenario ${activeScenario.value}`,
339
+ ));
340
+ }
341
+ const activationHash = parseTaggedHash(
342
+ candidate.active.activationHash,
343
+ "Direct session manifest activationHash",
344
+ );
345
+ let suppliedSelectionHash: DirectSelectionHash;
346
+ try {
347
+ suppliedSelectionHash = parseTaggedHash(
348
+ candidate.active.selectionHash,
349
+ "Direct session manifest selectionHash",
350
+ );
351
+ } catch (reason) {
352
+ return err(manifestError(
353
+ "invalid-selection-hash",
354
+ renderUnknownReason(reason, "Direct session manifest selectionHash is invalid"),
355
+ ));
356
+ }
357
+ const expectedSelectionHash = selectionHash({
358
+ source: candidate.active.source,
359
+ scenario: activeScenario.value,
360
+ route: activeDefinition.route,
361
+ activationHash,
362
+ });
363
+ if (!expectedSelectionHash.ok) return expectedSelectionHash;
364
+ if (suppliedSelectionHash !== expectedSelectionHash.value) {
365
+ return err(manifestError(
366
+ "selection-hash-mismatch",
367
+ "Direct session manifest selectionHash does not match its active selection",
368
+ ));
369
+ }
370
+ const active: DirectSessionManifestActive = Object.freeze({
371
+ source: candidate.active.source,
372
+ scenario: activeScenario.value,
373
+ route: activeDefinition.route,
374
+ activationHash,
375
+ selectionHash: expectedSelectionHash.value,
376
+ });
377
+
378
+ const coverage = parseCoverageCatalogSnapshot(
379
+ candidate.coverage,
380
+ DIRECT_SESSION_MANIFEST_JSON_LIMITS,
381
+ );
382
+ if (!coverage.ok) {
383
+ throw new Error(coverage.error.message);
384
+ }
385
+ for (const entry of coverage.value.entries) {
386
+ for (const scenario of entry.scenarios) {
387
+ if (!byId.has(scenario)) {
388
+ return err(manifestError(
389
+ "unknown-coverage-scenario",
390
+ `Coverage ${entry.key} cites unknown Direct session manifest scenario ${scenario}`,
391
+ ));
392
+ }
393
+ }
394
+ }
395
+
396
+ let suppliedCatalogHash: DirectCatalogHash;
397
+ try {
398
+ const parsedHash = parseTaggedHash(
399
+ candidate.catalogHash,
400
+ "Direct session manifest catalogHash",
401
+ );
402
+ const separator = parsedHash.indexOf(":");
403
+ suppliedCatalogHash =
404
+ `${DIRECT_CATALOG_HASH_ALGORITHM}:${parsedHash.slice(separator + 1)}`;
405
+ } catch (reason) {
406
+ return err(manifestError(
407
+ "invalid-catalog-hash",
408
+ renderUnknownReason(reason, "Direct session manifest catalogHash is invalid"),
409
+ ));
410
+ }
411
+ const expectedCatalogHash = catalogHash({
412
+ queries,
413
+ defaultScenario: defaultScenario.value,
414
+ scenarios: frozenScenarios,
415
+ coverage: coverage.value,
416
+ });
417
+ if (!expectedCatalogHash.ok) return expectedCatalogHash;
418
+ if (suppliedCatalogHash !== expectedCatalogHash.value) {
419
+ return err(manifestError(
420
+ "catalog-hash-mismatch",
421
+ "Direct session manifest catalogHash does not match its public catalog",
422
+ ));
423
+ }
424
+
425
+ return ok(Object.freeze({
426
+ schema: DIRECT_SESSION_MANIFEST_SCHEMA,
427
+ catalogHash: expectedCatalogHash.value,
428
+ queries,
429
+ defaultScenario: defaultScenario.value,
430
+ active,
431
+ scenarios: frozenScenarios,
432
+ coverage: coverage.value,
433
+ }));
434
+ }
435
+
436
+ /** Parse, validate, clone, and freeze a foreign Direct discovery manifest. */
437
+ export function parseDirectSessionManifest(
438
+ input: unknown,
439
+ ): Result<DirectSessionManifest, DirectSessionManifestError> {
440
+ try {
441
+ return parseManifestUnchecked(input);
442
+ } catch (reason) {
443
+ return err(manifestError(
444
+ "invalid-manifest",
445
+ renderUnknownReason(reason, "Direct session manifest is invalid"),
446
+ ));
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Project a validated definition and activation into the driver-neutral
452
+ * discovery manifest shared by browser and headless verification adapters.
453
+ */
454
+ export function createDirectSessionManifest<
455
+ World extends JsonValue,
456
+ Route extends string,
457
+ >(
458
+ definition: DirectDefinition<World, Route>,
459
+ activation: ActiveDirect<World, Route>,
460
+ ): Result<DirectSessionManifest, DirectSessionManifestError> {
461
+ try {
462
+ const activeScenario = definition.scenarios.get(activation.scenario);
463
+ if (activeScenario === undefined) {
464
+ return err(manifestError(
465
+ "unknown-scenario",
466
+ `Direct session activation is missing from its definition: ${activation.scenario}`,
467
+ ));
468
+ }
469
+ if (activeScenario.route !== activation.route) {
470
+ return err(manifestError(
471
+ "route-mismatch",
472
+ `Direct session activation route does not match scenario ${activation.scenario}`,
473
+ ));
474
+ }
475
+ const activeIdentity = {
476
+ source: activation.source,
477
+ scenario: activation.scenario,
478
+ route: activation.route,
479
+ world: activation.world,
480
+ runtime: activation.runtime,
481
+ };
482
+ const hashedActivation = stableHash(activeIdentity);
483
+ if (!hashedActivation.ok) {
484
+ return err(manifestError("invalid-manifest", hashedActivation.error.message));
485
+ }
486
+ const expectedActivationHash = tagStableHash(hashedActivation.value);
487
+ if (activation.activationHash !== expectedActivationHash) {
488
+ return err(manifestError(
489
+ "activation-hash-mismatch",
490
+ "Direct session activationHash does not identify its active state",
491
+ ));
492
+ }
493
+ if (activation.source === "scenario") {
494
+ const authoredHash = stableHash({
495
+ source: activation.source,
496
+ scenario: activeScenario.id,
497
+ route: activeScenario.route,
498
+ world: activeScenario.world,
499
+ runtime: activeScenario.runtime,
500
+ });
501
+ if (!authoredHash.ok) {
502
+ return err(manifestError("invalid-manifest", authoredHash.error.message));
503
+ }
504
+ if (tagStableHash(authoredHash.value) !== expectedActivationHash) {
505
+ return err(manifestError(
506
+ "activation-hash-mismatch",
507
+ `Direct scenario activation does not match authored scenario ${activation.scenario}`,
508
+ ));
509
+ }
510
+ }
511
+ const scenarios = Object.freeze(definition.scenarios.list().map((scenario) =>
512
+ Object.freeze({
513
+ id: scenario.id,
514
+ title: scenario.title,
515
+ description: scenario.description,
516
+ route: scenario.route,
517
+ })
518
+ ));
519
+ const queries: DirectSessionManifestQueries = Object.freeze({
520
+ scenario: SCENARIO_QUERY_KEY,
521
+ fixture: FIXTURE_QUERY_KEY,
522
+ });
523
+ const coverage = createCoverageCatalogSnapshot(definition.coverage);
524
+ const hash = catalogHash({
525
+ queries,
526
+ defaultScenario: definition.defaultScenario.id,
527
+ scenarios,
528
+ coverage,
529
+ });
530
+ if (!hash.ok) return hash;
531
+ const activeSelectionHash = selectionHash({
532
+ source: activation.source,
533
+ scenario: activation.scenario,
534
+ route: activation.route,
535
+ activationHash: expectedActivationHash,
536
+ });
537
+ if (!activeSelectionHash.ok) return activeSelectionHash;
538
+ return parseDirectSessionManifest({
539
+ schema: DIRECT_SESSION_MANIFEST_SCHEMA,
540
+ catalogHash: hash.value,
541
+ queries,
542
+ defaultScenario: definition.defaultScenario.id,
543
+ active: {
544
+ source: activation.source,
545
+ scenario: activation.scenario,
546
+ route: activation.route,
547
+ activationHash: expectedActivationHash,
548
+ selectionHash: activeSelectionHash.value,
549
+ },
550
+ scenarios,
551
+ coverage,
552
+ });
553
+ } catch (reason) {
554
+ return err(manifestError(
555
+ "invalid-manifest",
556
+ renderUnknownReason(reason, "Direct session manifest could not be created"),
557
+ ));
558
+ }
559
+ }