@optique/core 1.3.0-dev.2430 → 1.3.0-dev.2433

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.
@@ -404,6 +404,17 @@ async function collectExplicitSourceValuesAsync(nodes, runtime) {
404
404
  * @since 1.3.0
405
405
  */
406
406
  function orderDependencyNodes(nodes) {
407
+ return drainDependencyGraph(buildDependencyGraph(nodes));
408
+ }
409
+ /**
410
+ * Builds the provider-edge graph over runtime nodes.
411
+ *
412
+ * Scheduling barriers act as providers for the source IDs their
413
+ * selected subtree may expose and depend on their discriminator source
414
+ * as well as on outer providers of the completion dependencies their
415
+ * selectable subtrees aggregate.
416
+ */
417
+ function buildDependencyGraph(nodes) {
407
418
  const providers = /* @__PURE__ */ new Map();
408
419
  const addProvider = (sourceId, node) => {
409
420
  const existing = providers.get(sourceId);
@@ -418,51 +429,162 @@ function orderDependencyNodes(nodes) {
418
429
  const outgoing = /* @__PURE__ */ new Map();
419
430
  const indegree = /* @__PURE__ */ new Map();
420
431
  for (const node of nodes) indegree.set(node, 0);
421
- const addEdge = (provider, consumer) => {
422
- const edges = outgoing.get(provider) ?? /* @__PURE__ */ new Set();
423
- if (edges.has(consumer)) return;
424
- edges.add(consumer);
432
+ const addEdge = (provider, consumer, advisory) => {
433
+ const edges = outgoing.get(provider) ?? /* @__PURE__ */ new Map();
434
+ const existing = edges.get(consumer);
435
+ if (existing != null) {
436
+ if (existing && !advisory) edges.set(consumer, false);
437
+ return;
438
+ }
439
+ edges.set(consumer, advisory);
425
440
  outgoing.set(provider, edges);
426
441
  indegree.set(consumer, (indegree.get(consumer) ?? 0) + 1);
427
442
  };
428
443
  for (const node of nodes) {
429
444
  const derived = node.parser.dependencyMetadata?.derived;
430
- if (derived != null) for (const dependencySourceId of derived.dependencyIds) for (const provider of providers.get(dependencySourceId) ?? []) addEdge(provider, node);
445
+ if (derived != null) for (const dependencySourceId of derived.dependencyIds) for (const provider of providers.get(dependencySourceId) ?? []) addEdge(provider, node, false);
431
446
  const completion = node.parser.dependencyMetadata?.completion;
432
447
  if (completion != null && !hasInactiveCompletion(node)) {
433
448
  const ownSourceId = node.parser.dependencyMetadata?.source?.sourceId;
434
449
  for (const dependencySourceId of completion.dependencyIds) {
435
450
  if (dependencySourceId === ownSourceId) continue;
436
- for (const provider of providers.get(dependencySourceId) ?? []) if (provider !== node) addEdge(provider, node);
451
+ for (const provider of providers.get(dependencySourceId) ?? []) if (provider !== node) addEdge(provider, node, false);
437
452
  }
438
453
  }
439
454
  if (node.requiresSourceId != null) {
440
- for (const provider of providers.get(node.requiresSourceId) ?? []) if (provider !== node) addEdge(provider, node);
455
+ for (const provider of providers.get(node.requiresSourceId) ?? []) if (provider !== node) addEdge(provider, node, false);
441
456
  }
442
457
  const barrierDeps = node.barrierCompletionDependencies;
443
458
  if (barrierDeps != null) {
444
- for (const dependencySourceId of barrierDeps.orderingDependencyIds) for (const provider of providers.get(dependencySourceId) ?? []) if (provider !== node) addEdge(provider, node);
459
+ const advisory = node.barrierResolutionState !== "resolved";
460
+ for (const dependencySourceId of barrierDeps.orderingDependencyIds) for (const provider of providers.get(dependencySourceId) ?? []) if (provider !== node) addEdge(provider, node, advisory);
445
461
  }
446
462
  }
447
463
  const declarationOrder = new Map(nodes.map((node, index) => [node, index]));
464
+ return {
465
+ nodes,
466
+ outgoing,
467
+ indegree,
468
+ declarationOrder
469
+ };
470
+ }
471
+ /**
472
+ * Drains the graph in stable topological order.
473
+ *
474
+ * A stall—no node with indegree zero remains—normally means a genuine
475
+ * cycle. When the stalled region contains an unresolved barrier's
476
+ * advisory ordering edges within one strongly connected component, the
477
+ * apparent cycle aggregates edges from branches that cannot be selected
478
+ * together, so those advisory edges are relaxed and the drain resumes;
479
+ * the barrier's own resolution later replaces the estimate with
480
+ * concrete edges, and a cycle among those still throws.
481
+ *
482
+ * @throws {TypeError} If active provider edges contain a cycle.
483
+ */
484
+ function drainDependencyGraph(graph) {
485
+ const { nodes, outgoing, declarationOrder } = graph;
486
+ const indegree = new Map(graph.indegree);
448
487
  const ready = nodes.filter((node) => indegree.get(node) === 0);
449
488
  const ordered = [];
450
- while (ready.length > 0) {
451
- ready.sort((left, right) => declarationOrder.get(left) - declarationOrder.get(right));
452
- const node = ready.shift();
453
- ordered.push(node);
454
- for (const consumer of outgoing.get(node) ?? []) {
455
- const next = (indegree.get(consumer) ?? 0) - 1;
456
- indegree.set(consumer, next);
457
- if (next === 0) ready.push(consumer);
489
+ while (true) {
490
+ while (ready.length > 0) {
491
+ ready.sort((left, right) => declarationOrder.get(left) - declarationOrder.get(right));
492
+ const node = ready.shift();
493
+ ordered.push(node);
494
+ for (const consumer of outgoing.get(node)?.keys() ?? []) {
495
+ const next = (indegree.get(consumer) ?? 0) - 1;
496
+ indegree.set(consumer, next);
497
+ if (next === 0) ready.push(consumer);
498
+ }
499
+ }
500
+ if (ordered.length === nodes.length) return ordered;
501
+ const residual = nodes.filter((node) => (indegree.get(node) ?? 0) > 0);
502
+ const sccOf = findStronglyConnected(residual, outgoing);
503
+ let relaxed = false;
504
+ for (const provider of residual) {
505
+ const edges = outgoing.get(provider);
506
+ if (edges == null) continue;
507
+ for (const [consumer, advisory] of edges) {
508
+ if (!advisory) continue;
509
+ const providerScc = sccOf.get(provider);
510
+ if (providerScc == null || providerScc !== sccOf.get(consumer)) continue;
511
+ edges.delete(consumer);
512
+ const next = (indegree.get(consumer) ?? 0) - 1;
513
+ indegree.set(consumer, next);
514
+ if (next === 0) ready.push(consumer);
515
+ relaxed = true;
516
+ }
517
+ }
518
+ if (!relaxed) {
519
+ const labels = residual.map(formatDependencyNodeLabel).join(" -> ");
520
+ throw new TypeError(`Circular dependency detected among dependency sources: ${labels}.`);
458
521
  }
459
522
  }
460
- if (ordered.length !== nodes.length) {
461
- const cycle = nodes.filter((node) => (indegree.get(node) ?? 0) > 0);
462
- const labels = cycle.map(formatDependencyNodeLabel).join(" -> ");
463
- throw new TypeError(`Circular dependency detected among dependency sources: ${labels}.`);
464
- }
465
- return ordered;
523
+ }
524
+ /**
525
+ * Assigns each node in `subset` to a strongly connected component of
526
+ * the induced subgraph. Only components with more than one node are
527
+ * recorded—an acyclic node cannot be part of an apparent cycle.
528
+ */
529
+ function findStronglyConnected(subset, outgoing) {
530
+ const inSubset = new Set(subset);
531
+ const index = /* @__PURE__ */ new Map();
532
+ const lowLink = /* @__PURE__ */ new Map();
533
+ const onStack = /* @__PURE__ */ new Set();
534
+ const stack = [];
535
+ const sccOf = /* @__PURE__ */ new Map();
536
+ let nextIndex = 0;
537
+ let nextScc = 0;
538
+ const strongConnect = (root) => {
539
+ const frames = [{
540
+ node: root,
541
+ children: [...outgoing.get(root)?.keys() ?? []].filter((child) => inSubset.has(child)),
542
+ i: 0
543
+ }];
544
+ index.set(root, nextIndex);
545
+ lowLink.set(root, nextIndex);
546
+ nextIndex++;
547
+ stack.push(root);
548
+ onStack.add(root);
549
+ while (frames.length > 0) {
550
+ const frame = frames[frames.length - 1];
551
+ if (frame.i < frame.children.length) {
552
+ const child = frame.children[frame.i++];
553
+ if (!index.has(child)) {
554
+ index.set(child, nextIndex);
555
+ lowLink.set(child, nextIndex);
556
+ nextIndex++;
557
+ stack.push(child);
558
+ onStack.add(child);
559
+ frames.push({
560
+ node: child,
561
+ children: [...outgoing.get(child)?.keys() ?? []].filter((c) => inSubset.has(c)),
562
+ i: 0
563
+ });
564
+ } else if (onStack.has(child)) lowLink.set(frame.node, Math.min(lowLink.get(frame.node), index.get(child)));
565
+ continue;
566
+ }
567
+ frames.pop();
568
+ const node = frame.node;
569
+ if (lowLink.get(node) === index.get(node)) {
570
+ const members = [];
571
+ while (true) {
572
+ const member = stack.pop();
573
+ onStack.delete(member);
574
+ members.push(member);
575
+ if (member === node) break;
576
+ }
577
+ if (members.length > 1) {
578
+ for (const member of members) sccOf.set(member, nextScc);
579
+ nextScc++;
580
+ }
581
+ }
582
+ const parent = frames[frames.length - 1];
583
+ if (parent != null) lowLink.set(parent.node, Math.min(lowLink.get(parent.node), lowLink.get(node)));
584
+ }
585
+ };
586
+ for (const node of subset) if (!index.has(node)) strongConnect(node);
587
+ return sccOf;
466
588
  }
467
589
  /** Resolves and publishes matched derived sources in stable dependency order. */
468
590
  function resolveDerivedSourceValues(nodes, runtime, options) {
@@ -921,6 +1043,35 @@ const sourceCollectionExpansionKey = Symbol("@optique/core/dependency-runtime/so
921
1043
  */
922
1044
  const staticSourceScopeKey = Symbol("@optique/core/dependency-runtime/staticSourceScope");
923
1045
  /**
1046
+ * Static child parsers reachable only through a mutually exclusive
1047
+ * selection, such as `or()` alternatives, a `conditional()`'s branches,
1048
+ * or a `command()`'s inner parser.
1049
+ *
1050
+ * The walk unions sources and completion dependencies from this scope
1051
+ * exactly like {@link staticSourceScopeKey}, but a source occurrence
1052
+ * reached through an exclusive edge is never a *guaranteed* provider:
1053
+ * the run may select a sibling instead, so a scheduling barrier must not
1054
+ * assume the occurrence will publish. See issue #924.
1055
+ *
1056
+ * @internal
1057
+ * @since 1.3.0
1058
+ */
1059
+ const exclusiveSourceScopeKey = Symbol("@optique/core/dependency-runtime/exclusiveSourceScope");
1060
+ /**
1061
+ * Source IDs a parser guarantees to provide on every successful route
1062
+ * through it, regardless of which internal alternative is selected.
1063
+ *
1064
+ * A `conditional()` installs the intersection of its branches' guaranteed
1065
+ * provides here (including the default branch when present), so an outer
1066
+ * scheduling barrier can treat a source that *every* selectable route
1067
+ * publishes as guaranteed even though each individual branch edge is
1068
+ * exclusive. See issue #924.
1069
+ *
1070
+ * @internal
1071
+ * @since 1.3.0
1072
+ */
1073
+ const guaranteedStaticSourceIdsKey = Symbol("@optique/core/dependency-runtime/guaranteedStaticSourceIds");
1074
+ /**
924
1075
  * Forwards effectful scheduling through a shape-preserving wrapper such
925
1076
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
926
1077
  * wrapped parser—a selected exclusive or command branch, or an ordinary
@@ -1098,17 +1249,54 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
1098
1249
  const schedulable = nodes.filter((node) => node.parser.dependencyMetadata?.source?.completeSource != null || node.prepare != null);
1099
1250
  if (schedulable.length === 0 && options?.includeStructural !== true) return empty;
1100
1251
  const completed = [];
1101
- for (const node of orderDependencyNodes(nodes)) {
1252
+ const barrierContext = {
1253
+ runtime,
1254
+ exec,
1255
+ schedule: (barrierNodes) => completeEffectfulSourcesAsync(barrierNodes, state, runtime, exec, {
1256
+ isReusable: () => false,
1257
+ isCollected: () => true,
1258
+ includeStructural: true
1259
+ }).then((result) => result.success ? void 0 : result)
1260
+ };
1261
+ const pending = [...nodes];
1262
+ const providesId = (node, id) => node.parser.dependencyMetadata?.source?.sourceId === id || node.providesSourceIds?.has(id) === true;
1263
+ const resolveBarrierAt = async (index) => {
1264
+ const candidate = pending[index];
1265
+ const resolution = await candidate.resolveBarrier(barrierContext);
1266
+ if (resolution != null && "success" in resolution) return resolution;
1267
+ pending[index] = resolution == null ? {
1268
+ ...candidate,
1269
+ barrierResolutionState: "unresolvable"
1270
+ } : {
1271
+ ...candidate,
1272
+ barrierResolutionState: "resolved",
1273
+ providesSourceIds: resolution.activeProvidesSourceIds,
1274
+ barrierCompletionDependencies: {
1275
+ orderingDependencyIds: resolution.orderingDependencyIds,
1276
+ demandEdges: []
1277
+ }
1278
+ };
1279
+ return void 0;
1280
+ };
1281
+ while (pending.length > 0) {
1282
+ for (let i = 0; i < pending.length; i++) {
1283
+ const candidate = pending[i];
1284
+ if (candidate.resolveBarrier == null || candidate.barrierResolutionState != null) continue;
1285
+ const requires = candidate.requiresSourceId;
1286
+ if (requires == null) continue;
1287
+ if (pending.some((other) => other !== candidate && providesId(other, requires))) continue;
1288
+ const failure = await resolveBarrierAt(i);
1289
+ if (failure != null) return failure;
1290
+ }
1291
+ const node = orderDependencyNodes(pending)[0];
1292
+ if (node.resolveBarrier != null && node.barrierResolutionState == null) {
1293
+ const failure = await resolveBarrierAt(pending.indexOf(node));
1294
+ if (failure != null) return failure;
1295
+ continue;
1296
+ }
1297
+ pending.splice(pending.indexOf(node), 1);
1102
1298
  if (node.prepare != null) {
1103
- const barrierFailure = await node.prepare({
1104
- runtime,
1105
- exec,
1106
- schedule: (barrierNodes) => completeEffectfulSourcesAsync(barrierNodes, state, runtime, exec, {
1107
- isReusable: () => false,
1108
- isCollected: () => true,
1109
- includeStructural: true
1110
- }).then((result$1) => result$1.success ? void 0 : result$1)
1111
- });
1299
+ const barrierFailure = await node.prepare(barrierContext);
1112
1300
  if (barrierFailure != null) return barrierFailure;
1113
1301
  continue;
1114
1302
  }
@@ -1140,7 +1328,34 @@ async function completeEffectfulSourcesAsync(nodes, state, runtime, exec, option
1140
1328
  const collected = options?.isCollected?.(node) ?? options?.isReusable?.(node) ?? true;
1141
1329
  if (source.extractSourceValue == null || collected === false) continue;
1142
1330
  const extracted = await source.extractSourceValue(node.state);
1143
- if (extracted?.success === true && extracted.value !== void 0) runtime.registerSource(source.sourceId, extracted.value);
1331
+ if (extracted?.success === true && extracted.value !== void 0) {
1332
+ runtime.registerSource(source.sourceId, extracted.value);
1333
+ continue;
1334
+ }
1335
+ if (options?.includeStructural === true && extracted?.success !== false && node.matched !== true && source.preservesSourceValue && source.getMissingSourceValue != null && !runtime.hasSource(source.sourceId) && !runtime.isSourceFailed(source.sourceId)) {
1336
+ let fallback = session?.missingDefaultsByOccurrence.get(source.getMissingSourceValue);
1337
+ if (fallback == null) {
1338
+ try {
1339
+ fallback = await source.getMissingSourceValue();
1340
+ } catch (e) {
1341
+ const msg = e instanceof Error ? e.message : String(e);
1342
+ fallback = {
1343
+ success: false,
1344
+ error: require_message.message`Default value evaluation failed: ${msg}`
1345
+ };
1346
+ }
1347
+ session?.missingDefaultsByOccurrence.set(source.getMissingSourceValue, fallback);
1348
+ }
1349
+ if (fallback.success) runtime.registerSource(source.sourceId, fallback.value);
1350
+ else {
1351
+ runtime.markSourceFailed(source.sourceId);
1352
+ propagateRuntimeSourceFailures(nodes, runtime);
1353
+ return {
1354
+ success: false,
1355
+ error: includeSourceFailureChain(fallback.error, source.sourceId, runtime)
1356
+ };
1357
+ }
1358
+ }
1144
1359
  continue;
1145
1360
  }
1146
1361
  const pathKey = serializeSchedulingPath(node.path);
@@ -1479,9 +1694,11 @@ exports.createReplayKey = createReplayKey;
1479
1694
  exports.defineForwardedEffectfulSchedulingNodes = defineForwardedEffectfulSchedulingNodes;
1480
1695
  exports.derivedRawInputKey = derivedRawInputKey;
1481
1696
  exports.effectfulSchedulingNodesKey = effectfulSchedulingNodesKey;
1697
+ exports.exclusiveSourceScopeKey = exclusiveSourceScopeKey;
1482
1698
  exports.extractRawInputFromState = extractRawInputFromState;
1483
1699
  exports.fillMissingSourceDefaults = fillMissingSourceDefaults;
1484
1700
  exports.fillMissingSourceDefaultsAsync = fillMissingSourceDefaultsAsync;
1701
+ exports.guaranteedStaticSourceIdsKey = guaranteedStaticSourceIdsKey;
1485
1702
  exports.hasInactiveCompletion = hasInactiveCompletion;
1486
1703
  exports.includeSourceFailureChain = includeSourceFailureChain;
1487
1704
  exports.orderDependencyNodes = orderDependencyNodes;
@@ -148,6 +148,36 @@ interface RuntimeNode {
148
148
  readonly success: false;
149
149
  readonly error: Message;
150
150
  } | undefined>;
151
+ /**
152
+ * Resolves this barrier's *active* dependencies once its control
153
+ * dependencies (see {@link RuntimeNode.requiresSourceId}) are
154
+ * satisfied, before the barrier itself executes.
155
+ *
156
+ * A `conditional()` resolves the selected branch here—completing an
157
+ * effect-free discriminator, or reading an effectful one's cached
158
+ * completion—and reports which completion dependencies the branch
159
+ * actually has and which sources it will actively provide, so the
160
+ * scheduler can replace the static advisory estimate with concrete
161
+ * edges (see issue #924). Returning `undefined` leaves the static
162
+ * estimate in place; a failure aborts the pass. The scheduler calls
163
+ * this at most once per pass, and never lets advisory ordering edges
164
+ * delay it.
165
+ *
166
+ * @since 1.3.0
167
+ */
168
+ readonly resolveBarrier?: (ctx: SchedulingBarrierContext) => Promise<{
169
+ readonly success: false;
170
+ readonly error: Message;
171
+ } | BarrierResolution | undefined>;
172
+ /**
173
+ * Internal marker the effectful scheduler sets on the replacement
174
+ * node after {@link RuntimeNode.resolveBarrier} ran: `"resolved"`
175
+ * edges are concrete (a cycle through them is genuine), while
176
+ * `"unresolvable"` keeps the advisory estimate.
177
+ *
178
+ * @since 1.3.0
179
+ */
180
+ readonly barrierResolutionState?: "resolved" | "unresolvable";
151
181
  /**
152
182
  * Source IDs that the subtree guarded by this barrier can provide.
153
183
  * Used by the demand-only pass: when any of them is demanded, the
@@ -158,6 +188,15 @@ interface RuntimeNode {
158
188
  * @since 1.3.0
159
189
  */
160
190
  readonly providesSourceIds?: ReadonlySet<symbol>;
191
+ /**
192
+ * Source IDs every selectable route through this barrier's subtree is
193
+ * guaranteed to provide, regardless of which branch is selected. An
194
+ * enclosing barrier's resolution treats these as active provides; the
195
+ * rest of {@link RuntimeNode.providesSourceIds} stays merely possible.
196
+ *
197
+ * @since 1.3.0
198
+ */
199
+ readonly guaranteedProvidesSourceIds?: ReadonlySet<symbol>;
161
200
  /**
162
201
  * The source ID whose completion this barrier's preparation depends
163
202
  * on (a `conditional()` discriminator). See
@@ -224,6 +263,29 @@ interface BarrierCompletionDependencies {
224
263
  */
225
264
  readonly demandEdges: readonly BarrierCompletionDemandEdge[];
226
265
  }
266
+ /**
267
+ * The concrete dependencies a scheduling barrier reports from
268
+ * {@link RuntimeNode.resolveBarrier} once its selection is known.
269
+ *
270
+ * @internal
271
+ * @since 1.3.0
272
+ */
273
+ interface BarrierResolution {
274
+ /**
275
+ * The completion dependency IDs the selected subtree actually reads,
276
+ * minus the sources it actively provides itself. These replace the
277
+ * static advisory ordering estimate with concrete edges.
278
+ */
279
+ readonly orderingDependencyIds: readonly symbol[];
280
+ /**
281
+ * The source IDs the selected subtree will actively publish: a
282
+ * guaranteed occurrence (an unconditional prompt or a publishable
283
+ * default) or one whose state already extracts a value. These
284
+ * replace {@link RuntimeNode.providesSourceIds} for ordering, so a
285
+ * sibling consumer no longer waits for a merely possible provider.
286
+ */
287
+ readonly activeProvidesSourceIds: ReadonlySet<symbol>;
288
+ }
227
289
  /**
228
290
  * Options for resolving matched derived source values.
229
291
  *
@@ -524,6 +586,35 @@ declare const sourceCollectionExpansionKey: unique symbol;
524
586
  * @since 1.3.0
525
587
  */
526
588
  declare const staticSourceScopeKey: unique symbol;
589
+ /**
590
+ * Static child parsers reachable only through a mutually exclusive
591
+ * selection, such as `or()` alternatives, a `conditional()`'s branches,
592
+ * or a `command()`'s inner parser.
593
+ *
594
+ * The walk unions sources and completion dependencies from this scope
595
+ * exactly like {@link staticSourceScopeKey}, but a source occurrence
596
+ * reached through an exclusive edge is never a *guaranteed* provider:
597
+ * the run may select a sibling instead, so a scheduling barrier must not
598
+ * assume the occurrence will publish. See issue #924.
599
+ *
600
+ * @internal
601
+ * @since 1.3.0
602
+ */
603
+ declare const exclusiveSourceScopeKey: unique symbol;
604
+ /**
605
+ * Source IDs a parser guarantees to provide on every successful route
606
+ * through it, regardless of which internal alternative is selected.
607
+ *
608
+ * A `conditional()` installs the intersection of its branches' guaranteed
609
+ * provides here (including the default branch when present), so an outer
610
+ * scheduling barrier can treat a source that *every* selectable route
611
+ * publishes as guaranteed even though each individual branch edge is
612
+ * exclusive. See issue #924.
613
+ *
614
+ * @internal
615
+ * @since 1.3.0
616
+ */
617
+ declare const guaranteedStaticSourceIdsKey: unique symbol;
527
618
  /**
528
619
  * Forwards effectful scheduling through a shape-preserving wrapper such
529
620
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
@@ -767,4 +858,4 @@ declare function buildRuntimeNodesFromArray(parsers: ReadonlyArray<{
767
858
  readonly initialState?: unknown;
768
859
  }>, stateArray: readonly unknown[], parentPath?: readonly PropertyKey[]): readonly RuntimeNode[];
769
860
  //#endregion
770
- export { BarrierCompletionDemandEdge, BarrierCompletionDependencies, CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, ResolveDerivedSourceValuesOptions, RuntimeNode, SchedulingBarrierContext, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, derivedRawInputKey, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, hasInactiveCompletion, includeSourceFailureChain, orderDependencyNodes, recordDerivedRawInput, replayDerivedParser, replayDerivedParserAsync, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };
861
+ export { BarrierCompletionDemandEdge, BarrierCompletionDependencies, BarrierResolution, CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, ResolveDerivedSourceValuesOptions, RuntimeNode, SchedulingBarrierContext, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, derivedRawInputKey, effectfulSchedulingNodesKey, exclusiveSourceScopeKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, guaranteedStaticSourceIdsKey, hasInactiveCompletion, includeSourceFailureChain, orderDependencyNodes, recordDerivedRawInput, replayDerivedParser, replayDerivedParserAsync, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };
@@ -148,6 +148,36 @@ interface RuntimeNode {
148
148
  readonly success: false;
149
149
  readonly error: Message;
150
150
  } | undefined>;
151
+ /**
152
+ * Resolves this barrier's *active* dependencies once its control
153
+ * dependencies (see {@link RuntimeNode.requiresSourceId}) are
154
+ * satisfied, before the barrier itself executes.
155
+ *
156
+ * A `conditional()` resolves the selected branch here—completing an
157
+ * effect-free discriminator, or reading an effectful one's cached
158
+ * completion—and reports which completion dependencies the branch
159
+ * actually has and which sources it will actively provide, so the
160
+ * scheduler can replace the static advisory estimate with concrete
161
+ * edges (see issue #924). Returning `undefined` leaves the static
162
+ * estimate in place; a failure aborts the pass. The scheduler calls
163
+ * this at most once per pass, and never lets advisory ordering edges
164
+ * delay it.
165
+ *
166
+ * @since 1.3.0
167
+ */
168
+ readonly resolveBarrier?: (ctx: SchedulingBarrierContext) => Promise<{
169
+ readonly success: false;
170
+ readonly error: Message;
171
+ } | BarrierResolution | undefined>;
172
+ /**
173
+ * Internal marker the effectful scheduler sets on the replacement
174
+ * node after {@link RuntimeNode.resolveBarrier} ran: `"resolved"`
175
+ * edges are concrete (a cycle through them is genuine), while
176
+ * `"unresolvable"` keeps the advisory estimate.
177
+ *
178
+ * @since 1.3.0
179
+ */
180
+ readonly barrierResolutionState?: "resolved" | "unresolvable";
151
181
  /**
152
182
  * Source IDs that the subtree guarded by this barrier can provide.
153
183
  * Used by the demand-only pass: when any of them is demanded, the
@@ -158,6 +188,15 @@ interface RuntimeNode {
158
188
  * @since 1.3.0
159
189
  */
160
190
  readonly providesSourceIds?: ReadonlySet<symbol>;
191
+ /**
192
+ * Source IDs every selectable route through this barrier's subtree is
193
+ * guaranteed to provide, regardless of which branch is selected. An
194
+ * enclosing barrier's resolution treats these as active provides; the
195
+ * rest of {@link RuntimeNode.providesSourceIds} stays merely possible.
196
+ *
197
+ * @since 1.3.0
198
+ */
199
+ readonly guaranteedProvidesSourceIds?: ReadonlySet<symbol>;
161
200
  /**
162
201
  * The source ID whose completion this barrier's preparation depends
163
202
  * on (a `conditional()` discriminator). See
@@ -224,6 +263,29 @@ interface BarrierCompletionDependencies {
224
263
  */
225
264
  readonly demandEdges: readonly BarrierCompletionDemandEdge[];
226
265
  }
266
+ /**
267
+ * The concrete dependencies a scheduling barrier reports from
268
+ * {@link RuntimeNode.resolveBarrier} once its selection is known.
269
+ *
270
+ * @internal
271
+ * @since 1.3.0
272
+ */
273
+ interface BarrierResolution {
274
+ /**
275
+ * The completion dependency IDs the selected subtree actually reads,
276
+ * minus the sources it actively provides itself. These replace the
277
+ * static advisory ordering estimate with concrete edges.
278
+ */
279
+ readonly orderingDependencyIds: readonly symbol[];
280
+ /**
281
+ * The source IDs the selected subtree will actively publish: a
282
+ * guaranteed occurrence (an unconditional prompt or a publishable
283
+ * default) or one whose state already extracts a value. These
284
+ * replace {@link RuntimeNode.providesSourceIds} for ordering, so a
285
+ * sibling consumer no longer waits for a merely possible provider.
286
+ */
287
+ readonly activeProvidesSourceIds: ReadonlySet<symbol>;
288
+ }
227
289
  /**
228
290
  * Options for resolving matched derived source values.
229
291
  *
@@ -524,6 +586,35 @@ declare const sourceCollectionExpansionKey: unique symbol;
524
586
  * @since 1.3.0
525
587
  */
526
588
  declare const staticSourceScopeKey: unique symbol;
589
+ /**
590
+ * Static child parsers reachable only through a mutually exclusive
591
+ * selection, such as `or()` alternatives, a `conditional()`'s branches,
592
+ * or a `command()`'s inner parser.
593
+ *
594
+ * The walk unions sources and completion dependencies from this scope
595
+ * exactly like {@link staticSourceScopeKey}, but a source occurrence
596
+ * reached through an exclusive edge is never a *guaranteed* provider:
597
+ * the run may select a sibling instead, so a scheduling barrier must not
598
+ * assume the occurrence will publish. See issue #924.
599
+ *
600
+ * @internal
601
+ * @since 1.3.0
602
+ */
603
+ declare const exclusiveSourceScopeKey: unique symbol;
604
+ /**
605
+ * Source IDs a parser guarantees to provide on every successful route
606
+ * through it, regardless of which internal alternative is selected.
607
+ *
608
+ * A `conditional()` installs the intersection of its branches' guaranteed
609
+ * provides here (including the default branch when present), so an outer
610
+ * scheduling barrier can treat a source that *every* selectable route
611
+ * publishes as guaranteed even though each individual branch edge is
612
+ * exclusive. See issue #924.
613
+ *
614
+ * @internal
615
+ * @since 1.3.0
616
+ */
617
+ declare const guaranteedStaticSourceIdsKey: unique symbol;
527
618
  /**
528
619
  * Forwards effectful scheduling through a shape-preserving wrapper such
529
620
  * as `map()`, `optional()`, `withDefault()`, or `nonEmpty()`, so the
@@ -767,4 +858,4 @@ declare function buildRuntimeNodesFromArray(parsers: ReadonlyArray<{
767
858
  readonly initialState?: unknown;
768
859
  }>, stateArray: readonly unknown[], parentPath?: readonly PropertyKey[]): readonly RuntimeNode[];
769
860
  //#endregion
770
- export { BarrierCompletionDemandEdge, BarrierCompletionDependencies, CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, ResolveDerivedSourceValuesOptions, RuntimeNode, SchedulingBarrierContext, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, derivedRawInputKey, effectfulSchedulingNodesKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, hasInactiveCompletion, includeSourceFailureChain, orderDependencyNodes, recordDerivedRawInput, replayDerivedParser, replayDerivedParserAsync, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };
861
+ export { BarrierCompletionDemandEdge, BarrierCompletionDependencies, BarrierResolution, CompleteEffectfulSourcesOptions, DependencyRequest, DependencyResolution, DependencyRuntimeContext, EffectfulSchedulingNodesFn, EffectfulSourceCompletion, EffectfulSourceCompletionResult, ReplayKey, ResolveDerivedSourceValuesOptions, RuntimeNode, SchedulingBarrierContext, SourceDefaultFailure, buildRuntimeNodesFromArray, buildRuntimeNodesFromPairs, collectDemandedDependencyIds, collectExplicitSourceValues, collectExplicitSourceValuesAsync, collectSourcesFromState, completeEffectfulSourcesAsync, createDependencyFingerprint, createDependencyRuntimeContext, createReplayKey, defineForwardedEffectfulSchedulingNodes, derivedRawInputKey, effectfulSchedulingNodesKey, exclusiveSourceScopeKey, extractRawInputFromState, fillMissingSourceDefaults, fillMissingSourceDefaultsAsync, guaranteedStaticSourceIdsKey, hasInactiveCompletion, includeSourceFailureChain, orderDependencyNodes, recordDerivedRawInput, replayDerivedParser, replayDerivedParserAsync, resolveDerivedSourceValues, resolveDerivedSourceValuesAsync, resolveStateWithRuntime, resolveStateWithRuntimeAsync, serializeSchedulingPath, sourceCollectionExpansionKey, staticSourceScopeKey };