@feltdb/core 0.12.1 → 0.13.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 (139) hide show
  1. package/dist/build-identity.json +13 -4
  2. package/dist/cli/commands.js +96 -45
  3. package/dist/cli/workspace-integration.js +8 -0
  4. package/dist/collection.d.ts +41 -4
  5. package/dist/collection.d.ts.map +1 -1
  6. package/dist/collection.js +116 -47
  7. package/dist/composition.d.ts.map +1 -1
  8. package/dist/composition.js +11 -1
  9. package/dist/create/build-identity.json +13 -4
  10. package/dist/create/cli.js +49 -40
  11. package/dist/create/create.js +32 -19
  12. package/dist/create/package-versions.js +1 -1
  13. package/dist/create/server-source/Cargo.lock +18 -3
  14. package/dist/create/server-source/Cargo.toml +4 -2
  15. package/dist/create/server-source/crates/feltdb/Cargo.toml +20 -0
  16. package/dist/create/server-source/crates/feltdb/LICENSE +21 -0
  17. package/dist/create/server-source/crates/feltdb/README.md +75 -0
  18. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +320 -0
  19. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +1545 -0
  20. package/dist/create/server-source/crates/feltdb/src/authority.rs +145 -15
  21. package/dist/create/server-source/crates/feltdb/src/lib.rs +227 -23
  22. package/dist/create/server-source/crates/feltdb/src/state_model.rs +1 -5
  23. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +204 -1
  24. package/dist/create/server-source/crates/feltdb/src/workload.rs +685 -53
  25. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +1188 -0
  26. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +2884 -0
  27. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +534 -0
  28. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +23340 -0
  29. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +18 -12
  30. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +17 -5
  31. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +29 -0
  32. package/dist/create/server-source/crates/feltdb-client/LICENSE +21 -0
  33. package/dist/create/server-source/crates/feltdb-client/README.md +70 -0
  34. package/dist/create/server-source/crates/feltdb-client/src/application.rs +527 -0
  35. package/dist/create/server-source/crates/feltdb-client/src/authority.rs +679 -0
  36. package/dist/create/server-source/crates/feltdb-client/src/control.rs +292 -0
  37. package/dist/create/server-source/crates/feltdb-client/src/error.rs +227 -0
  38. package/dist/create/server-source/crates/feltdb-client/src/events.rs +146 -0
  39. package/dist/create/server-source/crates/feltdb-client/src/lib.rs +466 -0
  40. package/dist/create/server-source/crates/feltdb-client/tests/fixtures/README.md +4 -0
  41. package/dist/create/server-source/crates/feltdb-client/tests/fixtures/private-ca.pem +20 -0
  42. package/dist/create/server-source/crates/feltdb-client/tests/fixtures/server-key.pem +28 -0
  43. package/dist/create/server-source/crates/feltdb-client/tests/fixtures/server.pem +20 -0
  44. package/dist/create/server-source/crates/feltdb-client/tests/private_ca.rs +95 -0
  45. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +20 -1
  46. package/dist/create/server-source/crates/feltdb-server/LICENSE +21 -0
  47. package/dist/create/server-source/crates/feltdb-server/README.md +12 -1
  48. package/dist/create/server-source/crates/feltdb-server/src/agent_execution.rs +511 -0
  49. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +28 -0
  50. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +6 -2
  51. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +21 -0
  52. package/dist/create/server-source/crates/feltdb-server/src/main.rs +845 -138
  53. package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +683 -0
  54. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +3 -1
  55. package/dist/create/setup-summary.js +40 -0
  56. package/dist/create/template/default-project/README.md +12 -7
  57. package/dist/create/template/default-project/src/pages/Dashboard.tsx +1 -1
  58. package/dist/create/template/dot-feltdb-README.md +3 -3
  59. package/dist/db.d.ts +27 -1
  60. package/dist/db.d.ts.map +1 -1
  61. package/dist/db.js +60 -21
  62. package/dist/development-runtime-bridge.d.ts +20 -0
  63. package/dist/development-runtime-bridge.d.ts.map +1 -1
  64. package/dist/development-runtime-bridge.js +74 -4
  65. package/dist/embedded-transaction.d.ts +75 -8
  66. package/dist/embedded-transaction.d.ts.map +1 -1
  67. package/dist/embedded-transaction.js +124 -18
  68. package/dist/error-codes.d.ts +2 -0
  69. package/dist/error-codes.d.ts.map +1 -1
  70. package/dist/error-codes.js +2 -0
  71. package/dist/feltdb.d.ts +1 -11
  72. package/dist/feltdb.d.ts.map +1 -1
  73. package/dist/file-db.d.ts +6 -0
  74. package/dist/file-db.d.ts.map +1 -1
  75. package/dist/file-db.js +43 -11
  76. package/dist/http-db.d.ts +13 -0
  77. package/dist/http-db.d.ts.map +1 -1
  78. package/dist/http-db.js +33 -4
  79. package/dist/indexeddb-db.d.ts.map +1 -1
  80. package/dist/indexeddb-db.js +65 -41
  81. package/dist/memory-db.d.ts.map +1 -1
  82. package/dist/memory-db.js +29 -8
  83. package/dist/studio/{KeyManagementPanel-DZuSeWBK.js → KeyManagementPanel-skUNnKRw.js} +36 -28
  84. package/dist/studio/app.d.ts +8 -1
  85. package/dist/studio/app.d.ts.map +1 -1
  86. package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -1
  87. package/dist/studio/components/KeyManagementPanel.js +1 -1
  88. package/dist/studio/components/RouteErrorBoundary.d.ts +5 -0
  89. package/dist/studio/components/RouteErrorBoundary.d.ts.map +1 -0
  90. package/dist/studio/components/index.js +2 -2
  91. package/dist/studio/{components-Bivm2lX6.js → components-BSGmHfif.js} +6 -6
  92. package/dist/studio/control-plane/Agents.d.ts.map +1 -1
  93. package/dist/studio/control-plane/Data.d.ts.map +1 -1
  94. package/dist/studio/control-plane/Instance.d.ts +4 -1
  95. package/dist/studio/control-plane/Instance.d.ts.map +1 -1
  96. package/dist/studio/control-plane/Operations.d.ts +4 -1
  97. package/dist/studio/control-plane/Operations.d.ts.map +1 -1
  98. package/dist/studio/control-plane/Overview.d.ts.map +1 -1
  99. package/dist/studio/control-plane/Schema.d.ts.map +1 -1
  100. package/dist/studio/control-plane/context.d.ts +4 -1
  101. package/dist/studio/control-plane/context.d.ts.map +1 -1
  102. package/dist/studio/control-plane/index.js +2 -2
  103. package/dist/studio/control-plane/primitives.d.ts +24 -0
  104. package/dist/studio/control-plane/primitives.d.ts.map +1 -1
  105. package/dist/studio/{control-plane-CLm69XwX.js → control-plane-CaBA7Rgv.js} +1374 -1298
  106. package/dist/studio/index.d.ts +1 -0
  107. package/dist/studio/index.d.ts.map +1 -1
  108. package/dist/studio/index.js +415 -237
  109. package/dist/studio/navigation.d.ts +57 -0
  110. package/dist/studio/navigation.d.ts.map +1 -0
  111. package/dist/studio/studio.css +1 -1
  112. package/dist/studio/utils/control-plane-api.d.ts +2 -0
  113. package/dist/studio/utils/control-plane-api.d.ts.map +1 -1
  114. package/dist/studio/utils/control-plane-api.js +9 -4
  115. package/dist/studio/utils/service-api.d.ts.map +1 -1
  116. package/dist/studio/utils/service-api.js +5 -4
  117. package/dist/studio-app/assets/{feltdb_wasm-DACcD-zs.js → feltdb_wasm-3hDngl1h.js} +1 -1
  118. package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
  119. package/dist/studio-app/assets/index-Dql1hS5m.js +30 -0
  120. package/dist/studio-app/assets/{index-CiC8VY-a.css → index-vi21oEQa.css} +1 -1
  121. package/dist/studio-app/index.html +2 -2
  122. package/dist/telemetry.d.ts +1 -0
  123. package/dist/telemetry.d.ts.map +1 -1
  124. package/dist/telemetry.js +4 -3
  125. package/dist/transaction-identity.d.ts +62 -0
  126. package/dist/transaction-identity.d.ts.map +1 -0
  127. package/dist/transaction-identity.js +138 -0
  128. package/dist/transaction.d.ts +50 -5
  129. package/dist/transaction.d.ts.map +1 -1
  130. package/dist/transaction.js +28 -0
  131. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  132. package/dist/wasm/package.json +1 -1
  133. package/dist/workspace/local-development-authority.d.ts +19 -0
  134. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  135. package/dist/workspace/local-development-authority.js +335 -15
  136. package/package.json +1 -1
  137. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  138. package/dist/studio-app/assets/feltdb_wasm_bg-BejThWID.wasm +0 -0
  139. package/dist/studio-app/assets/index-CoZaxnlm.js +0 -30
@@ -486,6 +486,48 @@ impl WorkerMeshStore {
486
486
  }
487
487
  self.persist()
488
488
  }
489
+ /// Recover work whose claim lease expired, and release those claims from
490
+ /// the workers that held them so their capacity is not held by work they
491
+ /// no longer own. Recovery itself — `WorkerLost`, the attempt budget, the
492
+ /// decision — is `WorkloadStore::recover_expired`. Worker liveness is not
493
+ /// judged here; that is `detect_failures`.
494
+ pub fn recover_expired_work(
495
+ &mut self,
496
+ now: i64,
497
+ workloads: &mut WorkloadStore,
498
+ ) -> Result<Vec<String>, WorkerMeshError> {
499
+ let expired: Vec<(String, String, String)> = workloads
500
+ .workloads
501
+ .values()
502
+ .filter_map(|w| w.current_claim.as_ref().map(|claim| (w, claim)))
503
+ .filter(|(_, claim)| claim.expires_at <= now)
504
+ .map(|(w, claim)| {
505
+ (
506
+ w.workload_id.clone(),
507
+ claim.worker_id.clone(),
508
+ claim.claim_id.clone(),
509
+ )
510
+ })
511
+ .collect();
512
+ let recovered = workloads.recover_expired(now)?;
513
+ for (workload_id, worker_id, claim_id) in expired {
514
+ // Release a claim only once the committed workload no longer holds
515
+ // it, whichever writer of the store recovered it.
516
+ let still_current = workloads
517
+ .workloads
518
+ .get(&workload_id)
519
+ .and_then(|w| w.current_claim.as_ref())
520
+ .is_some_and(|claim| claim.claim_id == claim_id);
521
+ let held = self
522
+ .workers
523
+ .get(&worker_id)
524
+ .is_some_and(|worker| worker.claims.contains(&claim_id));
525
+ if held && !still_current {
526
+ self.release_claim(&worker_id, &claim_id)?;
527
+ }
528
+ }
529
+ Ok(recovered)
530
+ }
489
531
  pub fn detect_failures(
490
532
  &mut self,
491
533
  now: i64,
@@ -507,7 +549,7 @@ impl WorkerMeshStore {
507
549
  worker.claims.clear();
508
550
  }
509
551
  }
510
- workloads.recover_expired(now)?;
552
+ self.recover_expired_work(now, workloads)?;
511
553
  self.persist()?;
512
554
  Ok(failed)
513
555
  }
@@ -852,6 +894,167 @@ mod tests {
852
894
  let _ = fs::remove_file(w);
853
895
  }
854
896
  #[test]
897
+ fn a_claim_recovered_by_another_writer_is_still_released() {
898
+ let (m, wpath, mut mesh, mut workloads, grant) = setup("release-raced");
899
+ let worker = register(&mut mesh, grant.clone());
900
+ let heartbeat = |timestamp| WorkerHeartbeat {
901
+ worker_id: worker.worker_id.clone(),
902
+ version: "1".into(),
903
+ capability_version: 1,
904
+ capacity: WorkerCapacity {
905
+ concurrency: 1,
906
+ active: 0,
907
+ },
908
+ health: WorkerHealth::Healthy,
909
+ timestamp,
910
+ running_claims: BTreeSet::new(),
911
+ };
912
+ mesh.heartbeat(&worker.worker_id, heartbeat(11), 2).unwrap();
913
+ let subject = grant.grant.subject.clone();
914
+ let create = |key: &str| CreateWorkload {
915
+ tenant_id: "t".into(),
916
+ application_id: "a".into(),
917
+ environment: "production".into(),
918
+ definition_id: "extract".into(),
919
+ definition_revision: "r".into(),
920
+ trigger: "manual".into(),
921
+ input: serde_json::json!({"file": key}),
922
+ requested_by: subject.clone(),
923
+ session_id: None,
924
+ authorization_snapshot: grant.clone(),
925
+ capability_snapshot: ["documents.extract".into()].into(),
926
+ connection_scopes: BTreeSet::new(),
927
+ priority: 0,
928
+ idempotency_key: key.into(),
929
+ retry_policy: WorkloadRetryPolicy::default(),
930
+ timeouts: WorkloadTimeouts::default(),
931
+ dependencies: vec![],
932
+ not_before: None,
933
+ created_at: 11,
934
+ };
935
+ let first = workloads.create(create("first")).unwrap();
936
+ let second = workloads.create(create("second")).unwrap();
937
+ mesh.place_and_claim(
938
+ &mut workloads,
939
+ &first.workload_id,
940
+ &worker.worker_id,
941
+ 12,
942
+ 1000,
943
+ )
944
+ .unwrap();
945
+ assert_eq!(mesh.workers[&worker.worker_id].capacity.active, 1);
946
+ // Another writer of the same store recovers the work first.
947
+ let mut other = WorkloadStore::from_db(workloads.db_for_tests()).unwrap();
948
+ assert_eq!(
949
+ other.recover_expired(14).unwrap(),
950
+ vec![first.workload_id.clone()]
951
+ );
952
+ // This node's pass loses the guarded commit, adopts the committed
953
+ // workload, does not report it as its own recovery, and still frees the
954
+ // worker, whose claim is no longer the workload's.
955
+ assert!(mesh
956
+ .recover_expired_work(14, &mut workloads)
957
+ .unwrap()
958
+ .is_empty());
959
+ let recovered = workloads.get(&first.workload_id).unwrap();
960
+ assert_eq!(recovered.state, crate::workload::WorkloadState::Ready);
961
+ assert_eq!(
962
+ recovered
963
+ .execution_history
964
+ .iter()
965
+ .filter(|event| event.to == crate::workload::WorkloadState::Recovering)
966
+ .count(),
967
+ 1
968
+ );
969
+ assert!(mesh.workers[&worker.worker_id].claims.is_empty());
970
+ assert_eq!(mesh.workers[&worker.worker_id].capacity.active, 0);
971
+ let _ = second;
972
+ let _ = fs::remove_file(m);
973
+ let _ = fs::remove_file(wpath);
974
+ }
975
+ #[test]
976
+ fn recovering_an_expired_claim_releases_it_from_a_live_worker() {
977
+ let (m, wpath, mut mesh, mut workloads, grant) = setup("release-expired");
978
+ let worker = register(&mut mesh, grant.clone());
979
+ let heartbeat = |timestamp| WorkerHeartbeat {
980
+ worker_id: worker.worker_id.clone(),
981
+ version: "1".into(),
982
+ capability_version: 1,
983
+ capacity: WorkerCapacity {
984
+ concurrency: 1,
985
+ active: 0,
986
+ },
987
+ health: WorkerHealth::Healthy,
988
+ timestamp,
989
+ running_claims: BTreeSet::new(),
990
+ };
991
+ mesh.heartbeat(&worker.worker_id, heartbeat(11), 2).unwrap();
992
+ let subject = grant.grant.subject.clone();
993
+ let create = |key: &str| CreateWorkload {
994
+ tenant_id: "t".into(),
995
+ application_id: "a".into(),
996
+ environment: "production".into(),
997
+ definition_id: "extract".into(),
998
+ definition_revision: "r".into(),
999
+ trigger: "manual".into(),
1000
+ input: serde_json::json!({"file": key}),
1001
+ requested_by: subject.clone(),
1002
+ session_id: None,
1003
+ authorization_snapshot: grant.clone(),
1004
+ capability_snapshot: ["documents.extract".into()].into(),
1005
+ connection_scopes: BTreeSet::new(),
1006
+ priority: 0,
1007
+ idempotency_key: key.into(),
1008
+ retry_policy: WorkloadRetryPolicy::default(),
1009
+ timeouts: WorkloadTimeouts::default(),
1010
+ dependencies: vec![],
1011
+ not_before: None,
1012
+ created_at: 11,
1013
+ };
1014
+ let first = workloads.create(create("first")).unwrap();
1015
+ let second = workloads.create(create("second")).unwrap();
1016
+ mesh.place_and_claim(
1017
+ &mut workloads,
1018
+ &first.workload_id,
1019
+ &worker.worker_id,
1020
+ 12,
1021
+ 1000,
1022
+ )
1023
+ .unwrap();
1024
+ assert_eq!(mesh.workers[&worker.worker_id].capacity.active, 1);
1025
+ // The lease expired at 13. The server's recovery is claim recovery: it
1026
+ // recovers the work and frees the worker, and does not judge liveness.
1027
+ let recovered = mesh.recover_expired_work(14, &mut workloads).unwrap();
1028
+ assert_eq!(recovered, vec![first.workload_id.clone()]);
1029
+ let live = &mesh.workers[&worker.worker_id];
1030
+ assert!(live.claims.is_empty());
1031
+ assert_eq!(live.capacity.active, 0);
1032
+ assert_eq!(live.lifecycle, WorkerLifecycle::Ready);
1033
+ assert_eq!(
1034
+ workloads.get(&first.workload_id).unwrap().state,
1035
+ crate::workload::WorkloadState::Ready
1036
+ );
1037
+ // Its capacity is usable again.
1038
+ mesh.place_and_claim(
1039
+ &mut workloads,
1040
+ &second.workload_id,
1041
+ &worker.worker_id,
1042
+ 15,
1043
+ 10_000,
1044
+ )
1045
+ .unwrap();
1046
+ // Long past its heartbeat timeout, claim recovery still leaves the
1047
+ // worker's lifecycle alone; only `detect_failures` marks it offline.
1048
+ mesh.recover_expired_work(1_000, &mut workloads).unwrap();
1049
+ assert_ne!(
1050
+ mesh.workers[&worker.worker_id].lifecycle,
1051
+ WorkerLifecycle::Offline
1052
+ );
1053
+ assert!(mesh.workers[&worker.worker_id].claims.is_empty());
1054
+ let _ = fs::remove_file(m);
1055
+ let _ = fs::remove_file(wpath);
1056
+ }
1057
+ #[test]
855
1058
  fn placement_claim_fencing_failure_and_recovery() {
856
1059
  let (m, wpath, mut mesh, mut workloads, grant) = setup("claim");
857
1060
  let worker = register(&mut mesh, grant.clone());