@sensigo/realm 0.38.0 → 0.39.0

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 (43) hide show
  1. package/dist/engine/abandon-run.d.ts.map +1 -1
  2. package/dist/engine/abandon-run.js +5 -7
  3. package/dist/engine/abandon-run.js.map +1 -1
  4. package/dist/engine/apply-resume.d.ts.map +1 -1
  5. package/dist/engine/apply-resume.js +13 -1
  6. package/dist/engine/apply-resume.js.map +1 -1
  7. package/dist/engine/eligibility.d.ts +85 -2
  8. package/dist/engine/eligibility.d.ts.map +1 -1
  9. package/dist/engine/eligibility.js +271 -14
  10. package/dist/engine/eligibility.js.map +1 -1
  11. package/dist/engine/execution-loop.d.ts.map +1 -1
  12. package/dist/engine/execution-loop.js +164 -34
  13. package/dist/engine/execution-loop.js.map +1 -1
  14. package/dist/engine/run-health.d.ts.map +1 -1
  15. package/dist/engine/run-health.js.map +1 -1
  16. package/dist/engine/settlement.d.ts +61 -6
  17. package/dist/engine/settlement.d.ts.map +1 -1
  18. package/dist/engine/settlement.js +190 -25
  19. package/dist/engine/settlement.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +9 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/store/json-file-store.d.ts +9 -2
  25. package/dist/store/json-file-store.d.ts.map +1 -1
  26. package/dist/store/json-file-store.js +76 -0
  27. package/dist/store/json-file-store.js.map +1 -1
  28. package/dist/store/seal-integrity.d.ts +9 -0
  29. package/dist/store/seal-integrity.d.ts.map +1 -0
  30. package/dist/store/seal-integrity.js +249 -0
  31. package/dist/store/seal-integrity.js.map +1 -0
  32. package/dist/store/store-interface.d.ts +47 -2
  33. package/dist/store/store-interface.d.ts.map +1 -1
  34. package/dist/types/run-record.d.ts +123 -6
  35. package/dist/types/run-record.d.ts.map +1 -1
  36. package/dist/types/run-record.js +25 -1
  37. package/dist/types/run-record.js.map +1 -1
  38. package/dist/types/workflow-error.d.ts +1 -1
  39. package/dist/types/workflow-error.d.ts.map +1 -1
  40. package/dist/types/workflow-error.js.map +1 -1
  41. package/dist/workflow/yaml-loader.js +109 -0
  42. package/dist/workflow/yaml-loader.js.map +1 -1
  43. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { storeDeclaresSeal, storeDeclaresNonceCarriage } from '../store/trace-bu
5
5
  import { partitionBufferedEntries } from './trace-adoption.js';
6
6
  import { deriveDefaultedSteps } from './defaulted-steps.js';
7
7
  import { computeGateDueState } from './gate-timing.js';
8
- import { selectFinalizers, deriveEffectiveTriggers, applySettlement } from './settlement.js';
8
+ import { selectFinalizers, deriveEffectiveTriggers, applySettlement, renderFailCause, failureMessagesFromEvidence, failureMessagesWithOverlay, } from './settlement.js';
9
9
  import { captureEvidence } from '../evidence/snapshot.js';
10
10
  import { validateInputSchema, validateOutputSchema, validateTraceSchema, } from '../validation/input-schema.js';
11
11
  import { normalizeTrace } from './trace-normalizer.js';
@@ -18,7 +18,7 @@ import { createDefaultRegistry } from '../extensions/default-registry.js';
18
18
  import { renderTemplate, resolvePath, UnknownFilterError } from './render-template.js';
19
19
  import { generateSchemaSkeleton } from '../utils/schema-skeleton.js';
20
20
  import { loadWorkflowContext } from './workflow-context-loader.js';
21
- import { findEligibleSteps, findEligibleGuardSteps, isWorkflowComplete, buildEvidenceByStep, propagateSkips, deriveRunPhase, } from './eligibility.js';
21
+ import { findEligibleSteps, findEligibleGuardSteps, isWorkflowComplete, buildEvidenceByStep, propagateSkips, deriveRunPhase, armToOutcome, assertSealMarkersAgree, } from './eligibility.js';
22
22
  import { requirementForStep } from './capability.js';
23
23
  import { resolvePreExecutionAgentAction, resolvePostDispatchAgentAction, } from './error-resolution.js';
24
24
  function delayMs(ms) {
@@ -1764,6 +1764,8 @@ export async function executeStep(store, definition, options) {
1764
1764
  const abortDraft = {
1765
1765
  ...withAllSkipped,
1766
1766
  terminal_state: true,
1767
+ // issue #367: the seal fields live in the SAME object literal as the terminal flip.
1768
+ sealed_by: { arm: 'handler_abort', step: options.command },
1767
1769
  terminal_reason: `Handler '${options.command}' aborted the run: ${abortMessage}`,
1768
1770
  aborted_at: {
1769
1771
  step_id: options.command,
@@ -2394,14 +2396,34 @@ export async function executeStep(store, definition, options) {
2394
2396
  (withSkippedFail.in_progress_steps.length === 0 &&
2395
2397
  findEligibleSteps(definition, withSkippedFail).length === 0 &&
2396
2398
  findEligibleGuardSteps(definition, withSkippedFail).length === 0);
2397
- const failDraft = {
2398
- ...withSkippedFail,
2399
- evidence: [...pendingRun.evidence, ...allEvidence],
2400
- terminal_state: isComplete,
2401
- ...(isComplete
2402
- ? { terminal_reason: `Step '${options.command}' failed: ${dispatchError.message}` }
2403
- : {}),
2404
- };
2399
+ // Hoisted so the #373 message walk below reads the SAME evidence the record will carry —
2400
+ // `withSkippedFail` does not yet include this attempt's snapshots.
2401
+ const failEvidence = [...pendingRun.evidence, ...allEvidence];
2402
+ // issue #373 — twin of settlement.ts's fail seal; both layers must emit byte-identical
2403
+ // sentences for identical inputs. Single-failure shape unchanged (this site has never had the
2404
+ // `?? 'unknown error'` fallback `dispatchError.message` is always a string).
2405
+ const failCause = new Set(withSkippedFail.failed_steps).size > 1
2406
+ ? renderFailCause(withSkippedFail.failed_steps, failureMessagesWithOverlay(failEvidence, options.command, dispatchError.message))
2407
+ : `Step '${options.command}' failed: ${dispatchError.message}`;
2408
+ // issue #367: seal fields staged FIRST in the SAME object literal, and a stale prior seal
2409
+ // never survives either fork. #373's `failCause` (computed above) is untouched — the arm sits
2410
+ // BESIDE the rendered sentence, never inside it. Validation exhaustion is deliberately NOT a
2411
+ // distinct arm: a VALIDATION_EXHAUSTED failure seals 'step_failure' like any other terminal
2412
+ // step failure, and the distinction lives in defaulted_steps/diagnostics.
2413
+ const { sealed_by: _priorFailSeal, ...withSkippedFailBase } = withSkippedFail;
2414
+ const failDraft = isComplete
2415
+ ? {
2416
+ ...withSkippedFailBase,
2417
+ terminal_state: true,
2418
+ sealed_by: { arm: 'step_failure', step: options.command },
2419
+ evidence: failEvidence,
2420
+ terminal_reason: failCause,
2421
+ }
2422
+ : {
2423
+ ...withSkippedFailBase,
2424
+ terminal_state: false,
2425
+ evidence: failEvidence,
2426
+ };
2405
2427
  // On the terminal transition, drain the fail/always finalizers before the single seal.
2406
2428
  // Non-terminal failures (recovery steps remain) run no finalizers.
2407
2429
  const failedRun = isComplete
@@ -2979,11 +3001,16 @@ export async function executeStep(store, definition, options) {
2979
3001
  (withSkippedComplete.in_progress_steps.length === 0 &&
2980
3002
  findEligibleSteps(definition, withSkippedComplete).length === 0 &&
2981
3003
  findEligibleGuardSteps(definition, withSkippedComplete).length === 0);
2982
- const completeDraft = {
2983
- ...withSkippedComplete,
2984
- terminal_state: isComplete,
2985
- ...(isComplete ? { terminal_reason: `Workflow completed.` } : {}),
2986
- };
3004
+ // issue #367: seal fields staged first in the SAME object literal; stale prior seal stripped.
3005
+ const { sealed_by: _priorCompleteSeal, ...withSkippedCompleteBase } = withSkippedComplete;
3006
+ const completeDraft = isComplete
3007
+ ? {
3008
+ ...withSkippedCompleteBase,
3009
+ terminal_state: true,
3010
+ sealed_by: { arm: 'complete', step: options.command },
3011
+ terminal_reason: `Workflow completed.`,
3012
+ }
3013
+ : { ...withSkippedCompleteBase, terminal_state: false };
2987
3014
  // On the terminal transition, drain the complete/always finalizers before the single seal.
2988
3015
  // issue #220 PR-2 (D6): stamp defaulted_steps onto the SEALED terminal record only — the
2989
3016
  // non-terminal branch (`completeDraft`) is never stamped (FM-5 guard: it must never leak onto a
@@ -3619,7 +3646,11 @@ export async function submitHumanResponse(store, definition, options) {
3619
3646
  ? { gate_message: run.pending_gate.resolved_message }
3620
3647
  : {}),
3621
3648
  };
3622
- const { pending_gate: _pg, terminal_reason: _tr, ...rest } = run;
3649
+ // issue #367: `sealed_by` joins the strip list this write re-derives the run's liveness from
3650
+ // scratch, so a stale seal (a grandfathered/mixed-fleet record) must not survive it. The
3651
+ // explicit `terminal_state: false` below keeps the strip inside the store boundary's ORPHANED
3652
+ // exemption: a strip site flips non-terminal in the SAME write.
3653
+ const { pending_gate: _pg, terminal_reason: _tr, sealed_by: _sb, ...rest } = run;
3623
3654
  const afterGate = {
3624
3655
  ...rest,
3625
3656
  in_progress_steps: rest.in_progress_steps.filter((s) => s !== gateStepName),
@@ -3643,11 +3674,16 @@ export async function submitHumanResponse(store, definition, options) {
3643
3674
  (withSkippedGate.in_progress_steps.length === 0 &&
3644
3675
  findEligibleSteps(definition, withSkippedGate).length === 0 &&
3645
3676
  findEligibleGuardSteps(definition, withSkippedGate).length === 0);
3646
- const gateDraft = {
3647
- ...withSkippedGate,
3648
- terminal_state: isComplete,
3649
- ...(isComplete ? { terminal_reason: `Workflow completed.` } : {}),
3650
- };
3677
+ // issue #367: seal fields staged first (the prior seal was already stripped at the `afterGate`
3678
+ // construction above, so this fork starts clean by construction).
3679
+ const gateDraft = isComplete
3680
+ ? {
3681
+ ...withSkippedGate,
3682
+ terminal_state: true,
3683
+ sealed_by: { arm: 'gate_resolution_complete', step: gateStepName },
3684
+ terminal_reason: `Workflow completed.`,
3685
+ }
3686
+ : { ...withSkippedGate, terminal_state: false };
3651
3687
  // On the gate-completion terminal transition, drain complete/always finalizers before seal.
3652
3688
  // issue #220 PR-2 (D6): stamp defaulted_steps onto the SEALED terminal record only (never the
3653
3689
  // non-terminal `gateDraft` — the FM-5 guard).
@@ -3736,7 +3772,19 @@ async function executeGuardStep(stepName, stepDef, definition, run) {
3736
3772
  completedAt: now,
3737
3773
  input: {},
3738
3774
  output: { error: `Unresolvable path: ${outcome.unresolvable_path}` },
3739
- error: `Guard resolution error on condition: ${outcome.condition}`,
3775
+ // issue #373 correction: the path is the DIAGNOSTIC, and it used to live only in
3776
+ // `output_summary` + a transient seal-time overlay — so the post-drain re-render, which
3777
+ // rebuilds the cause from evidence alone, replaced it with the generic condition text.
3778
+ // Carrying it here makes every downstream read of this failure lossless. Sole production
3779
+ // mint: the settlement delta reuses this exact snapshot via `guardOwnEvidence`.
3780
+ //
3781
+ // The path goes FIRST because the per-message cap slices from the head: with the path last,
3782
+ // a long enough condition pushed it off the tail and the diagnostic vanished again. Honest
3783
+ // bound: a pathological PATH over ~230 chars still truncates itself, which is accepted —
3784
+ // head-first truncation keeps its prefix, and the prefix is the orienting part. ASCII
3785
+ // parenthetical, not an em dash, for the same reason the truncation marker is ASCII (logs,
3786
+ // terminals, a Postgres text column) — and a cut-off parenthetical reads as obviously partial.
3787
+ error: `Guard resolution error: unresolvable path '${outcome.unresolvable_path}' (condition: ${outcome.condition})`,
3740
3788
  });
3741
3789
  const withFailed = {
3742
3790
  ...run,
@@ -3749,10 +3797,18 @@ async function executeGuardStep(stepName, stepDef, definition, run) {
3749
3797
  skipped_steps: resolutionErrorPropagated.skipped,
3750
3798
  skip_details: resolutionErrorPropagated.details,
3751
3799
  };
3800
+ // issue #373 — twin of settlement.ts's guard seal. The overlay is DEFENSIVE once evidence
3801
+ // carries the path (issue #373 correction, the `error` above); kept against caller-shaped
3802
+ // evidence that arrives without it.
3803
+ const guardPath = `unresolvable path '${outcome.unresolvable_path}'`;
3752
3804
  return {
3753
3805
  ...withSkipped,
3754
3806
  terminal_state: true,
3755
- terminal_reason: `Guard step '${stepName}' failed: unresolvable path '${outcome.unresolvable_path}'`,
3807
+ // issue #367: the arm sits BESIDE #373's rendered sentence, never inside it.
3808
+ sealed_by: { arm: 'guard_resolution_error', step: stepName },
3809
+ terminal_reason: new Set(withSkipped.failed_steps).size > 1
3810
+ ? renderFailCause(withSkipped.failed_steps, failureMessagesWithOverlay(withSkipped.evidence, stepName, guardPath))
3811
+ : `Guard step '${stepName}' failed: ${guardPath}`,
3756
3812
  };
3757
3813
  }
3758
3814
  if (outcome.kind === 'pass') {
@@ -3779,11 +3835,16 @@ async function executeGuardStep(stepName, stepDef, definition, run) {
3779
3835
  (withSkipped.in_progress_steps.length === 0 &&
3780
3836
  findEligibleSteps(definition, withSkipped).length === 0 &&
3781
3837
  findEligibleGuardSteps(definition, withSkipped).length === 0);
3782
- return {
3783
- ...withSkipped,
3784
- terminal_state: isComplete,
3785
- ...(isComplete ? { terminal_reason: 'Workflow completed.' } : {}),
3786
- };
3838
+ // issue #367: the stamp lives INSIDE the isComplete arm ONLY — a non-terminal guard pass must
3839
+ // never carry a seal.
3840
+ return isComplete
3841
+ ? {
3842
+ ...withSkipped,
3843
+ terminal_state: true,
3844
+ sealed_by: { arm: 'guard_pass_complete', step: stepName },
3845
+ terminal_reason: 'Workflow completed.',
3846
+ }
3847
+ : { ...withSkipped, terminal_state: false };
3787
3848
  }
3788
3849
  // Guard fired — one or more conditions false; abort the run.
3789
3850
  const evidenceEntry = captureEvidence({
@@ -3816,6 +3877,10 @@ async function executeGuardStep(stepName, stepDef, definition, run) {
3816
3877
  return {
3817
3878
  ...withAllSkipped,
3818
3879
  terminal_state: true,
3880
+ // issue #367: same object literal as the terminal flip. terminal_reason stays ABSENT here (a
3881
+ // guard abort is the one reason-less seal) — the phase now derives from the arm, and
3882
+ // `aborted_at` is asserted congruent rather than consulted.
3883
+ sealed_by: { arm: 'guard_abort', step: stepName },
3819
3884
  aborted_at: {
3820
3885
  step_id: stepName,
3821
3886
  conditions: outcome.conditions,
@@ -3841,8 +3906,10 @@ async function executeGuardStep(stepName, stepDef, definition, run) {
3841
3906
  * `withTimeout` honoring `timeout_seconds` (default `DRAIN_CEILING_SECONDS`). Success →
3842
3907
  * evidence + completed_steps; thrown error / STEP_TIMEOUT / handler `{ abort }` → evidence
3843
3908
  * marked failed + failed_steps, NON-FATAL (the drain continues). A finalizer NEVER mutates
3844
- * `aborted_at`, `terminal_reason`, `terminal_state`, `skipped_steps`, or emits next_actions
3845
- * the terminal marks come from `sealDraft` and `deriveRunPhase` precedence keeps the phase.
3909
+ * `aborted_at`, `terminal_state`, `skipped_steps`, or emits next_actions, and never changes the
3910
+ * sealed OUTCOME — the terminal marks come from `sealDraft` and `deriveRunPhase` precedence keeps
3911
+ * the phase. issue #373: a finalizer whose OWN failure grows `failed_steps` on a fail-class seal
3912
+ * DOES re-render `terminal_reason`, so the one-line cause keeps agreeing with the record.
3846
3913
  */
3847
3914
  async function buildFinalizedSeal(definition, sealDraft, outcome, registry) {
3848
3915
  // Zero-finalizer fast path: no finalizer steps declared ⇒ return the seal draft
@@ -3858,6 +3925,13 @@ async function buildFinalizedSeal(definition, sealDraft, outcome, registry) {
3858
3925
  // legacy seal path for a non-declaring external store (the dormancy fallback).
3859
3926
  const settled = new Set([...sealDraft.completed_steps, ...sealDraft.failed_steps]);
3860
3927
  const selected = selectFinalizers(definition, settled, deriveEffectiveTriggers(outcome, sealDraft));
3928
+ // issue #367: the draft already carries sealed_by (every caller stamps at construction), so the
3929
+ // fast path above can never bypass the stamp.
3930
+ // issue #373: the post-drain re-render fires only if the drain ACTUALLY appended a failure.
3931
+ // The legacy seal is reached on every fail-class outcome, including one where every finalizer
3932
+ // succeeds — re-rendering there would rebuild the sentence without the seal site's in-hand
3933
+ // overlay (the guard sites' unresolvable-path text) and silently drop it.
3934
+ const failedBeforeDrain = sealDraft.failed_steps.length;
3861
3935
  let record = sealDraft;
3862
3936
  for (const name of selected) {
3863
3937
  const step = definition.steps[name];
@@ -3876,8 +3950,10 @@ async function buildFinalizedSeal(definition, sealDraft, outcome, registry) {
3876
3950
  try {
3877
3951
  const result = await withTimeout((signal) => callHandler(step, options, record, evidenceByStep, signal), timeoutMs, name);
3878
3952
  if (result.kind === 'abort') {
3879
- // A finalizer handler returning { abort } is a recorded NON-FATAL failure — it must
3880
- // never mutate aborted_at/terminal_reason (that would corrupt the sealed outcome).
3953
+ // A finalizer handler returning { abort } is a recorded NON-FATAL failure — it must never
3954
+ // mutate aborted_at or the sealed OUTCOME. (issue #373: it does join `failed_steps`, and
3955
+ // the post-drain re-render below folds it into the one-line cause — that reports the
3956
+ // outcome faithfully, it does not change it.)
3881
3957
  record = {
3882
3958
  ...record,
3883
3959
  evidence: [
@@ -3932,7 +4008,44 @@ async function buildFinalizedSeal(definition, sealDraft, outcome, registry) {
3932
4008
  };
3933
4009
  }
3934
4010
  }
3935
- return { ...record, terminal_state: true };
4011
+ // issue #367: `sealed_by` is INHERITED from sealDraft, never re-derived here — a finalizer never
4012
+ // changes which arm sealed the run, and re-deriving from `outcome` would re-couple the arm to
4013
+ // mintOutcome, the exact coupling the recorded fact exists to break. `sealDraft` is terminal at
4014
+ // every call site (all of them sit inside an isComplete/abort branch), so this narrow is total;
4015
+ // it throws rather than fabricating an arm if a future caller violates that.
4016
+ if (sealDraft.terminal_state !== true) {
4017
+ throw new Error('buildFinalizedSeal called with a non-terminal sealDraft');
4018
+ }
4019
+ // Conditional spread, NOT `sealed_by: sealDraft.sealed_by`: under the flat optional plus
4020
+ // exactOptionalPropertyTypes the unconditional form does not compile. An unstamped draft passes
4021
+ // through honest-absent, and refusing it is the store boundary's job.
4022
+ /* eslint-disable-next-line no-restricted-syntax --
4023
+ * issue #367 (part 2), AUTHORIZED: the stamp IS in this literal, as a conditional spread —
4024
+ * `exactOptionalPropertyTypes` forbids the unconditional `sealed_by: sealDraft.sealed_by` form,
4025
+ * and a spread is not a `sealed_by` Property the selector can see. An unstamped draft passes
4026
+ * through honest-absent on purpose; refusing it is the store boundary's job, per the comment
4027
+ * directly above.
4028
+ */
4029
+ const drained = {
4030
+ ...record,
4031
+ terminal_state: true,
4032
+ ...(sealDraft.sealed_by !== undefined ? { sealed_by: sealDraft.sealed_by } : {}),
4033
+ };
4034
+ // issue #367: SEAL_MARKERS_AGREE — transform-scoped assertion on the record THIS function
4035
+ // produces (the second of the two transform homes; never universal).
4036
+ assertSealMarkersAgree(drained);
4037
+ // Twin of applyMarkFinalizer's failed-arm re-render. `deriveRunPhase`, not `outcome`: an aborted
4038
+ // run whose finalizer also failed keeps its abort sentence (aborted_at wins), and a
4039
+ // complete-outcome run keeps the complete-seal literal untouched.
4040
+ if (record.failed_steps.length > failedBeforeDrain &&
4041
+ deriveRunPhase(drained) === 'failed' &&
4042
+ new Set(record.failed_steps).size > 1) {
4043
+ return {
4044
+ ...drained,
4045
+ terminal_reason: renderFailCause(record.failed_steps, failureMessagesFromEvidence(record.evidence)),
4046
+ };
4047
+ }
4048
+ return drained;
3936
4049
  }
3937
4050
  /** Every currently-`'pending'` finalizer in `run`'s ledger, ascending by rank — the order the
3938
4051
  * drain loop below consumes (design record §6). */
@@ -4412,13 +4525,30 @@ depth0Warnings) {
4412
4525
  // (terminal_reason 'Workflow completed.', no aborted_at). `aborted_at ? 'abort' : 'fail'`
4413
4526
  // would wrongly run the catch finalizers on that success. When terminal, drain the
4414
4527
  // matching finalizers before the single seal write; non-terminal guard passes persist as-is.
4415
- const guardOutcome = guardResult.terminal_state
4528
+ const guardProse = guardResult.terminal_state
4416
4529
  ? guardResult.aborted_at !== undefined
4417
4530
  ? 'abort'
4418
4531
  : guardResult.terminal_reason === 'Workflow completed.'
4419
4532
  ? 'complete'
4420
4533
  : 'fail'
4421
4534
  : undefined;
4535
+ // issue #367: this classifier stays the PRODUCER of guardOutcome (it feeds buildFinalizedSeal,
4536
+ // so a wrong answer here silently drains the wrong finalizers — the executed harm). It now
4537
+ // prefers the RECORDED arm, keeps the prose branch as the fallback for an unstamped record,
4538
+ // and throws if the two disagree: a future writer gap here is loud, never a silent loss.
4539
+ const guardArmOutcome = guardResult.terminal_state && guardResult.sealed_by !== undefined
4540
+ ? armToOutcome(guardResult.sealed_by.arm)
4541
+ : undefined;
4542
+ if (guardArmOutcome === 'abandon') {
4543
+ throw new Error(`guard seal on '${guardName}' produced a non-guard arm`);
4544
+ }
4545
+ if (guardArmOutcome !== undefined &&
4546
+ guardProse !== undefined &&
4547
+ guardArmOutcome !== guardProse) {
4548
+ throw new Error(`sealed_by.arm (${guardArmOutcome}) disagrees with the prose classifier (${guardProse}) ` +
4549
+ `on guard '${guardName}'`);
4550
+ }
4551
+ const guardOutcome = guardArmOutcome ?? guardProse;
4422
4552
  // issue #220 PR-2 (D6): stamp defaulted_steps ONLY on the 'complete' seal — a guard that FAILS
4423
4553
  // or ABORTS the run does not get the qualifier (the FM-5 guard: never on a non-complete
4424
4554
  // terminal, and never on the non-terminal `guardResult` passthrough).