@feltdb/core 0.13.0 → 0.15.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 (92) hide show
  1. package/dist/agent-runtime.d.ts +64 -2
  2. package/dist/agent-runtime.d.ts.map +1 -1
  3. package/dist/agent-runtime.js +121 -11
  4. package/dist/agent-state.d.ts +48 -0
  5. package/dist/agent-state.d.ts.map +1 -1
  6. package/dist/agent-state.js +174 -12
  7. package/dist/agent.d.ts +10 -1
  8. package/dist/agent.d.ts.map +1 -1
  9. package/dist/agent.js +10 -1
  10. package/dist/build-identity.json +4 -4
  11. package/dist/cli/commands.js +27 -6
  12. package/dist/collection.d.ts +3 -1
  13. package/dist/collection.d.ts.map +1 -1
  14. package/dist/collection.js +30 -8
  15. package/dist/create/build-identity.json +4 -4
  16. package/dist/create/cli.js +1 -1
  17. package/dist/create/create.js +4 -6
  18. package/dist/create/package-versions.js +1 -1
  19. package/dist/create/server-source/Cargo.lock +6 -6
  20. package/dist/create/server-source/Cargo.toml +1 -1
  21. package/dist/create/server-source/crates/feltdb/src/lib.rs +126 -35
  22. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +162 -53
  23. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +137 -1
  24. package/dist/create/server-source/crates/feltdb/src/workload.rs +1427 -91
  25. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  26. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  27. package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
  28. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
  29. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
  30. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
  31. package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
  32. package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
  33. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
  34. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
  35. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
  36. package/dist/create/server-source/crates/feltdb-server/src/main.rs +879 -84
  37. package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
  38. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
  39. package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
  40. package/dist/db.d.ts +64 -9
  41. package/dist/db.d.ts.map +1 -1
  42. package/dist/db.js +153 -27
  43. package/dist/deployment.d.ts +19 -0
  44. package/dist/deployment.d.ts.map +1 -1
  45. package/dist/deployment.js +38 -2
  46. package/dist/embedded-query.d.ts.map +1 -1
  47. package/dist/embedded-query.js +5 -4
  48. package/dist/embedded-transaction.d.ts +8 -4
  49. package/dist/embedded-transaction.d.ts.map +1 -1
  50. package/dist/embedded-transaction.js +55 -19
  51. package/dist/error-codes.d.ts +31 -2
  52. package/dist/error-codes.d.ts.map +1 -1
  53. package/dist/error-codes.js +41 -2
  54. package/dist/feltdb.d.ts +7 -0
  55. package/dist/feltdb.d.ts.map +1 -1
  56. package/dist/file-db.d.ts +27 -0
  57. package/dist/file-db.d.ts.map +1 -1
  58. package/dist/file-db.js +145 -7
  59. package/dist/http-client.d.ts +11 -3
  60. package/dist/http-client.d.ts.map +1 -1
  61. package/dist/http-client.js +27 -14
  62. package/dist/http-db.d.ts +27 -1
  63. package/dist/http-db.d.ts.map +1 -1
  64. package/dist/http-db.js +252 -54
  65. package/dist/index.js +1 -1
  66. package/dist/local-state-lock.d.ts +34 -0
  67. package/dist/local-state-lock.d.ts.map +1 -0
  68. package/dist/local-state-lock.js +225 -0
  69. package/dist/reactive-graph.d.ts +0 -3
  70. package/dist/reactive-graph.d.ts.map +1 -1
  71. package/dist/reactive-graph.js +11 -1
  72. package/dist/runtime-registry.d.ts +7 -2
  73. package/dist/runtime-registry.d.ts.map +1 -1
  74. package/dist/runtime-registry.js +2 -2
  75. package/dist/studio/components/index.js +1 -1
  76. package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
  77. package/dist/studio/index.js +1 -1
  78. package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-BjM1ylL3.js} +1 -1
  79. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  80. package/dist/studio-app/assets/index-1wd0uKua.js +30 -0
  81. package/dist/studio-app/index.html +1 -1
  82. package/dist/transaction.d.ts +27 -11
  83. package/dist/transaction.d.ts.map +1 -1
  84. package/dist/transaction.js +20 -14
  85. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  86. package/dist/wasm/package.json +1 -1
  87. package/dist/workload.d.ts +31 -3
  88. package/dist/workload.d.ts.map +1 -1
  89. package/dist/workload.js +28 -3
  90. package/package.json +1 -1
  91. package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
  92. package/dist/studio-app/assets/index-Dql1hS5m.js +0 -30
@@ -16,7 +16,7 @@ exclude = ["tests/", "target/"]
16
16
  # The wire types are the server's own: the request and response documents are defined once, in
17
17
  # `feltdb`, and both sides serialize the same structs. Exact, because the modules they come from are
18
18
  # not yet covered by `feltdb`'s compatibility policy; the workspace releases both at one version.
19
- feltdb = { version = "=0.2.2", path = "../feltdb" }
19
+ feltdb = { version = "=0.3.0", path = "../feltdb" }
20
20
  reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
21
21
  serde = { version = "1", features = ["derive"] }
22
22
  serde_json = "1"
@@ -24,7 +24,7 @@ hmac = "0.12"
24
24
  # Exact: the server uses `unstable-internals`, which carries no semver promise, so a release of
25
25
  # this crate is built against the one `feltdb` release it was tested with. The workspace shares one
26
26
  # version, so this moves with every release (a mismatch fails to resolve, it cannot drift silently).
27
- feltdb = { version = "=0.2.2", path = "../feltdb", features = ["unstable-internals"] }
27
+ feltdb = { version = "=0.3.0", path = "../feltdb", features = ["unstable-internals"] }
28
28
  futures-core = "0.3"
29
29
  rand = "0.8"
30
30
  ring = "0.17"
@@ -53,7 +53,12 @@ Implemented in this slice:
53
53
  - collection list/create/get/update/delete endpoints
54
54
  - `GET /events` as a server-sent event stream
55
55
  - durable, hashed API keys with namespace and `state:read`, `state:write`, or `events:read` scopes
56
- - `GET /metrics` request, authorization, state, and event counters
56
+ - `GET /metrics` request, authorization, state, and event counters (requires `operator:read`)
57
+ - `GET /v1/status` operator status: release identity, a write/fsync/read-back storage probe, the
58
+ last recorded backup and the last verified restore (requires `operator:read`)
59
+ - `/api/keys` listing (never secrets), issuing, `POST /api/keys/{id}/rotate` and revocation
60
+ (requires `keys:admin`; a caller can never delegate a scope it does not hold). See
61
+ [`docs/reference/managed-operator-surface.md`](../../docs/reference/managed-operator-surface.md).
57
62
  - authenticated `POST /sync/pull` and `POST /sync/push` operation exchange
58
63
  - durable, transitive operation history with content-hash verification, deduplication, and gap rejection
59
64
 
@@ -21,7 +21,7 @@ use crate::{
21
21
  auth::KeyStore, canonical_principals::CanonicalPrincipalAuthority,
22
22
  causal::CanonicalEventAuthority,
23
23
  clock::LeaseClock, cluster::ClusterStore,
24
- connections::ConnectionStore, content::ContentStore, identity::IdentityStore,
24
+ connections::ConnectionStore, console_accounts::ConsoleAccountStore, content::ContentStore, identity::IdentityStore,
25
25
  leases::LeaseStore, metrics::Metrics, portable_bundle::BundleStore,
26
26
  providers::ProviderStore, releases::ReleaseStore, sessions::SessionVerifier,
27
27
  tenancy::TenancyStore,
@@ -60,6 +60,12 @@ pub struct AppState {
60
60
  /// arbiter; this semaphore only bounds pre-commit working memory.
61
61
  pub state_transaction_admission: Arc<tokio::sync::Semaphore>,
62
62
  pub worker_enabled: bool,
63
+ /// Started with `--work-authority`: this node commits Durable Work even
64
+ /// with peers. See `is_work_authority`.
65
+ pub work_authority: bool,
66
+ /// A peer also reports being the Durable Work authority. Set by peer sync;
67
+ /// while set, this node refuses Durable Work writes (fail closed).
68
+ pub work_authority_conflict: Arc<std::sync::atomic::AtomicBool>,
63
69
  pub leases: LeaseStore,
64
70
  pub content: ContentStore,
65
71
  pub artifacts: ArtifactStore,
@@ -74,6 +80,8 @@ pub struct AppState {
74
80
  pub backup_artifacts: Arc<Vec<(String, PathBuf)>>,
75
81
  pub tenancy: TenancyStore,
76
82
  pub sessions: SessionVerifier,
83
+ /// Console (website) accounts; the only source of console user ids.
84
+ pub console_accounts: ConsoleAccountStore,
77
85
  pub principals: CanonicalPrincipalAuthority,
78
86
  pub connections: ConnectionStore,
79
87
  pub identities: IdentityStore,
@@ -230,35 +230,13 @@ impl KeyStore {
230
230
  namespaces: Vec<String>,
231
231
  worker_id: Option<String>,
232
232
  ) -> Result<CreatedApiKey, String> {
233
- let secret: String = rand::thread_rng()
234
- .sample_iter(&Alphanumeric)
235
- .take(48)
236
- .map(char::from)
237
- .collect();
238
- let token = format!("fdb_live_{secret}");
239
- // Service keys are high-entropy random secrets, not user-selected
240
- // passwords. A deterministic digest keeps them out of the credential
241
- // store without imposing Argon2's CPU cost on every API request.
242
- let hash = hash_api_key(&token);
243
- let id: String = rand::thread_rng()
244
- .sample_iter(&Alphanumeric)
245
- .take(12)
246
- .map(char::from)
247
- .collect();
233
+ let (record, created) = new_record(name, scopes, namespaces, worker_id);
248
234
  self.records
249
235
  .write()
250
236
  .map_err(|_| "API key store lock poisoned".to_string())?
251
- .push(ApiKeyRecord {
252
- id: id.clone(),
253
- name,
254
- hash,
255
- scopes,
256
- namespaces,
257
- revoked: false,
258
- worker_id,
259
- });
237
+ .push(record);
260
238
  self.persist()?;
261
- Ok(CreatedApiKey { id, secret: token })
239
+ Ok(created)
262
240
  }
263
241
 
264
242
  pub fn list(&self) -> Result<Vec<ApiKeySummary>, String> {
@@ -277,6 +255,46 @@ impl KeyStore {
277
255
  .collect())
278
256
  }
279
257
 
258
+ /// The listing entry of one key — never its secret or digest.
259
+ pub fn summary(&self, id: &str) -> Option<ApiKeySummary> {
260
+ self.list()
261
+ .ok()?
262
+ .into_iter()
263
+ .find(|summary| summary.id == id)
264
+ }
265
+
266
+ /// Replace a key: issue a new secret with the same name, scopes, namespaces
267
+ /// and worker identity, and revoke the old key in the same store write.
268
+ ///
269
+ /// The new secret is returned once and is never stored or listed. `None`
270
+ /// when no active key has this id.
271
+ pub fn rotate(&self, id: &str) -> Result<Option<CreatedApiKey>, String> {
272
+ let created = {
273
+ let mut records = self
274
+ .records
275
+ .write()
276
+ .map_err(|_| "API key store lock poisoned".to_string())?;
277
+ let Some(old) = records
278
+ .iter_mut()
279
+ .find(|record| record.id == id && !record.revoked)
280
+ else {
281
+ return Ok(None);
282
+ };
283
+ old.revoked = true;
284
+ let (name, scopes, namespaces, worker_id) = (
285
+ old.name.clone(),
286
+ old.scopes.clone(),
287
+ old.namespaces.clone(),
288
+ old.worker_id.clone(),
289
+ );
290
+ let (record, created) = new_record(name, scopes, namespaces, worker_id);
291
+ records.push(record);
292
+ created
293
+ };
294
+ self.persist()?;
295
+ Ok(Some(created))
296
+ }
297
+
280
298
  pub fn revoke(&self, id: &str) -> Result<bool, String> {
281
299
  let found = {
282
300
  let mut records = self
@@ -308,6 +326,13 @@ impl KeyStore {
308
326
  .map_err(|_| "API key store lock poisoned".to_string())?;
309
327
  let bytes = serde_json::to_vec_pretty(&*records).map_err(|error| error.to_string())?;
310
328
  fs::write(&temporary, bytes).map_err(|error| error.to_string())?;
329
+ // Digests, not secrets, but still credential material: owner-only.
330
+ #[cfg(unix)]
331
+ {
332
+ use std::os::unix::fs::PermissionsExt;
333
+ fs::set_permissions(&temporary, fs::Permissions::from_mode(0o600))
334
+ .map_err(|error| error.to_string())?;
335
+ }
311
336
  fs::rename(&temporary, &self.path).map_err(|error| error.to_string())?;
312
337
  Ok(())
313
338
  }
@@ -317,6 +342,41 @@ impl KeyStore {
317
342
  }
318
343
  }
319
344
 
345
+ fn new_record(
346
+ name: String,
347
+ scopes: Vec<String>,
348
+ namespaces: Vec<String>,
349
+ worker_id: Option<String>,
350
+ ) -> (ApiKeyRecord, CreatedApiKey) {
351
+ let secret: String = rand::thread_rng()
352
+ .sample_iter(&Alphanumeric)
353
+ .take(48)
354
+ .map(char::from)
355
+ .collect();
356
+ let token = format!("fdb_live_{secret}");
357
+ // Service keys are high-entropy random secrets, not user-selected
358
+ // passwords. A deterministic digest keeps them out of the credential
359
+ // store without imposing Argon2's CPU cost on every API request.
360
+ let hash = hash_api_key(&token);
361
+ let id: String = rand::thread_rng()
362
+ .sample_iter(&Alphanumeric)
363
+ .take(12)
364
+ .map(char::from)
365
+ .collect();
366
+ (
367
+ ApiKeyRecord {
368
+ id: id.clone(),
369
+ name,
370
+ hash,
371
+ scopes,
372
+ namespaces,
373
+ revoked: false,
374
+ worker_id,
375
+ },
376
+ CreatedApiKey { id, secret: token },
377
+ )
378
+ }
379
+
320
380
  fn hash_api_key(token: &str) -> String {
321
381
  let digest = Sha256::digest(token.as_bytes());
322
382
  let encoded = digest
@@ -166,6 +166,10 @@ pub fn server_artifacts(data: &Path, keys: &Path, audit: &Path) -> Vec<(String,
166
166
  "revoked-sessions.json".into(),
167
167
  data.with_extension("revoked-sessions.json"),
168
168
  ),
169
+ (
170
+ "console-accounts.json".into(),
171
+ data.with_extension("console-accounts.json"),
172
+ ),
169
173
  ("api-keys.json".into(), keys.to_path_buf()),
170
174
  ("audit.log".into(), audit.to_path_buf()),
171
175
  ]