@abelxiaoxing/cadence 1.6.0 → 1.8.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.
- package/README.md +25 -4
- package/config/.env.example +6 -0
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/apply-transaction.ts +28 -8
- package/src/build-windows-job.mjs +95 -0
- package/src/candidate-workspace.ts +6 -0
- package/src/change-verification.ts +764 -67
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +21 -3
- package/src/durable-workflow.ts +55 -40
- package/src/execution-profile.ts +28 -2
- package/src/execution-retention.ts +77 -0
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/isolation-backend.ts +2 -1
- package/src/operator-cli.mjs +664 -104
- package/src/operator-tools.ts +7 -1
- package/src/package-state.ts +11 -0
- package/src/package-verification.ts +66 -18
- package/src/package-workflow.ts +6 -0
- package/src/phase-execution.ts +18 -7
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/remove-path.ts +32 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +18 -6
- package/src/verification-environment-io.ts +123 -14
- package/src/verification-environment.ts +50 -3
- package/src/windows-job-backend.ts +419 -0
- package/src/windows-job.c +149 -0
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
- package/src/workspace-io-entry.mjs +2 -2
- package/src/workspace-io-worker.mjs +207 -110
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
normalizeChangeContract,
|
|
8
8
|
retainedChangeContract,
|
|
9
9
|
} from "./change-contract.ts";
|
|
10
|
-
import { LIMITS } from "./contracts.ts";
|
|
10
|
+
import { LIMITS, verificationInputPaths } from "./contracts.ts";
|
|
11
11
|
import {
|
|
12
12
|
assertControlCommand,
|
|
13
13
|
type ControlCommand,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
type ActiveOperation,
|
|
46
46
|
assertDelivery,
|
|
47
47
|
type BootstrapRow,
|
|
48
|
+
baselineRecoveryKey,
|
|
48
49
|
bootstrapAcceptanceHash,
|
|
49
50
|
CHANGE_NAME,
|
|
50
51
|
cancellableRead,
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
isRecord,
|
|
63
64
|
isWorkflowApprovalCode,
|
|
64
65
|
normalizeBootstrapAcceptanceFacts,
|
|
66
|
+
normalizeVerificationPrerequisite,
|
|
65
67
|
normalizeWorkflowContextRequest,
|
|
66
68
|
normalizeWorkflowVerificationStatus,
|
|
67
69
|
parseJsonRecord,
|
|
@@ -1153,12 +1155,7 @@ export class WorkflowEngine {
|
|
|
1153
1155
|
serializedPlan,
|
|
1154
1156
|
);
|
|
1155
1157
|
|
|
1156
|
-
|
|
1157
|
-
database
|
|
1158
|
-
.prepare("SELECT 1 FROM workflow_work_budget WHERE run_id = ?")
|
|
1159
|
-
.get(runId)
|
|
1160
|
-
)
|
|
1161
|
-
this.#growWorkBudget(database, runId, delivery.plan);
|
|
1158
|
+
this.#growWorkBudget(database, runId, delivery.plan);
|
|
1162
1159
|
const admitted = new Set<string>();
|
|
1163
1160
|
delivery.plan.tasks.forEach((task, taskOrder) => {
|
|
1164
1161
|
admitted.add(task.taskId);
|
|
@@ -1595,8 +1592,12 @@ export class WorkflowEngine {
|
|
|
1595
1592
|
: undefined;
|
|
1596
1593
|
const priorDiagnostic = this.#safeAttemptDiagnostic(runId, row);
|
|
1597
1594
|
const priorRecovery = priorDiagnostic?.recovery;
|
|
1595
|
+
const preparationKey = baselineRecoveryKey(task, phase);
|
|
1598
1596
|
const activeRecovery =
|
|
1599
|
-
priorRecovery
|
|
1597
|
+
priorRecovery &&
|
|
1598
|
+
[recoveryKey(task, phase, row, current), preparationKey].includes(
|
|
1599
|
+
priorRecovery.key,
|
|
1600
|
+
)
|
|
1600
1601
|
? priorRecovery
|
|
1601
1602
|
: undefined;
|
|
1602
1603
|
const maxRecoveryAttempts = Math.min(
|
|
@@ -1619,9 +1620,10 @@ export class WorkflowEngine {
|
|
|
1619
1620
|
const granted = this.#hasRecoveryGrant(
|
|
1620
1621
|
runId,
|
|
1621
1622
|
operationId,
|
|
1622
|
-
recoveryKey(task, phase, row, current),
|
|
1623
|
+
activeRecovery?.key ?? recoveryKey(task, phase, row, current),
|
|
1623
1624
|
);
|
|
1624
1625
|
const verificationOnly =
|
|
1626
|
+
activeRecovery?.key !== preparationKey &&
|
|
1625
1627
|
recoveryExhausted(activeRecovery, maxRecoveryAttempts, granted) &&
|
|
1626
1628
|
(await this.#worker.hasPendingVerification?.({
|
|
1627
1629
|
runId,
|
|
@@ -1658,7 +1660,7 @@ export class WorkflowEngine {
|
|
|
1658
1660
|
);
|
|
1659
1661
|
return;
|
|
1660
1662
|
}
|
|
1661
|
-
if (activeRecovery)
|
|
1663
|
+
if (activeRecovery && activeRecovery.key !== preparationKey)
|
|
1662
1664
|
artifactAttempts = Math.max(artifactAttempts, activeRecovery.failures);
|
|
1663
1665
|
const repair =
|
|
1664
1666
|
verificationStatus?.scope === "change-task-affected" &&
|
|
@@ -1684,6 +1686,187 @@ export class WorkflowEngine {
|
|
|
1684
1686
|
: {}),
|
|
1685
1687
|
}
|
|
1686
1688
|
: undefined;
|
|
1689
|
+
if (this.#worker.prepareTask) {
|
|
1690
|
+
// Reserve this selected task's conflicts while asynchronous baseline
|
|
1691
|
+
// preparation runs, without reserving a Worker attempt.
|
|
1692
|
+
this.#setTask(
|
|
1693
|
+
runId,
|
|
1694
|
+
row.task_id,
|
|
1695
|
+
{ state: "phase-ready", phase, queuePosition: null },
|
|
1696
|
+
lease,
|
|
1697
|
+
);
|
|
1698
|
+
if (granted && activeRecovery?.key === preparationKey) {
|
|
1699
|
+
this.#leasedTransaction(lease, () => {
|
|
1700
|
+
const consumed = this.#database
|
|
1701
|
+
.prepare(
|
|
1702
|
+
"UPDATE workflow_recovery_grants SET consumed = 1 WHERE run_id = ? AND operation_id = ? AND incident_key = ? AND consumed = 0",
|
|
1703
|
+
)
|
|
1704
|
+
.run(runId, operationId, preparationKey);
|
|
1705
|
+
if (consumed.changes !== 1)
|
|
1706
|
+
throw new Error("recovery-request-stale");
|
|
1707
|
+
this.#database
|
|
1708
|
+
.prepare(`INSERT INTO workflow_recovery_events(run_id, sequence, incident_key, kind)
|
|
1709
|
+
SELECT ?, COALESCE(MAX(sequence), 0) + 1, ?, 'launch' FROM workflow_recovery_events WHERE run_id = ?`)
|
|
1710
|
+
.run(runId, preparationKey, runId);
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
const prepared = await this.#worker.prepareTask({
|
|
1714
|
+
runId,
|
|
1715
|
+
operationId,
|
|
1716
|
+
deliveryRevision: row.delivery_revision,
|
|
1717
|
+
taskId: row.task_id,
|
|
1718
|
+
phase,
|
|
1719
|
+
task: structuredClone(task),
|
|
1720
|
+
plan: structuredClone(plan),
|
|
1721
|
+
...(current.baseline_workspace_revision
|
|
1722
|
+
? { baselineRevisionId: current.baseline_workspace_revision }
|
|
1723
|
+
: {}),
|
|
1724
|
+
...(current.current_workspace_revision
|
|
1725
|
+
? { currentWorkspaceRevisionId: current.current_workspace_revision }
|
|
1726
|
+
: {}),
|
|
1727
|
+
signal,
|
|
1728
|
+
});
|
|
1729
|
+
this.#runStore.assertLease(lease);
|
|
1730
|
+
if (prepared.kind === "phase-committed")
|
|
1731
|
+
throw new Error("workflow-preparation-outcome-invalid");
|
|
1732
|
+
this.#recordWorkspaceFacts(
|
|
1733
|
+
runId,
|
|
1734
|
+
{
|
|
1735
|
+
...prepared,
|
|
1736
|
+
kind: "paused",
|
|
1737
|
+
code: "task-prepared",
|
|
1738
|
+
// Preparation has no merge authority over a sibling's newer commit.
|
|
1739
|
+
...(prepared.currentWorkspaceRevisionId
|
|
1740
|
+
? {
|
|
1741
|
+
currentWorkspaceRevisionId:
|
|
1742
|
+
this.#engineRun(runId).current_workspace_revision ??
|
|
1743
|
+
prepared.currentWorkspaceRevisionId,
|
|
1744
|
+
}
|
|
1745
|
+
: {}),
|
|
1746
|
+
},
|
|
1747
|
+
lease,
|
|
1748
|
+
);
|
|
1749
|
+
// Preparation may finish successfully while close is settling it.
|
|
1750
|
+
// Re-enter the cancellation guard before granting any Worker work.
|
|
1751
|
+
if (signal.aborted) continue;
|
|
1752
|
+
if (prepared.kind !== "prepared") {
|
|
1753
|
+
const prerequisite =
|
|
1754
|
+
prepared.kind !== "operation-cancelled"
|
|
1755
|
+
? normalizeVerificationPrerequisite(prepared.prerequisite)
|
|
1756
|
+
: undefined;
|
|
1757
|
+
if (prerequisite) {
|
|
1758
|
+
const verification =
|
|
1759
|
+
prerequisite.scope === "baseline-full-suite"
|
|
1760
|
+
? plan.verification.baseline?.fullSuite
|
|
1761
|
+
: (task.baselineVerification ?? task.affectedVerification);
|
|
1762
|
+
if (
|
|
1763
|
+
!verification ||
|
|
1764
|
+
verification.id !== prerequisite.verificationId ||
|
|
1765
|
+
(prerequisite.scope === "baseline-task-affected" &&
|
|
1766
|
+
prerequisite.taskId !== row.task_id) ||
|
|
1767
|
+
(prerequisite.input &&
|
|
1768
|
+
!verificationInputPaths(verification).includes(
|
|
1769
|
+
prerequisite.input.path,
|
|
1770
|
+
)) ||
|
|
1771
|
+
(prerequisite.producer &&
|
|
1772
|
+
!plan.outputs.some(
|
|
1773
|
+
(output) =>
|
|
1774
|
+
output.path === prerequisite.input?.path &&
|
|
1775
|
+
output.producer.taskId === prerequisite.producer?.taskId &&
|
|
1776
|
+
output.producer.phase === prerequisite.producer?.phase,
|
|
1777
|
+
))
|
|
1778
|
+
)
|
|
1779
|
+
throw new Error("workflow-prerequisite-evidence-invalid");
|
|
1780
|
+
}
|
|
1781
|
+
if (prerequisite && prepared.kind === "paused") {
|
|
1782
|
+
const identity = hash(
|
|
1783
|
+
canonicalJson({
|
|
1784
|
+
...prerequisite,
|
|
1785
|
+
verificationId: undefined,
|
|
1786
|
+
taskId: undefined,
|
|
1787
|
+
producer: prerequisite.producer
|
|
1788
|
+
? { phase: prerequisite.producer.phase }
|
|
1789
|
+
: undefined,
|
|
1790
|
+
}),
|
|
1791
|
+
);
|
|
1792
|
+
const priorPreparation = this.#recovery(runId, preparationKey);
|
|
1793
|
+
if (priorPreparation?.feedback.prerequisiteIdentity !== identity) {
|
|
1794
|
+
const failures = (priorPreparation?.failures ?? 0) + 1;
|
|
1795
|
+
this.#recordRecovery(
|
|
1796
|
+
runId,
|
|
1797
|
+
{
|
|
1798
|
+
key: preparationKey,
|
|
1799
|
+
failures,
|
|
1800
|
+
feedback: {
|
|
1801
|
+
code: prepared.code,
|
|
1802
|
+
attempt: failures + 1,
|
|
1803
|
+
maxAttempts: maxRecoveryAttempts,
|
|
1804
|
+
strategy: "repair-verification",
|
|
1805
|
+
prerequisiteIdentity: identity,
|
|
1806
|
+
},
|
|
1807
|
+
},
|
|
1808
|
+
lease,
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (prepared.kind !== "operation-cancelled" && prepared.verification)
|
|
1813
|
+
this.#setVerificationStatus(runId, prepared.verification, lease);
|
|
1814
|
+
this.#setTask(
|
|
1815
|
+
runId,
|
|
1816
|
+
row.task_id,
|
|
1817
|
+
{
|
|
1818
|
+
state:
|
|
1819
|
+
prepared.kind === "approval-needed"
|
|
1820
|
+
? "approval-needed"
|
|
1821
|
+
: "paused",
|
|
1822
|
+
phase,
|
|
1823
|
+
pauseCode:
|
|
1824
|
+
prepared.kind === "operation-cancelled"
|
|
1825
|
+
? "operation-cancelled"
|
|
1826
|
+
: prepared.code,
|
|
1827
|
+
attemptDiagnostic: {
|
|
1828
|
+
...priorDiagnostic,
|
|
1829
|
+
prerequisite,
|
|
1830
|
+
},
|
|
1831
|
+
queuePosition: null,
|
|
1832
|
+
},
|
|
1833
|
+
lease,
|
|
1834
|
+
);
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (priorDiagnostic) delete priorDiagnostic.prerequisite;
|
|
1838
|
+
if (activeRecovery?.key === preparationKey) {
|
|
1839
|
+
// A successful original-baseline observation resolves this incident.
|
|
1840
|
+
// Keep its events and immutable failure evidence, then re-enter the
|
|
1841
|
+
// ordinary phase recovery checks before reserving Worker work.
|
|
1842
|
+
this.#leasedTransaction(lease, () => {
|
|
1843
|
+
this.#database
|
|
1844
|
+
.prepare(`INSERT INTO workflow_recovery_events(run_id, sequence, incident_key, kind)
|
|
1845
|
+
SELECT ?, COALESCE(MAX(sequence), 0) + 1, ?, 'resolved' FROM workflow_recovery_events WHERE run_id = ?`)
|
|
1846
|
+
.run(runId, preparationKey, runId);
|
|
1847
|
+
this.#database
|
|
1848
|
+
.prepare(
|
|
1849
|
+
"DELETE FROM workflow_recovery_incidents WHERE run_id = ? AND incident_key = ?",
|
|
1850
|
+
)
|
|
1851
|
+
.run(runId, preparationKey);
|
|
1852
|
+
});
|
|
1853
|
+
if (priorDiagnostic) delete priorDiagnostic.recovery;
|
|
1854
|
+
const retainedDiagnostic =
|
|
1855
|
+
priorDiagnostic && Object.keys(priorDiagnostic).length
|
|
1856
|
+
? priorDiagnostic
|
|
1857
|
+
: null;
|
|
1858
|
+
this.#setTask(
|
|
1859
|
+
runId,
|
|
1860
|
+
row.task_id,
|
|
1861
|
+
{ attemptDiagnostic: retainedDiagnostic },
|
|
1862
|
+
lease,
|
|
1863
|
+
);
|
|
1864
|
+
row.attempt_diagnostic_json = retainedDiagnostic
|
|
1865
|
+
? JSON.stringify(retainedDiagnostic)
|
|
1866
|
+
: null;
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1687
1870
|
this.#setTask(
|
|
1688
1871
|
runId,
|
|
1689
1872
|
row.task_id,
|
|
@@ -1857,6 +2040,8 @@ export class WorkflowEngine {
|
|
|
1857
2040
|
? { action: "rebind-or-revise-delivery" as const }
|
|
1858
2041
|
: {}),
|
|
1859
2042
|
};
|
|
2043
|
+
// Only parent preparation can contribute original-baseline authority.
|
|
2044
|
+
delete attemptDiagnostic.prerequisite;
|
|
1860
2045
|
this.#setTask(runId, row.task_id, { attemptDiagnostic }, lease);
|
|
1861
2046
|
row.attempt_diagnostic_json = JSON.stringify(attemptDiagnostic);
|
|
1862
2047
|
}
|
|
@@ -2741,73 +2926,90 @@ export class WorkflowEngine {
|
|
|
2741
2926
|
this.#setVerificationStatus(runId, null, lease);
|
|
2742
2927
|
}
|
|
2743
2928
|
const attempted = new Set<string>();
|
|
2744
|
-
|
|
2745
|
-
while
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
const
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2929
|
+
// Keep every launched operation owned until it settles, including failures.
|
|
2930
|
+
// A fulfilled envelope avoids unhandled rejections while another task wins
|
|
2931
|
+
// the race; the finally barrier also protects cancellation and storage close.
|
|
2932
|
+
type Settlement =
|
|
2933
|
+
| { taskId: string; status: "fulfilled" }
|
|
2934
|
+
| { taskId: string; status: "rejected"; reason: unknown };
|
|
2935
|
+
const pending = new Map<string, Promise<Settlement>>();
|
|
2936
|
+
let firstFailure: { reason: unknown } | undefined;
|
|
2937
|
+
try {
|
|
2938
|
+
while (!signal.aborted) {
|
|
2939
|
+
if (firstFailure) throw firstFailure.reason;
|
|
2940
|
+
const rows = this.#tasks(runId);
|
|
2941
|
+
const selected = selectRunnableTasks({
|
|
2942
|
+
rows,
|
|
2943
|
+
attempted,
|
|
2944
|
+
activeTasks: this.#activeTasks,
|
|
2945
|
+
capacity: LIMITS.maxActiveChildSessions,
|
|
2946
|
+
nextQueuePosition: this.#engineRun(runId).next_queue_position,
|
|
2947
|
+
});
|
|
2948
|
+
const runnable = selected.runnable.map((taskId) => {
|
|
2949
|
+
const row = rows.find((candidate) => candidate.task_id === taskId);
|
|
2950
|
+
if (!row) throw new Error("scheduler-task-unavailable");
|
|
2951
|
+
attempted.add(taskId);
|
|
2952
|
+
return row;
|
|
2953
|
+
});
|
|
2954
|
+
for (const update of selected.updates) {
|
|
2955
|
+
const row = rows.find(
|
|
2956
|
+
(candidate) => candidate.task_id === update.taskId,
|
|
2772
2957
|
);
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
runnable.
|
|
2784
|
-
|
|
2958
|
+
if (!row) throw new Error("scheduler-task-unavailable");
|
|
2959
|
+
if (update.kind === "pending")
|
|
2960
|
+
this.#setTask(
|
|
2961
|
+
runId,
|
|
2962
|
+
row.task_id,
|
|
2963
|
+
{ state: "pending", pauseCode: null, queuePosition: null },
|
|
2964
|
+
lease,
|
|
2965
|
+
);
|
|
2966
|
+
else this.#queueTask(runId, row, lease, update.reason);
|
|
2967
|
+
}
|
|
2968
|
+
this.#activeTasks += runnable.length;
|
|
2969
|
+
for (const row of runnable) {
|
|
2970
|
+
const operation = this.#runTask(
|
|
2785
2971
|
runId,
|
|
2786
2972
|
row,
|
|
2787
2973
|
operationId,
|
|
2788
2974
|
signal,
|
|
2789
2975
|
lease,
|
|
2790
2976
|
onActivity,
|
|
2791
|
-
)
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
)
|
|
2806
|
-
|
|
2807
|
-
|
|
2977
|
+
)
|
|
2978
|
+
.then<Settlement, Settlement>(
|
|
2979
|
+
() => ({ taskId: row.task_id, status: "fulfilled" }),
|
|
2980
|
+
(reason: unknown) => {
|
|
2981
|
+
firstFailure ??= { reason };
|
|
2982
|
+
return { taskId: row.task_id, status: "rejected", reason };
|
|
2983
|
+
},
|
|
2984
|
+
)
|
|
2985
|
+
.finally(() => {
|
|
2986
|
+
this.#activeTasks--;
|
|
2987
|
+
for (const wake of this.#capacityWaiters) wake();
|
|
2988
|
+
});
|
|
2989
|
+
pending.set(row.task_id, operation);
|
|
2990
|
+
}
|
|
2991
|
+
if (pending.size > 0) {
|
|
2992
|
+
const settled = await Promise.race([
|
|
2993
|
+
...pending.values(),
|
|
2994
|
+
...(selected.capacityBlocked
|
|
2995
|
+
? [this.#waitForCapacity(signal).then(() => null)]
|
|
2996
|
+
: []),
|
|
2997
|
+
]);
|
|
2998
|
+
if (settled === null) continue;
|
|
2999
|
+
pending.delete(settled.taskId);
|
|
3000
|
+
if (settled.status === "rejected") throw settled.reason;
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
if (selected.capacityBlocked) {
|
|
3004
|
+
await this.#waitForCapacity(signal);
|
|
3005
|
+
continue;
|
|
2808
3006
|
}
|
|
3007
|
+
break;
|
|
2809
3008
|
}
|
|
3009
|
+
} finally {
|
|
3010
|
+
await Promise.all(pending.values());
|
|
2810
3011
|
}
|
|
3012
|
+
if (firstFailure) throw firstFailure.reason;
|
|
2811
3013
|
|
|
2812
3014
|
if (signal.aborted) {
|
|
2813
3015
|
const current = this.#runStore.status(runId);
|
|
@@ -2898,8 +3100,11 @@ export class WorkflowEngine {
|
|
|
2898
3100
|
|
|
2899
3101
|
#recoveryConditions(runId: string, key: string) {
|
|
2900
3102
|
const run = this.#engineRun(runId);
|
|
2901
|
-
const task = this.#tasks(runId).find(
|
|
2902
|
-
|
|
3103
|
+
const task = this.#tasks(runId).find((row) =>
|
|
3104
|
+
[
|
|
3105
|
+
recoveryKey(this.#taskPlan(row), row.phase, row, run),
|
|
3106
|
+
baselineRecoveryKey(this.#taskPlan(row), row.phase),
|
|
3107
|
+
].includes(key),
|
|
2903
3108
|
);
|
|
2904
3109
|
return {
|
|
2905
3110
|
route: task?.route_fingerprint ?? run.route_fingerprint,
|
|
@@ -2939,12 +3144,15 @@ export class WorkflowEngine {
|
|
|
2939
3144
|
const current = this.#tasks(runId).some(
|
|
2940
3145
|
(row) =>
|
|
2941
3146
|
["paused", "retryable"].includes(row.state) &&
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
3147
|
+
[
|
|
3148
|
+
recoveryKey(
|
|
3149
|
+
this.#taskPlan(row),
|
|
3150
|
+
row.phase,
|
|
3151
|
+
row,
|
|
3152
|
+
this.#engineRun(runId),
|
|
3153
|
+
),
|
|
3154
|
+
baselineRecoveryKey(this.#taskPlan(row), row.phase),
|
|
3155
|
+
].includes(request.incidentKey),
|
|
2948
3156
|
);
|
|
2949
3157
|
if (
|
|
2950
3158
|
!current ||
|
|
@@ -3080,10 +3288,20 @@ export class WorkflowEngine {
|
|
|
3080
3288
|
? (JSON.parse(row.attempt_diagnostic_json) as SafeAttemptDiagnostic)
|
|
3081
3289
|
: {};
|
|
3082
3290
|
delete diagnostic.recovery;
|
|
3083
|
-
const recovery =
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3291
|
+
const recovery =
|
|
3292
|
+
this.#recovery(
|
|
3293
|
+
runId,
|
|
3294
|
+
baselineRecoveryKey(this.#taskPlan(row), row.phase),
|
|
3295
|
+
) ??
|
|
3296
|
+
this.#recovery(
|
|
3297
|
+
runId,
|
|
3298
|
+
recoveryKey(
|
|
3299
|
+
this.#taskPlan(row),
|
|
3300
|
+
row.phase,
|
|
3301
|
+
row,
|
|
3302
|
+
this.#engineRun(runId),
|
|
3303
|
+
),
|
|
3304
|
+
);
|
|
3087
3305
|
if (recovery) diagnostic.recovery = recovery;
|
|
3088
3306
|
return Object.keys(diagnostic).length ? diagnostic : undefined;
|
|
3089
3307
|
}
|
|
@@ -3794,8 +4012,24 @@ export class WorkflowEngine {
|
|
|
3794
4012
|
lease,
|
|
3795
4013
|
);
|
|
3796
4014
|
}
|
|
3797
|
-
outcome = this.#statusByRun(runId);
|
|
3798
4015
|
await activeSettlement;
|
|
4016
|
+
for (const task of this.#tasks(runId)) {
|
|
4017
|
+
if (
|
|
4018
|
+
["phase-ready", "phase-running", "validating"].includes(task.state)
|
|
4019
|
+
) {
|
|
4020
|
+
this.#setTask(
|
|
4021
|
+
runId,
|
|
4022
|
+
task.task_id,
|
|
4023
|
+
{
|
|
4024
|
+
state: "paused",
|
|
4025
|
+
pauseCode: "operation-cancelled",
|
|
4026
|
+
queuePosition: null,
|
|
4027
|
+
},
|
|
4028
|
+
lease,
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
outcome = this.#statusByRun(runId);
|
|
3799
4033
|
}
|
|
3800
4034
|
outcome = {
|
|
3801
4035
|
...outcome,
|