@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.
- package/dist/agent-runtime.d.ts +64 -2
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +121 -11
- package/dist/agent-state.d.ts +48 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +174 -12
- package/dist/agent.d.ts +10 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -1
- package/dist/build-identity.json +4 -4
- package/dist/cli/commands.js +27 -6
- package/dist/collection.d.ts +3 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +30 -8
- package/dist/create/build-identity.json +4 -4
- package/dist/create/cli.js +1 -1
- package/dist/create/create.js +4 -6
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +6 -6
- package/dist/create/server-source/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb/src/lib.rs +126 -35
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +162 -53
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +137 -1
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1427 -91
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
- package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
- package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +879 -84
- package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
- package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
- package/dist/db.d.ts +64 -9
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +153 -27
- package/dist/deployment.d.ts +19 -0
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +38 -2
- package/dist/embedded-query.d.ts.map +1 -1
- package/dist/embedded-query.js +5 -4
- package/dist/embedded-transaction.d.ts +8 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +55 -19
- package/dist/error-codes.d.ts +31 -2
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +41 -2
- package/dist/feltdb.d.ts +7 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +27 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +145 -7
- package/dist/http-client.d.ts +11 -3
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +27 -14
- package/dist/http-db.d.ts +27 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +252 -54
- package/dist/index.js +1 -1
- package/dist/local-state-lock.d.ts +34 -0
- package/dist/local-state-lock.d.ts.map +1 -0
- package/dist/local-state-lock.js +225 -0
- package/dist/reactive-graph.d.ts +0 -3
- package/dist/reactive-graph.d.ts.map +1 -1
- package/dist/reactive-graph.js +11 -1
- package/dist/runtime-registry.d.ts +7 -2
- package/dist/runtime-registry.d.ts.map +1 -1
- package/dist/runtime-registry.js +2 -2
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-BjM1ylL3.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
- package/dist/studio-app/assets/index-1wd0uKua.js +30 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +27 -11
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +20 -14
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workload.d.ts +31 -3
- package/dist/workload.d.ts.map +1 -1
- package/dist/workload.js +28 -3
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
- 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.
|
|
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.
|
|
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
|
|
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(
|
|
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(
|
|
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
|
]
|