@feltdb/core 0.4.12 → 0.4.13
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/dist/cli/commands.js +5 -2
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +16 -1
- package/dist/create/create.js +30 -16
- package/dist/create/docker-compose-generator.js +48 -9
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/// Workflow Runtime module for managing workflow execution and state
|
|
2
|
+
///
|
|
3
|
+
/// Provides durable state management through FeltDB operations,
|
|
4
|
+
/// reactive advancement, and distributed execution coordination.
|
|
5
|
+
use crate::{
|
|
6
|
+
BlockedReason, FlowRef, WorkflowGraph, WorkflowInstance, WorkflowOperation, WorkflowRef,
|
|
7
|
+
WorkflowState, WorkflowStepRef, WorkflowStepState,
|
|
8
|
+
};
|
|
9
|
+
use serde::{Deserialize, Serialize};
|
|
10
|
+
use std::collections::{HashMap, VecDeque};
|
|
11
|
+
|
|
12
|
+
/// Workflow runtime - manages workflow execution and state
|
|
13
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
14
|
+
pub struct WorkflowRuntime {
|
|
15
|
+
/// Workflow instances by instance ID
|
|
16
|
+
instances: HashMap<String, WorkflowInstance>,
|
|
17
|
+
/// Workflow definitions by workflow ref
|
|
18
|
+
graphs: HashMap<String, WorkflowGraph>,
|
|
19
|
+
/// Pending state transitions
|
|
20
|
+
pending_transitions: VecDeque<WorkflowStateTransition>,
|
|
21
|
+
/// Emitted operations (for testing and debugging)
|
|
22
|
+
operations_log: Vec<WorkflowOperationEntry>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// An emitted workflow operation with metadata
|
|
26
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
27
|
+
pub struct WorkflowOperationEntry {
|
|
28
|
+
/// The operation that occurred
|
|
29
|
+
pub operation: WorkflowOperation,
|
|
30
|
+
/// Timestamp in ms
|
|
31
|
+
pub timestamp_ms: u128,
|
|
32
|
+
/// Instance ID that emitted this operation
|
|
33
|
+
pub instance_id: String,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// State transition to process reactively
|
|
37
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
38
|
+
struct WorkflowStateTransition {
|
|
39
|
+
/// Instance ID
|
|
40
|
+
pub instance_id: String,
|
|
41
|
+
/// Step ID that completed or changed
|
|
42
|
+
pub step_id: String,
|
|
43
|
+
/// New state
|
|
44
|
+
pub new_state: WorkflowStepState,
|
|
45
|
+
/// Optional result or error
|
|
46
|
+
pub data: Option<TransitionData>,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
50
|
+
enum TransitionData {
|
|
51
|
+
Result(FlowRef),
|
|
52
|
+
Error(String),
|
|
53
|
+
Claim(String),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl WorkflowRuntime {
|
|
57
|
+
/// Create a new workflow runtime
|
|
58
|
+
pub fn new() -> Self {
|
|
59
|
+
Self {
|
|
60
|
+
instances: HashMap::new(),
|
|
61
|
+
graphs: HashMap::new(),
|
|
62
|
+
pending_transitions: VecDeque::new(),
|
|
63
|
+
operations_log: Vec::new(),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Register a workflow definition
|
|
68
|
+
pub fn register_workflow(&mut self, graph: WorkflowGraph) -> Result<(), String> {
|
|
69
|
+
graph.validate()?;
|
|
70
|
+
self.graphs.insert(graph.workflow_ref.canonical(), graph);
|
|
71
|
+
Ok(())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Create a new workflow instance
|
|
75
|
+
pub fn create_instance(
|
|
76
|
+
&mut self,
|
|
77
|
+
workflow_ref: WorkflowRef,
|
|
78
|
+
instance_id: impl Into<String>,
|
|
79
|
+
_inputs: Vec<FlowRef>,
|
|
80
|
+
) -> Result<String, String> {
|
|
81
|
+
let instance_id = instance_id.into();
|
|
82
|
+
let graph = self
|
|
83
|
+
.graphs
|
|
84
|
+
.get(&workflow_ref.canonical())
|
|
85
|
+
.ok_or_else(|| format!("Workflow not found: {}", workflow_ref))?
|
|
86
|
+
.clone();
|
|
87
|
+
|
|
88
|
+
let mut instance = WorkflowInstance::new(workflow_ref.clone(), instance_id.clone());
|
|
89
|
+
|
|
90
|
+
// Initialize all steps to Pending
|
|
91
|
+
for node_id in graph.nodes.keys() {
|
|
92
|
+
instance.set_step_state(node_id, WorkflowStepState::Pending);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Emit WorkflowCreated operation
|
|
96
|
+
self.emit_operation(
|
|
97
|
+
instance_id.clone(),
|
|
98
|
+
WorkflowOperation::Created {
|
|
99
|
+
workflow_ref: workflow_ref.clone(),
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Store instance
|
|
104
|
+
self.instances.insert(instance_id.clone(), instance);
|
|
105
|
+
|
|
106
|
+
// Make initial transitions - start with entry nodes
|
|
107
|
+
self.advance_workflow(&instance_id, &graph)?;
|
|
108
|
+
|
|
109
|
+
Ok(instance_id)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Start a workflow instance
|
|
113
|
+
pub fn start_instance(&mut self, instance_id: &str) -> Result<(), String> {
|
|
114
|
+
let instance = self
|
|
115
|
+
.instances
|
|
116
|
+
.get_mut(instance_id)
|
|
117
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
118
|
+
|
|
119
|
+
if instance.state != WorkflowState::Created {
|
|
120
|
+
return Err(format!(
|
|
121
|
+
"Cannot start instance in state {:?}",
|
|
122
|
+
instance.state
|
|
123
|
+
));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let workflow_ref = instance.workflow_ref.clone();
|
|
127
|
+
instance.state = WorkflowState::Running;
|
|
128
|
+
instance.started_ms = Some(crate::now_ms());
|
|
129
|
+
|
|
130
|
+
self.emit_operation(
|
|
131
|
+
instance_id.to_string(),
|
|
132
|
+
WorkflowOperation::Started { workflow_ref },
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
Ok(())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Claim a step for execution (single-owner execution)
|
|
139
|
+
pub fn claim_step(
|
|
140
|
+
&mut self,
|
|
141
|
+
instance_id: &str,
|
|
142
|
+
step_id: &str,
|
|
143
|
+
peer_id: impl Into<String>,
|
|
144
|
+
) -> Result<(), String> {
|
|
145
|
+
let peer_id = peer_id.into();
|
|
146
|
+
let instance = self
|
|
147
|
+
.instances
|
|
148
|
+
.get_mut(instance_id)
|
|
149
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
150
|
+
|
|
151
|
+
instance.claim_step(step_id, peer_id.clone())?;
|
|
152
|
+
|
|
153
|
+
let step_ref = WorkflowStepRef::new(instance.workflow_ref.clone(), step_id);
|
|
154
|
+
self.emit_operation(
|
|
155
|
+
instance_id.to_string(),
|
|
156
|
+
WorkflowOperation::StepClaimed {
|
|
157
|
+
step_ref,
|
|
158
|
+
claimed_by: peer_id,
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
Ok(())
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Mark a step as executing
|
|
166
|
+
pub fn step_executing(&mut self, instance_id: &str, step_id: &str) -> Result<(), String> {
|
|
167
|
+
let instance = self
|
|
168
|
+
.instances
|
|
169
|
+
.get_mut(instance_id)
|
|
170
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
171
|
+
|
|
172
|
+
let claimed_by = instance
|
|
173
|
+
.step_claims
|
|
174
|
+
.get(step_id)
|
|
175
|
+
.cloned()
|
|
176
|
+
.ok_or_else(|| format!("Step not claimed: {}", step_id))?;
|
|
177
|
+
|
|
178
|
+
instance.set_step_state(step_id, WorkflowStepState::Running);
|
|
179
|
+
|
|
180
|
+
let step_ref = WorkflowStepRef::new(instance.workflow_ref.clone(), step_id);
|
|
181
|
+
self.emit_operation(
|
|
182
|
+
instance_id.to_string(),
|
|
183
|
+
WorkflowOperation::StepExecuting {
|
|
184
|
+
step_ref,
|
|
185
|
+
claimed_by,
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
Ok(())
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Mark a step as completed with a result
|
|
193
|
+
pub fn step_completed(
|
|
194
|
+
&mut self,
|
|
195
|
+
instance_id: &str,
|
|
196
|
+
step_id: &str,
|
|
197
|
+
result_ref: FlowRef,
|
|
198
|
+
) -> Result<(), String> {
|
|
199
|
+
// Collect data before releasing borrows
|
|
200
|
+
let (workflow_ref, graph_canonical, step_ref) = {
|
|
201
|
+
let instance = self
|
|
202
|
+
.instances
|
|
203
|
+
.get_mut(instance_id)
|
|
204
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
205
|
+
|
|
206
|
+
instance.complete_step(step_id, result_ref.clone())?;
|
|
207
|
+
|
|
208
|
+
let step_ref = WorkflowStepRef::new(instance.workflow_ref.clone(), step_id);
|
|
209
|
+
(
|
|
210
|
+
instance.workflow_ref.clone(),
|
|
211
|
+
instance.workflow_ref.canonical(),
|
|
212
|
+
step_ref,
|
|
213
|
+
)
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Now emit after releasing the mutable borrow on instance
|
|
217
|
+
self.emit_operation(
|
|
218
|
+
instance_id.to_string(),
|
|
219
|
+
WorkflowOperation::StepCompleted {
|
|
220
|
+
step_ref,
|
|
221
|
+
result_ref: Some(result_ref),
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Check if this advancement triggers other steps
|
|
226
|
+
let graph = self
|
|
227
|
+
.graphs
|
|
228
|
+
.get(&graph_canonical)
|
|
229
|
+
.ok_or_else(|| format!("Workflow not found: {}", workflow_ref))?
|
|
230
|
+
.clone();
|
|
231
|
+
|
|
232
|
+
self.advance_workflow(instance_id, &graph)?;
|
|
233
|
+
|
|
234
|
+
Ok(())
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Mark a step as failed
|
|
238
|
+
pub fn step_failed(
|
|
239
|
+
&mut self,
|
|
240
|
+
instance_id: &str,
|
|
241
|
+
step_id: &str,
|
|
242
|
+
error: String,
|
|
243
|
+
) -> Result<(), String> {
|
|
244
|
+
let error_msg = {
|
|
245
|
+
let instance = self
|
|
246
|
+
.instances
|
|
247
|
+
.get_mut(instance_id)
|
|
248
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
249
|
+
|
|
250
|
+
let attempt = instance
|
|
251
|
+
.step_failures
|
|
252
|
+
.get(step_id)
|
|
253
|
+
.map(|errs| errs.len() as u32 + 1)
|
|
254
|
+
.unwrap_or(1);
|
|
255
|
+
|
|
256
|
+
instance.fail_step(step_id, error)?;
|
|
257
|
+
|
|
258
|
+
let error_str = instance
|
|
259
|
+
.step_failures
|
|
260
|
+
.get(step_id)
|
|
261
|
+
.map(|e| e.last())
|
|
262
|
+
.flatten()
|
|
263
|
+
.unwrap_or(&"unknown".to_string())
|
|
264
|
+
.clone();
|
|
265
|
+
format!("Attempt {}: {}", attempt, error_str)
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
let step_ref = WorkflowStepRef::new(
|
|
269
|
+
self.instances
|
|
270
|
+
.get(instance_id)
|
|
271
|
+
.unwrap()
|
|
272
|
+
.workflow_ref
|
|
273
|
+
.clone(),
|
|
274
|
+
step_id,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
let attempt = self
|
|
278
|
+
.instances
|
|
279
|
+
.get(instance_id)
|
|
280
|
+
.map(|i| {
|
|
281
|
+
i.step_failures
|
|
282
|
+
.get(step_id)
|
|
283
|
+
.map(|e| e.len() as u32)
|
|
284
|
+
.unwrap_or(1)
|
|
285
|
+
})
|
|
286
|
+
.unwrap_or(1);
|
|
287
|
+
|
|
288
|
+
self.emit_operation(
|
|
289
|
+
instance_id.to_string(),
|
|
290
|
+
WorkflowOperation::StepFailed {
|
|
291
|
+
step_ref,
|
|
292
|
+
error: error_msg,
|
|
293
|
+
attempt,
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
Ok(())
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Mark a step as blocked
|
|
301
|
+
pub fn step_blocked(
|
|
302
|
+
&mut self,
|
|
303
|
+
instance_id: &str,
|
|
304
|
+
step_id: &str,
|
|
305
|
+
reason: BlockedReason,
|
|
306
|
+
) -> Result<(), String> {
|
|
307
|
+
let instance = self
|
|
308
|
+
.instances
|
|
309
|
+
.get_mut(instance_id)
|
|
310
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
311
|
+
|
|
312
|
+
instance.set_step_state(step_id, WorkflowStepState::Blocked);
|
|
313
|
+
instance
|
|
314
|
+
.step_blocked_reasons
|
|
315
|
+
.insert(step_id.to_string(), reason.clone());
|
|
316
|
+
|
|
317
|
+
let step_ref = WorkflowStepRef::new(instance.workflow_ref.clone(), step_id);
|
|
318
|
+
self.emit_operation(
|
|
319
|
+
instance_id.to_string(),
|
|
320
|
+
WorkflowOperation::StepBlocked { step_ref, reason },
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
Ok(())
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/// Unblock a step and continue workflow
|
|
327
|
+
pub fn unblock_step(&mut self, instance_id: &str, step_id: &str) -> Result<(), String> {
|
|
328
|
+
let instance = self
|
|
329
|
+
.instances
|
|
330
|
+
.get_mut(instance_id)
|
|
331
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
332
|
+
|
|
333
|
+
if instance.get_step_state(step_id) != Some(WorkflowStepState::Blocked) {
|
|
334
|
+
return Err(format!("Step not blocked: {}", step_id));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
instance.set_step_state(step_id, WorkflowStepState::Ready);
|
|
338
|
+
instance.step_blocked_reasons.remove(step_id);
|
|
339
|
+
|
|
340
|
+
// Check if this advancement triggers other steps
|
|
341
|
+
let graph = self
|
|
342
|
+
.graphs
|
|
343
|
+
.get(&instance.workflow_ref.canonical())
|
|
344
|
+
.ok_or_else(|| format!("Workflow not found: {}", instance.workflow_ref))?
|
|
345
|
+
.clone();
|
|
346
|
+
|
|
347
|
+
self.advance_workflow(instance_id, &graph)?;
|
|
348
|
+
|
|
349
|
+
Ok(())
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/// Advance workflow reactively - make ready steps available for execution
|
|
353
|
+
fn advance_workflow(&mut self, instance_id: &str, graph: &WorkflowGraph) -> Result<(), String> {
|
|
354
|
+
// Collect operations to emit to avoid borrow checker issues
|
|
355
|
+
let mut ops_to_emit = Vec::new();
|
|
356
|
+
|
|
357
|
+
{
|
|
358
|
+
let instance = self
|
|
359
|
+
.instances
|
|
360
|
+
.get_mut(instance_id)
|
|
361
|
+
.ok_or_else(|| format!("Instance not found: {}", instance_id))?;
|
|
362
|
+
|
|
363
|
+
// Find steps that can transition to Ready
|
|
364
|
+
for (step_id, node) in &graph.nodes {
|
|
365
|
+
let current_state = instance.get_step_state(step_id);
|
|
366
|
+
|
|
367
|
+
// Only transition from Pending to Ready
|
|
368
|
+
if current_state != Some(WorkflowStepState::Pending) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check if all dependencies are completed
|
|
373
|
+
let all_deps_satisfied = node.inputs.iter().all(|dep| {
|
|
374
|
+
if dep.starts_with("input:") {
|
|
375
|
+
// Input dependencies are always satisfied
|
|
376
|
+
true
|
|
377
|
+
} else {
|
|
378
|
+
// Check if dependency step is completed
|
|
379
|
+
instance.get_step_state(dep) == Some(WorkflowStepState::Completed)
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if all_deps_satisfied {
|
|
384
|
+
instance.set_step_state(step_id, WorkflowStepState::Ready);
|
|
385
|
+
|
|
386
|
+
let step_ref = WorkflowStepRef::new(instance.workflow_ref.clone(), step_id);
|
|
387
|
+
ops_to_emit.push(WorkflowOperation::StepReady { step_ref });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check if workflow is complete
|
|
392
|
+
let all_completed = graph.nodes.keys().all(|step_id| {
|
|
393
|
+
instance.get_step_state(step_id) == Some(WorkflowStepState::Completed)
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if all_completed {
|
|
397
|
+
instance.state = WorkflowState::Completed;
|
|
398
|
+
instance.completed_ms = Some(crate::now_ms());
|
|
399
|
+
|
|
400
|
+
ops_to_emit.push(WorkflowOperation::Completed {
|
|
401
|
+
workflow_ref: instance.workflow_ref.clone(),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Now emit the operations after the borrow is released
|
|
407
|
+
for op in ops_to_emit {
|
|
408
|
+
self.emit_operation(instance_id.to_string(), op);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
Ok(())
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// Get a workflow instance
|
|
415
|
+
pub fn get_instance(&self, instance_id: &str) -> Option<WorkflowInstance> {
|
|
416
|
+
self.instances.get(instance_id).cloned()
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/// Get workflow graph
|
|
420
|
+
pub fn get_graph(&self, workflow_ref: &WorkflowRef) -> Option<WorkflowGraph> {
|
|
421
|
+
self.graphs.get(&workflow_ref.canonical()).cloned()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/// Get all operations logged so far
|
|
425
|
+
pub fn operations_log(&self) -> &[WorkflowOperationEntry] {
|
|
426
|
+
&self.operations_log
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Get ready steps for a workflow instance
|
|
430
|
+
pub fn ready_steps(&self, instance_id: &str) -> Vec<String> {
|
|
431
|
+
self.instances
|
|
432
|
+
.get(instance_id)
|
|
433
|
+
.map(|instance| {
|
|
434
|
+
instance
|
|
435
|
+
.step_states
|
|
436
|
+
.iter()
|
|
437
|
+
.filter(|(_, state)| *state == &WorkflowStepState::Ready)
|
|
438
|
+
.map(|(step_id, _)| step_id.clone())
|
|
439
|
+
.collect()
|
|
440
|
+
})
|
|
441
|
+
.unwrap_or_default()
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/// Emit a workflow operation
|
|
445
|
+
fn emit_operation(&mut self, instance_id: String, operation: WorkflowOperation) {
|
|
446
|
+
self.operations_log.push(WorkflowOperationEntry {
|
|
447
|
+
operation,
|
|
448
|
+
timestamp_ms: crate::now_ms(),
|
|
449
|
+
instance_id,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
impl Default for WorkflowRuntime {
|
|
455
|
+
fn default() -> Self {
|
|
456
|
+
Self::new()
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
#[cfg(test)]
|
|
461
|
+
mod tests {
|
|
462
|
+
use super::*;
|
|
463
|
+
use crate::{WorkflowGraph, WorkflowNode};
|
|
464
|
+
|
|
465
|
+
fn create_test_workflow() -> WorkflowGraph {
|
|
466
|
+
let wf = WorkflowRef::new("workflow", "test", 1);
|
|
467
|
+
WorkflowGraph::new(wf)
|
|
468
|
+
.add_node(WorkflowNode::new("step-1", "capability-a"))
|
|
469
|
+
.add_node(WorkflowNode::new("step-2", "capability-b").with_input("step-1"))
|
|
470
|
+
.add_edge("step-1", "step-2")
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
#[test]
|
|
474
|
+
fn test_workflow_runtime_creation() {
|
|
475
|
+
let mut runtime = WorkflowRuntime::new();
|
|
476
|
+
let graph = create_test_workflow();
|
|
477
|
+
assert!(runtime.register_workflow(graph).is_ok());
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#[test]
|
|
481
|
+
fn test_workflow_instance_creation() {
|
|
482
|
+
let mut runtime = WorkflowRuntime::new();
|
|
483
|
+
let graph = create_test_workflow();
|
|
484
|
+
let wf = graph.workflow_ref.clone();
|
|
485
|
+
runtime.register_workflow(graph).unwrap();
|
|
486
|
+
|
|
487
|
+
let instance_id = runtime.create_instance(wf, "inst-1", Vec::new()).unwrap();
|
|
488
|
+
|
|
489
|
+
let instance = runtime.get_instance(&instance_id).unwrap();
|
|
490
|
+
assert_eq!(instance.state, WorkflowState::Created);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#[test]
|
|
494
|
+
fn test_workflow_reactive_advancement() {
|
|
495
|
+
let mut runtime = WorkflowRuntime::new();
|
|
496
|
+
let graph = create_test_workflow();
|
|
497
|
+
let wf = graph.workflow_ref.clone();
|
|
498
|
+
runtime.register_workflow(graph).unwrap();
|
|
499
|
+
|
|
500
|
+
let instance_id = runtime.create_instance(wf, "inst-1", Vec::new()).unwrap();
|
|
501
|
+
|
|
502
|
+
runtime.start_instance(&instance_id).unwrap();
|
|
503
|
+
|
|
504
|
+
// Initially, step-1 should be ready (no dependencies)
|
|
505
|
+
let ready = runtime.ready_steps(&instance_id);
|
|
506
|
+
assert!(ready.contains(&"step-1".to_string()));
|
|
507
|
+
assert!(!ready.contains(&"step-2".to_string()));
|
|
508
|
+
|
|
509
|
+
// Claim and complete step-1
|
|
510
|
+
runtime
|
|
511
|
+
.claim_step(&instance_id, "step-1", "peer-a")
|
|
512
|
+
.unwrap();
|
|
513
|
+
runtime
|
|
514
|
+
.step_completed(&instance_id, "step-1", FlowRef::new("results", "result-1"))
|
|
515
|
+
.unwrap();
|
|
516
|
+
|
|
517
|
+
// Now step-2 should be ready
|
|
518
|
+
let ready = runtime.ready_steps(&instance_id);
|
|
519
|
+
assert!(ready.contains(&"step-2".to_string()));
|
|
520
|
+
|
|
521
|
+
// Check operations log
|
|
522
|
+
let ops = runtime.operations_log();
|
|
523
|
+
assert!(ops
|
|
524
|
+
.iter()
|
|
525
|
+
.any(|op| matches!(op.operation, WorkflowOperation::StepReady { .. })));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#[test]
|
|
529
|
+
fn test_workflow_completion() {
|
|
530
|
+
let mut runtime = WorkflowRuntime::new();
|
|
531
|
+
let graph = create_test_workflow();
|
|
532
|
+
let wf = graph.workflow_ref.clone();
|
|
533
|
+
runtime.register_workflow(graph).unwrap();
|
|
534
|
+
|
|
535
|
+
let instance_id = runtime.create_instance(wf, "inst-1", Vec::new()).unwrap();
|
|
536
|
+
|
|
537
|
+
runtime.start_instance(&instance_id).unwrap();
|
|
538
|
+
|
|
539
|
+
// Complete step-1
|
|
540
|
+
runtime
|
|
541
|
+
.claim_step(&instance_id, "step-1", "peer-a")
|
|
542
|
+
.unwrap();
|
|
543
|
+
runtime
|
|
544
|
+
.step_completed(&instance_id, "step-1", FlowRef::new("results", "result-1"))
|
|
545
|
+
.unwrap();
|
|
546
|
+
|
|
547
|
+
// Complete step-2
|
|
548
|
+
runtime
|
|
549
|
+
.claim_step(&instance_id, "step-2", "peer-b")
|
|
550
|
+
.unwrap();
|
|
551
|
+
runtime
|
|
552
|
+
.step_completed(&instance_id, "step-2", FlowRef::new("results", "result-2"))
|
|
553
|
+
.unwrap();
|
|
554
|
+
|
|
555
|
+
// Workflow should be completed
|
|
556
|
+
let instance = runtime.get_instance(&instance_id).unwrap();
|
|
557
|
+
assert_eq!(instance.state, WorkflowState::Completed);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
#[test]
|
|
561
|
+
fn test_step_blocking() {
|
|
562
|
+
let mut runtime = WorkflowRuntime::new();
|
|
563
|
+
let graph = create_test_workflow();
|
|
564
|
+
let wf = graph.workflow_ref.clone();
|
|
565
|
+
runtime.register_workflow(graph).unwrap();
|
|
566
|
+
|
|
567
|
+
let instance_id = runtime.create_instance(wf, "inst-1", Vec::new()).unwrap();
|
|
568
|
+
|
|
569
|
+
runtime.start_instance(&instance_id).unwrap();
|
|
570
|
+
|
|
571
|
+
// Block step-1
|
|
572
|
+
runtime
|
|
573
|
+
.step_blocked(
|
|
574
|
+
&instance_id,
|
|
575
|
+
"step-1",
|
|
576
|
+
BlockedReason::AwaitingApproval("Needs manager approval".to_string()),
|
|
577
|
+
)
|
|
578
|
+
.unwrap();
|
|
579
|
+
|
|
580
|
+
let instance = runtime.get_instance(&instance_id).unwrap();
|
|
581
|
+
assert_eq!(
|
|
582
|
+
instance.get_step_state("step-1"),
|
|
583
|
+
Some(WorkflowStepState::Blocked)
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
// Unblock step-1
|
|
587
|
+
runtime.unblock_step(&instance_id, "step-1").unwrap();
|
|
588
|
+
let instance = runtime.get_instance(&instance_id).unwrap();
|
|
589
|
+
assert_eq!(
|
|
590
|
+
instance.get_step_state("step-1"),
|
|
591
|
+
Some(WorkflowStepState::Ready)
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|