@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.
- package/dist/build-identity.json +13 -4
- package/dist/cli/commands.js +96 -45
- package/dist/cli/workspace-integration.js +8 -0
- package/dist/collection.d.ts +41 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +116 -47
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +11 -1
- package/dist/create/build-identity.json +13 -4
- package/dist/create/cli.js +49 -40
- package/dist/create/create.js +32 -19
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +18 -3
- package/dist/create/server-source/Cargo.toml +4 -2
- package/dist/create/server-source/crates/feltdb/Cargo.toml +20 -0
- package/dist/create/server-source/crates/feltdb/LICENSE +21 -0
- package/dist/create/server-source/crates/feltdb/README.md +75 -0
- package/dist/create/server-source/crates/feltdb/src/agent_state.rs +320 -0
- package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +1545 -0
- package/dist/create/server-source/crates/feltdb/src/authority.rs +145 -15
- package/dist/create/server-source/crates/feltdb/src/lib.rs +227 -23
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +204 -1
- package/dist/create/server-source/crates/feltdb/src/workload.rs +685 -53
- package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +1188 -0
- package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +2884 -0
- package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +534 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +23340 -0
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +18 -12
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +17 -5
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +29 -0
- package/dist/create/server-source/crates/feltdb-client/LICENSE +21 -0
- package/dist/create/server-source/crates/feltdb-client/README.md +70 -0
- package/dist/create/server-source/crates/feltdb-client/src/application.rs +527 -0
- package/dist/create/server-source/crates/feltdb-client/src/authority.rs +679 -0
- package/dist/create/server-source/crates/feltdb-client/src/control.rs +292 -0
- package/dist/create/server-source/crates/feltdb-client/src/error.rs +227 -0
- package/dist/create/server-source/crates/feltdb-client/src/events.rs +146 -0
- package/dist/create/server-source/crates/feltdb-client/src/lib.rs +466 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/README.md +4 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/private-ca.pem +20 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/server-key.pem +28 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/server.pem +20 -0
- package/dist/create/server-source/crates/feltdb-client/tests/private_ca.rs +95 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +20 -1
- package/dist/create/server-source/crates/feltdb-server/LICENSE +21 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +12 -1
- package/dist/create/server-source/crates/feltdb-server/src/agent_execution.rs +511 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +28 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +6 -2
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +21 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +845 -138
- package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +683 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +3 -1
- package/dist/create/setup-summary.js +40 -0
- package/dist/create/template/default-project/README.md +12 -7
- package/dist/create/template/default-project/src/pages/Dashboard.tsx +1 -1
- package/dist/create/template/dot-feltdb-README.md +3 -3
- package/dist/db.d.ts +27 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +60 -21
- package/dist/development-runtime-bridge.d.ts +20 -0
- package/dist/development-runtime-bridge.d.ts.map +1 -1
- package/dist/development-runtime-bridge.js +74 -4
- package/dist/embedded-transaction.d.ts +75 -8
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +124 -18
- package/dist/error-codes.d.ts +2 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +2 -0
- package/dist/feltdb.d.ts +1 -11
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +6 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +43 -11
- package/dist/http-db.d.ts +13 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +33 -4
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +65 -41
- package/dist/memory-db.d.ts.map +1 -1
- package/dist/memory-db.js +29 -8
- package/dist/studio/{KeyManagementPanel-DZuSeWBK.js → KeyManagementPanel-skUNnKRw.js} +36 -28
- package/dist/studio/app.d.ts +8 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.js +1 -1
- package/dist/studio/components/RouteErrorBoundary.d.ts +5 -0
- package/dist/studio/components/RouteErrorBoundary.d.ts.map +1 -0
- package/dist/studio/components/index.js +2 -2
- package/dist/studio/{components-Bivm2lX6.js → components-BSGmHfif.js} +6 -6
- package/dist/studio/control-plane/Agents.d.ts.map +1 -1
- package/dist/studio/control-plane/Data.d.ts.map +1 -1
- package/dist/studio/control-plane/Instance.d.ts +4 -1
- package/dist/studio/control-plane/Instance.d.ts.map +1 -1
- package/dist/studio/control-plane/Operations.d.ts +4 -1
- package/dist/studio/control-plane/Operations.d.ts.map +1 -1
- package/dist/studio/control-plane/Overview.d.ts.map +1 -1
- package/dist/studio/control-plane/Schema.d.ts.map +1 -1
- package/dist/studio/control-plane/context.d.ts +4 -1
- package/dist/studio/control-plane/context.d.ts.map +1 -1
- package/dist/studio/control-plane/index.js +2 -2
- package/dist/studio/control-plane/primitives.d.ts +24 -0
- package/dist/studio/control-plane/primitives.d.ts.map +1 -1
- package/dist/studio/{control-plane-CLm69XwX.js → control-plane-CaBA7Rgv.js} +1374 -1298
- package/dist/studio/index.d.ts +1 -0
- package/dist/studio/index.d.ts.map +1 -1
- package/dist/studio/index.js +415 -237
- package/dist/studio/navigation.d.ts +57 -0
- package/dist/studio/navigation.d.ts.map +1 -0
- package/dist/studio/studio.css +1 -1
- package/dist/studio/utils/control-plane-api.d.ts +2 -0
- package/dist/studio/utils/control-plane-api.d.ts.map +1 -1
- package/dist/studio/utils/control-plane-api.js +9 -4
- package/dist/studio/utils/service-api.d.ts.map +1 -1
- package/dist/studio/utils/service-api.js +5 -4
- package/dist/studio-app/assets/{feltdb_wasm-DACcD-zs.js → feltdb_wasm-3hDngl1h.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
- package/dist/studio-app/assets/index-Dql1hS5m.js +30 -0
- package/dist/studio-app/assets/{index-CiC8VY-a.css → index-vi21oEQa.css} +1 -1
- package/dist/studio-app/index.html +2 -2
- package/dist/telemetry.d.ts +1 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +4 -3
- package/dist/transaction-identity.d.ts +62 -0
- package/dist/transaction-identity.d.ts.map +1 -0
- package/dist/transaction-identity.js +138 -0
- package/dist/transaction.d.ts +50 -5
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +28 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workspace/local-development-authority.d.ts +19 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -1
- package/dist/workspace/local-development-authority.js +335 -15
- package/package.json +1 -1
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/studio-app/assets/feltdb_wasm_bg-BejThWID.wasm +0 -0
- 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
|
-
|
|
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());
|