@feltdb/core 0.4.12 → 0.4.14

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 (165) hide show
  1. package/dist/cli/commands.js +42 -25
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +39 -4
  4. package/dist/create/create.js +30 -18
  5. package/dist/create/docker-compose-generator.js +68 -11
  6. package/dist/create/package-versions.js +1 -1
  7. package/dist/create/server-source/Cargo.lock +2238 -0
  8. package/dist/create/server-source/Cargo.toml +7 -0
  9. package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
  10. package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
  11. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
  12. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
  13. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
  14. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
  15. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
  19. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
  20. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
  21. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
  22. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
  23. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
  24. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
  25. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
  26. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
  27. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
  28. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
  29. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
  30. package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
  31. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
  32. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
  33. package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
  34. package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
  35. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
  36. package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
  37. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
  38. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
  39. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
  40. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
  41. package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
  42. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
  43. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
  44. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
  45. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
  46. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
  47. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
  48. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
  49. package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
  50. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
  51. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
  52. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
  53. package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
  54. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
  55. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
  56. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
  57. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
  58. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
  59. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
  60. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
  61. package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
  62. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
  63. package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
  64. package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
  65. package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
  66. package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
  67. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
  68. package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
  69. package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
  70. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
  71. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
  72. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
  73. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
  74. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
  75. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
  76. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
  77. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
  78. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
  79. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
  80. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
  81. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
  82. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
  83. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
  84. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
  85. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
  86. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
  87. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
  88. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
  89. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
  90. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
  91. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
  92. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
  93. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
  94. package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
  95. package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
  96. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
  97. package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
  98. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
  99. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
  100. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
  101. package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
  102. package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
  103. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
  104. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
  105. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
  106. package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
  107. package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
  108. package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
  109. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
  110. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
  111. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
  112. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
  113. package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
  114. package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
  115. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
  116. package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
  117. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
  118. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
  119. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
  120. package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
  121. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
  122. package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
  123. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
  124. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
  125. package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
  126. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
  127. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
  128. package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
  129. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
  130. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
  131. package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
  132. package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
  133. package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
  134. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
  135. package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
  136. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
  137. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
  139. package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
  140. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
  141. package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
  142. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
  143. package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
  144. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
  145. package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
  146. package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
  147. package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
  149. package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
  150. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
  151. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
  152. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  153. package/dist/flowspec.d.ts +2 -0
  154. package/dist/flowspec.d.ts.map +1 -1
  155. package/dist/flowspec.js +28 -4
  156. package/dist/state-contract.d.ts +7 -1
  157. package/dist/state-contract.d.ts.map +1 -1
  158. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  159. package/dist/studio/components/ConflictExplorer.d.ts.map +1 -1
  160. package/dist/studio/components/index.js +1 -1
  161. package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
  162. package/dist/studio/index.js +31 -28
  163. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
  164. package/dist/studio-app/index.html +1 -1
  165. package/package.json +1 -1
@@ -0,0 +1,266 @@
1
+ /// Standalone FeltDB node for OS process boundary testing
2
+ ///
3
+ /// Simplified runtime that:
4
+ /// 1. Accepts command via stdin (execute-tx, query-state, shutdown)
5
+ /// 2. Outputs state to stdout in JSON format
6
+ /// 3. Runs replication over TCP in background
7
+ /// 4. Executes local transactions and replicates to peers
8
+ /// 5. Receives and applies replicated transactions from peers
9
+
10
+ use feltdb::convergence::VectorClock;
11
+ use feltdb::distributed_transactions::{DistributedTransactionExecutor, ReplicationMessage};
12
+ use feltdb::replication_protocol::ProtocolTransport;
13
+ use feltdb::state_hash::StateHash;
14
+ use feltdb::tcp_transport::TcpTransport;
15
+ use feltdb::transactions::{
16
+ ConsistencyContract, Operation, OperationCommand, OperationId, StateVersion,
17
+ };
18
+ use serde_json::json;
19
+ use std::collections::HashMap;
20
+ use std::io::{self, BufRead, Write};
21
+ use std::sync::atomic::{AtomicBool, Ordering};
22
+ use std::sync::Arc;
23
+ use tokio::sync::{mpsc, Mutex};
24
+ use tokio::time::{sleep, Duration};
25
+
26
+ #[derive(Clone)]
27
+ struct NodeState {
28
+ executor: Arc<Mutex<DistributedTransactionExecutor>>,
29
+ node_id: String,
30
+ transport: Arc<Mutex<TcpTransport>>,
31
+ tx_sequence: Arc<Mutex<u64>>,
32
+ is_server: bool,
33
+ should_exit: Arc<AtomicBool>,
34
+ }
35
+
36
+ #[tokio::main]
37
+ async fn main() -> Result<(), Box<dyn std::error::Error>> {
38
+ let args: Vec<String> = std::env::args().collect();
39
+
40
+ // Parse arguments
41
+ let mut mode = "server";
42
+ let mut replication_port = 9001u16;
43
+ let mut peer_addr = String::new();
44
+
45
+ let mut i = 1;
46
+ while i < args.len() {
47
+ match args[i].as_str() {
48
+ "--mode" => {
49
+ i += 1;
50
+ mode = &args[i];
51
+ }
52
+ "--port" => {
53
+ i += 1;
54
+ replication_port = args[i].parse()?;
55
+ }
56
+ "--peer" => {
57
+ i += 1;
58
+ peer_addr = args[i].clone();
59
+ }
60
+ _ => {}
61
+ }
62
+ i += 1;
63
+ }
64
+
65
+ let is_server = mode == "server";
66
+ let node_id = if is_server {
67
+ "node_a".to_string()
68
+ } else {
69
+ "node_b".to_string()
70
+ };
71
+
72
+ // Initialize executor
73
+ let initial_state = StateHash::from_hex("hash_initial".to_string());
74
+ let mut executor =
75
+ DistributedTransactionExecutor::new(node_id.clone(), initial_state.clone());
76
+ executor.register_replica("node_a".to_string(), initial_state.clone());
77
+ executor.register_replica("node_b".to_string(), initial_state.clone());
78
+
79
+ let transport = if is_server {
80
+ TcpTransport::new(format!("127.0.0.1:{}", replication_port))
81
+ } else {
82
+ TcpTransport::new("127.0.0.1:0".to_string())
83
+ };
84
+
85
+ let node_state = NodeState {
86
+ executor: Arc::new(Mutex::new(executor)),
87
+ node_id: node_id.clone(),
88
+ transport: Arc::new(Mutex::new(transport)),
89
+ tx_sequence: Arc::new(Mutex::new(1)),
90
+ is_server,
91
+ should_exit: Arc::new(AtomicBool::new(false)),
92
+ };
93
+
94
+ // Start TCP replication in background
95
+ if is_server {
96
+ let replication_addr = format!("127.0.0.1:{}", replication_port);
97
+ let ns = node_state.clone();
98
+ tokio::spawn(async move {
99
+ let mut transport = ns.transport.lock().await;
100
+ if let Err(e) = transport.listen().await {
101
+ eprintln!("ERROR: listen failed: {}", e);
102
+ }
103
+ });
104
+ println!("STARTED server on port {}", replication_port);
105
+ let _ = std::io::stdout().flush();
106
+ } else {
107
+ let ns = node_state.clone();
108
+ let peer = peer_addr.clone();
109
+ tokio::spawn(async move {
110
+ let mut transport = ns.transport.lock().await;
111
+ if let Err(e) = transport.connect(&peer).await {
112
+ eprintln!("ERROR: connect failed: {}", e);
113
+ }
114
+ });
115
+ println!("STARTED client connecting to {}", peer_addr);
116
+ let _ = std::io::stdout().flush();
117
+ }
118
+
119
+ // Spawn background task to receive and apply replicated messages
120
+ let ns = node_state.clone();
121
+ tokio::spawn(async move {
122
+ loop {
123
+ if ns.should_exit.load(Ordering::Relaxed) {
124
+ break;
125
+ }
126
+
127
+ let messages = {
128
+ let transport = ns.transport.lock().await;
129
+ match transport.receive().await {
130
+ Ok(msgs) => msgs,
131
+ Err(_) => {
132
+ sleep(Duration::from_millis(100)).await;
133
+ continue;
134
+ }
135
+ }
136
+ };
137
+
138
+ for msg in messages {
139
+ let mut executor = ns.executor.lock().await;
140
+ let mut parent_vc = VectorClock::new();
141
+ parent_vc.increment(&ns.node_id);
142
+ let parent_version = StateVersion::new(parent_vc, "hash_initial".to_string());
143
+
144
+ if let Err(e) = executor.receive_replicated_transaction(msg, parent_version) {
145
+ eprintln!("ERROR: apply replication failed: {}", e);
146
+ }
147
+ }
148
+
149
+ sleep(Duration::from_millis(50)).await;
150
+ }
151
+ });
152
+
153
+ // Give replication setup time to establish
154
+ sleep(Duration::from_millis(500)).await;
155
+
156
+ // Create a channel for stdin commands
157
+ let (tx, mut rx) = mpsc::channel::<String>(100);
158
+
159
+ // Spawn a blocking task to read stdin
160
+ tokio::task::spawn_blocking(move || {
161
+ let stdin = io::stdin();
162
+ let handle = stdin.lock();
163
+
164
+ for line in handle.lines() {
165
+ if let Ok(cmd) = line {
166
+ let cmd_str = cmd.trim().to_string();
167
+ if !cmd_str.is_empty() {
168
+ // Non-blocking send, ignore if receiver is gone
169
+ let _ = tx.blocking_send(cmd_str);
170
+ }
171
+ }
172
+ }
173
+ });
174
+
175
+ // Process commands from the channel
176
+ while let Some(cmd) = rx.recv().await {
177
+ match cmd.trim() {
178
+ "execute-tx" => {
179
+ let seq = {
180
+ let mut s = node_state.tx_sequence.lock().await;
181
+ let current = *s;
182
+ *s += 1;
183
+ current
184
+ };
185
+
186
+ // Create a test operation
187
+ let mut parent_vc = VectorClock::new();
188
+ parent_vc.increment(&node_state.node_id);
189
+ let parent_version = StateVersion::new(parent_vc, "hash_initial".to_string());
190
+
191
+ let mut fields = HashMap::new();
192
+ fields.insert("key".to_string(), json!("value"));
193
+
194
+ let op = Operation::new(
195
+ OperationId::new(node_state.node_id.clone(), seq),
196
+ parent_version.clone(),
197
+ format!("tx_{}", seq),
198
+ OperationCommand {
199
+ op_type: "set".to_string(),
200
+ collection: "items".to_string(),
201
+ record_id: format!("item{}", seq),
202
+ fields,
203
+ },
204
+ node_state.node_id.clone(),
205
+ );
206
+
207
+ {
208
+ let mut executor = node_state.executor.lock().await;
209
+ match executor.execute_local_transaction(
210
+ seq,
211
+ format!("tx_{}", seq),
212
+ parent_version,
213
+ vec![op],
214
+ ConsistencyContract::local(),
215
+ ) {
216
+ Ok(envelope) => {
217
+ let replication_msg = ReplicationMessage::new(
218
+ envelope,
219
+ node_state.node_id.clone(),
220
+ if node_state.node_id == "node_a" {
221
+ "node_b".to_string()
222
+ } else {
223
+ "node_a".to_string()
224
+ },
225
+ seq,
226
+ );
227
+
228
+ let transport = node_state.transport.lock().await;
229
+ if let Err(e) = transport.send(replication_msg).await {
230
+ println!("ERROR: send replication failed: {}", e);
231
+ } else {
232
+ println!("TX_EXECUTED");
233
+ }
234
+ }
235
+ Err(e) => {
236
+ println!("ERROR: execute transaction failed: {}", e);
237
+ }
238
+ }
239
+ }
240
+ let _ = std::io::stdout().flush();
241
+ }
242
+ "query-state" => {
243
+ let executor = node_state.executor.lock().await;
244
+ if let Some(replica) = executor.get_replica_state(&node_state.node_id) {
245
+ println!("STATE:{},{}", replica.state_hash, replica.operations_applied);
246
+ } else {
247
+ println!("ERROR: node not found");
248
+ }
249
+ let _ = std::io::stdout().flush();
250
+ }
251
+ "shutdown" => {
252
+ node_state.should_exit.store(true, Ordering::Relaxed);
253
+ println!("SHUTDOWN");
254
+ let _ = std::io::stdout().flush();
255
+ sleep(Duration::from_millis(100)).await;
256
+ std::process::exit(0);
257
+ }
258
+ _ => {
259
+ println!("ERROR: unknown command");
260
+ let _ = std::io::stdout().flush();
261
+ }
262
+ }
263
+ }
264
+
265
+ Ok(())
266
+ }
@@ -0,0 +1,8 @@
1
+ /// Concrete capability implementations
2
+ pub mod search;
3
+ pub mod vector;
4
+
5
+ pub use search::{SearchCapability, SearchDocument, SearchResult};
6
+ pub use vector::{
7
+ DemoEmbeddingProvider, EmbeddingProvider, VectorCapability, VectorDocument, VectorSearchResult,
8
+ };
@@ -0,0 +1,418 @@
1
+ /// Full-text Search Capability
2
+ ///
3
+ /// A concrete implementation of FlowCapability that maintains a full-text search index.
4
+ /// This demonstrates the capability extension model with a projection that doesn't own
5
+ /// canonical state but derives from it.
6
+ use crate::capability::{
7
+ CapabilityAccessModel, CapabilityContext, CapabilityId, CapabilityMetadata,
8
+ CapabilityOperationResult, CapabilityQuery, CapabilityQueryResult, CapabilityRole,
9
+ FlowCapability,
10
+ };
11
+ use crate::{Operation, OperationType, Result};
12
+ use serde::{Deserialize, Serialize};
13
+ use serde_json::{json, Value};
14
+ use std::collections::HashMap;
15
+
16
+ /// Search index document representation
17
+ #[derive(Debug, Clone, Serialize, Deserialize)]
18
+ pub struct SearchDocument {
19
+ pub key: String,
20
+ pub content: String,
21
+ pub metadata: Value,
22
+ }
23
+
24
+ /// Search result from the search capability
25
+ #[derive(Debug, Clone, Serialize, Deserialize)]
26
+ pub struct SearchResult {
27
+ pub key: String,
28
+ pub content: String,
29
+ pub score: f32,
30
+ pub metadata: Value,
31
+ }
32
+
33
+ /// Full-text search capability implementation
34
+ pub struct SearchCapability {
35
+ capability_id: CapabilityId,
36
+ metadata: CapabilityMetadata,
37
+ /// In-memory document store
38
+ documents: HashMap<String, SearchDocument>,
39
+ }
40
+
41
+ impl SearchCapability {
42
+ pub fn new() -> Self {
43
+ let capability_id = CapabilityId::new("search".to_string());
44
+ let access_model = CapabilityAccessModel::new(
45
+ vec!["*".to_string()],
46
+ vec!["search.index".to_string()],
47
+ vec![],
48
+ );
49
+
50
+ let metadata = CapabilityMetadata::new(
51
+ capability_id.clone(),
52
+ "Full-Text Search".to_string(),
53
+ "Maintains a full-text search index over documents".to_string(),
54
+ CapabilityRole::Projection,
55
+ access_model,
56
+ );
57
+
58
+ Self {
59
+ capability_id,
60
+ metadata,
61
+ documents: HashMap::new(),
62
+ }
63
+ }
64
+
65
+ /// Add or update a document in the search index
66
+ fn index_document(&mut self, doc: &SearchDocument) -> Result<()> {
67
+ self.documents.insert(doc.key.clone(), doc.clone());
68
+ Ok(())
69
+ }
70
+
71
+ /// Remove a document from the search index
72
+ fn remove_document(&mut self, key: &str) -> Result<()> {
73
+ self.documents.remove(key);
74
+ Ok(())
75
+ }
76
+
77
+ /// Search the index - simple term matching
78
+ fn search(&self, query_str: &str, limit: usize) -> Result<Vec<SearchResult>> {
79
+ // Simple tokenization
80
+ let query_terms: Vec<&str> = query_str
81
+ .split_whitespace()
82
+ .filter(|s| !s.is_empty())
83
+ .collect();
84
+
85
+ let mut results = Vec::new();
86
+
87
+ // Search through all documents
88
+ for doc in self.documents.values() {
89
+ let mut score = 0.0f32;
90
+ let content_lower = doc.content.to_lowercase();
91
+
92
+ // Score based on term matches
93
+ for term in &query_terms {
94
+ let term_lower = term.to_lowercase();
95
+ if content_lower.contains(&term_lower) {
96
+ // Count occurrences
97
+ let count = content_lower.matches(&term_lower).count() as f32;
98
+ score += count * 1.0;
99
+ }
100
+ }
101
+
102
+ if score > 0.0 {
103
+ results.push(SearchResult {
104
+ key: doc.key.clone(),
105
+ content: doc.content.clone(),
106
+ score,
107
+ metadata: doc.metadata.clone(),
108
+ });
109
+ }
110
+ }
111
+
112
+ // Sort by score descending
113
+ results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
114
+ results.truncate(limit);
115
+
116
+ Ok(results)
117
+ }
118
+ }
119
+
120
+ impl Default for SearchCapability {
121
+ fn default() -> Self {
122
+ Self::new()
123
+ }
124
+ }
125
+
126
+ impl FlowCapability for SearchCapability {
127
+ fn id(&self) -> CapabilityId {
128
+ self.capability_id.clone()
129
+ }
130
+
131
+ fn metadata(&self) -> CapabilityMetadata {
132
+ self.metadata.clone()
133
+ }
134
+
135
+ fn initialize(&mut self, _context: &CapabilityContext) -> Result<()> {
136
+ Ok(())
137
+ }
138
+
139
+ fn on_operation(
140
+ &mut self,
141
+ operation: &Operation,
142
+ _context: &CapabilityContext,
143
+ ) -> Result<CapabilityOperationResult> {
144
+ // Only index Insert and Update operations
145
+ match operation.op_type {
146
+ OperationType::Insert | OperationType::Update => {
147
+ if let Some(value) = &operation.value {
148
+ let content = if value.is_string() {
149
+ value.as_str().unwrap_or("").to_string()
150
+ } else {
151
+ value.to_string()
152
+ };
153
+
154
+ let doc = SearchDocument {
155
+ key: operation.key.clone(),
156
+ content,
157
+ metadata: json!({
158
+ "source_operation": operation.op_id,
159
+ "timestamp": operation.timestamp_ms,
160
+ }),
161
+ };
162
+
163
+ self.index_document(&doc)?;
164
+ Ok(CapabilityOperationResult::with_derived_state(json!({
165
+ "indexed": true,
166
+ "key": operation.key.clone(),
167
+ })))
168
+ } else {
169
+ Ok(CapabilityOperationResult::success())
170
+ }
171
+ }
172
+ OperationType::Delete => {
173
+ self.remove_document(&operation.key)?;
174
+ Ok(CapabilityOperationResult::success())
175
+ }
176
+ }
177
+ }
178
+
179
+ fn query(
180
+ &self,
181
+ query: &CapabilityQuery,
182
+ _context: &CapabilityContext,
183
+ ) -> Result<CapabilityQueryResult> {
184
+ if query.query_type != "search" {
185
+ return Ok(CapabilityQueryResult::error(format!(
186
+ "Unknown query type: {}",
187
+ query.query_type
188
+ )));
189
+ }
190
+
191
+ let query_str = query.params.get("q").and_then(|v| v.as_str()).unwrap_or("");
192
+
193
+ let limit = query
194
+ .params
195
+ .get("limit")
196
+ .and_then(|v| v.as_u64())
197
+ .unwrap_or(10) as usize;
198
+
199
+ let results = self.search(query_str, limit)?;
200
+ let result_values: Vec<Value> = results
201
+ .iter()
202
+ .map(|r| {
203
+ json!({
204
+ "key": r.key,
205
+ "content": r.content,
206
+ "score": r.score,
207
+ "metadata": r.metadata,
208
+ })
209
+ })
210
+ .collect();
211
+
212
+ Ok(CapabilityQueryResult::success(result_values))
213
+ }
214
+
215
+ fn rebuild(&mut self, operations: &[Operation], context: &CapabilityContext) -> Result<()> {
216
+ // Clear existing index
217
+ self.documents.clear();
218
+
219
+ // Replay all operations
220
+ for op in operations {
221
+ self.on_operation(op, context)?;
222
+ }
223
+
224
+ Ok(())
225
+ }
226
+ }
227
+
228
+ #[cfg(test)]
229
+ mod tests {
230
+ use super::*;
231
+
232
+ #[test]
233
+ fn test_search_capability_creation() {
234
+ let cap = SearchCapability::new();
235
+ assert_eq!(cap.id().0, "search");
236
+ assert!(cap.metadata().access_model.can_read("docs"));
237
+ assert!(cap.metadata().access_model.can_write("search.index"));
238
+ }
239
+
240
+ #[test]
241
+ fn test_search_capability_initialization() {
242
+ let mut cap = SearchCapability::new();
243
+ let context = CapabilityContext {
244
+ capability_id: cap.id(),
245
+ instance_id: "test-instance".to_string(),
246
+ sequence: 0,
247
+ version: 1,
248
+ };
249
+
250
+ assert!(cap.initialize(&context).is_ok());
251
+ }
252
+
253
+ #[test]
254
+ fn test_search_capability_indexing() {
255
+ let mut cap = SearchCapability::new();
256
+ let context = CapabilityContext {
257
+ capability_id: cap.id(),
258
+ instance_id: "test-instance".to_string(),
259
+ sequence: 0,
260
+ version: 1,
261
+ };
262
+
263
+ cap.initialize(&context).unwrap();
264
+
265
+ let op = Operation::insert(
266
+ 1,
267
+ "instance-1".to_string(),
268
+ 1,
269
+ "docs:1".to_string(),
270
+ json!({"title": "Rust Programming", "content": "Learn Rust"}),
271
+ "Document".to_string(),
272
+ "docs".to_string(),
273
+ );
274
+
275
+ let result = cap.on_operation(&op, &context).unwrap();
276
+ assert!(result.success);
277
+ assert!(result.produced_derived_state);
278
+ }
279
+
280
+ #[test]
281
+ fn test_search_capability_query() {
282
+ let mut cap = SearchCapability::new();
283
+ let context = CapabilityContext {
284
+ capability_id: cap.id(),
285
+ instance_id: "test-instance".to_string(),
286
+ sequence: 0,
287
+ version: 1,
288
+ };
289
+
290
+ cap.initialize(&context).unwrap();
291
+
292
+ let op1 = Operation::insert(
293
+ 1,
294
+ "instance-1".to_string(),
295
+ 1,
296
+ "docs:1".to_string(),
297
+ json!("Rust Programming Language"),
298
+ "Document".to_string(),
299
+ "docs".to_string(),
300
+ );
301
+
302
+ let op2 = Operation::insert(
303
+ 2,
304
+ "instance-1".to_string(),
305
+ 2,
306
+ "docs:2".to_string(),
307
+ json!("Python for Data Science"),
308
+ "Document".to_string(),
309
+ "docs".to_string(),
310
+ );
311
+
312
+ cap.on_operation(&op1, &context).unwrap();
313
+ cap.on_operation(&op2, &context).unwrap();
314
+
315
+ let query = CapabilityQuery {
316
+ query_type: "search".to_string(),
317
+ params: json!({
318
+ "q": "Rust",
319
+ "limit": 10
320
+ }),
321
+ };
322
+
323
+ let results = cap.query(&query, &context).unwrap();
324
+ assert!(results.success);
325
+ assert!(results.results.len() > 0);
326
+ }
327
+
328
+ #[test]
329
+ fn test_search_capability_delete() {
330
+ let mut cap = SearchCapability::new();
331
+ let context = CapabilityContext {
332
+ capability_id: cap.id(),
333
+ instance_id: "test-instance".to_string(),
334
+ sequence: 0,
335
+ version: 1,
336
+ };
337
+
338
+ cap.initialize(&context).unwrap();
339
+
340
+ // Insert a document
341
+ let op1 = Operation::insert(
342
+ 1,
343
+ "instance-1".to_string(),
344
+ 1,
345
+ "docs:1".to_string(),
346
+ json!("Test Document"),
347
+ "Document".to_string(),
348
+ "docs".to_string(),
349
+ );
350
+ cap.on_operation(&op1, &context).unwrap();
351
+
352
+ // Verify it's indexed
353
+ let query = CapabilityQuery {
354
+ query_type: "search".to_string(),
355
+ params: json!({"q": "Test", "limit": 10}),
356
+ };
357
+ let results = cap.query(&query, &context).unwrap();
358
+ assert_eq!(results.results.len(), 1);
359
+
360
+ // Delete it
361
+ let op2 = Operation::delete(
362
+ 2,
363
+ "instance-1".to_string(),
364
+ 2,
365
+ "docs:1".to_string(),
366
+ "Document".to_string(),
367
+ "docs".to_string(),
368
+ );
369
+ cap.on_operation(&op2, &context).unwrap();
370
+
371
+ // Verify it's gone
372
+ let results = cap.query(&query, &context).unwrap();
373
+ assert_eq!(results.results.len(), 0);
374
+ }
375
+
376
+ #[test]
377
+ fn test_search_capability_rebuild() {
378
+ let mut cap = SearchCapability::new();
379
+ let context = CapabilityContext {
380
+ capability_id: cap.id(),
381
+ instance_id: "test-instance".to_string(),
382
+ sequence: 0,
383
+ version: 1,
384
+ };
385
+
386
+ cap.initialize(&context).unwrap();
387
+
388
+ let ops = vec![
389
+ Operation::insert(
390
+ 1,
391
+ "instance-1".to_string(),
392
+ 1,
393
+ "docs:1".to_string(),
394
+ json!("Rust Guide"),
395
+ "Document".to_string(),
396
+ "docs".to_string(),
397
+ ),
398
+ Operation::insert(
399
+ 2,
400
+ "instance-1".to_string(),
401
+ 2,
402
+ "docs:2".to_string(),
403
+ json!("Go Tutorial"),
404
+ "Document".to_string(),
405
+ "docs".to_string(),
406
+ ),
407
+ ];
408
+
409
+ cap.rebuild(&ops, &context).unwrap();
410
+
411
+ let query = CapabilityQuery {
412
+ query_type: "search".to_string(),
413
+ params: json!({"q": "Rust", "limit": 10}),
414
+ };
415
+ let results = cap.query(&query, &context).unwrap();
416
+ assert_eq!(results.results.len(), 1);
417
+ }
418
+ }