create-feltdb 0.11.4 → 0.11.8
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/package-versions.js +1 -1
- package/dist/server-source/crates/feltdb/src/lib.rs +10 -1
- package/dist/server-source/crates/feltdb/src/semantic_decision.rs +124 -12
- package/dist/server-source/crates/feltdb/src/semantic_query.rs +599 -0
- package/dist/server-source/crates/feltdb/src/state_contract.rs +222 -0
- package/dist/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +1 -0
- package/dist/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +1 -0
- package/dist/server-source/crates/feltdb/tests/semantic_query.rs +767 -0
- package/dist/server-source/crates/feltdb/tests/semantic_query_contract.rs +363 -0
- package/dist/server-source/crates/feltdb-server/src/app_state.rs +5 -0
- package/dist/server-source/crates/feltdb-server/src/backup.rs +290 -7
- package/dist/server-source/crates/feltdb-server/src/main.rs +452 -15
- package/dist/server-source/crates/feltdb-wasm/test_wasm.db.lock +0 -0
- package/package.json +1 -1
package/dist/package-versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// One release train keeps generated applications installable. The repository
|
|
2
2
|
// validation script checks these values against every workspace manifest.
|
|
3
|
-
export const FELTDB_PACKAGE_VERSION = '0.11.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.11.8';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -109,6 +109,7 @@ pub mod replication_manager;
|
|
|
109
109
|
pub mod replication_protocol;
|
|
110
110
|
mod routing;
|
|
111
111
|
pub mod semantic_decision;
|
|
112
|
+
pub mod semantic_query;
|
|
112
113
|
pub mod sharding;
|
|
113
114
|
pub mod state_conflict_contract;
|
|
114
115
|
pub mod state_contract;
|
|
@@ -225,7 +226,8 @@ pub use routing::{
|
|
|
225
226
|
RouteDecision,
|
|
226
227
|
};
|
|
227
228
|
pub use semantic_decision::{
|
|
228
|
-
authorize_context, decide_record, decision_cache_key,
|
|
229
|
+
authorize_context, configured_decision_runtime, decide_record, decision_cache_key,
|
|
230
|
+
deterministic_request_key, reuse_record_judgment,
|
|
229
231
|
execute_decision_batch, execute_decision_transport, execute_inline_decision_transport, jev,
|
|
230
232
|
run_choice_diagnostics, run_decision_benchmark, validate_decision_definition,
|
|
231
233
|
validate_decision_result, BinaryDecisionResult, ChoiceDecisionResult, ChoiceDiagnosticsReport,
|
|
@@ -240,6 +242,13 @@ pub use semantic_decision::{
|
|
|
240
242
|
DecisionTransportExecutionResponse, DecisionTransportRuntime, DeterministicDecisionRuntime,
|
|
241
243
|
ScoreDecisionResult, WeightedOption, DEFAULT_DECISION_COLLECTION,
|
|
242
244
|
};
|
|
245
|
+
pub use semantic_query::{
|
|
246
|
+
compose_semantic_page, semantic_clause_hash, semantic_definition_hash,
|
|
247
|
+
validate_semantic_clause, SemanticAnnotation, SemanticCandidate, SemanticOrderDirection,
|
|
248
|
+
SemanticPage, SemanticQueryClause, SemanticQueryError, SemanticQueryFilter,
|
|
249
|
+
SemanticQueryMode, SemanticQueryOrder, SemanticStageSummary, SemanticUnevaluated,
|
|
250
|
+
SemanticUnevaluatedReason, MAX_SEMANTIC_CANDIDATES,
|
|
251
|
+
};
|
|
243
252
|
pub use sharding::{
|
|
244
253
|
HotspotAlert, RebalanceOperation, ShardDistributionSummary, ShardId, ShardKey, ShardManager,
|
|
245
254
|
ShardMetrics, ShardRange, ShardingStrategy,
|
|
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
|
|
4
4
|
use serde_json::{json, Map, Value};
|
|
5
5
|
use sha2::{Digest, Sha256};
|
|
6
6
|
use std::collections::{BTreeMap, BTreeSet};
|
|
7
|
-
use std::sync::Mutex;
|
|
7
|
+
use std::sync::{Arc, Mutex};
|
|
8
8
|
|
|
9
9
|
pub const DEFAULT_DECISION_COLLECTION: &str = "semantic_decisions";
|
|
10
10
|
|
|
@@ -578,10 +578,18 @@ impl std::fmt::Display for DecisionError {
|
|
|
578
578
|
|
|
579
579
|
impl std::error::Error for DecisionError {}
|
|
580
580
|
|
|
581
|
-
struct TransportAuthorizationGate {
|
|
581
|
+
pub(crate) struct TransportAuthorizationGate {
|
|
582
582
|
allowed_fields: BTreeSet<String>,
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
+
impl TransportAuthorizationGate {
|
|
586
|
+
pub(crate) fn new(allowed_fields: &[String]) -> Self {
|
|
587
|
+
Self {
|
|
588
|
+
allowed_fields: allowed_fields.iter().cloned().collect(),
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
585
593
|
impl DecisionAuthorizationGate for TransportAuthorizationGate {
|
|
586
594
|
fn check(
|
|
587
595
|
&self,
|
|
@@ -1312,6 +1320,118 @@ fn stored_to_evaluation(
|
|
|
1312
1320
|
}
|
|
1313
1321
|
}
|
|
1314
1322
|
|
|
1323
|
+
/// The stored judgment whose identity matches `cache_key`, if one exists.
|
|
1324
|
+
///
|
|
1325
|
+
/// A judgment superseded by an intermediate state change that has since been
|
|
1326
|
+
/// reversed carries the same identity as the state it was derived from, so it
|
|
1327
|
+
/// is reactivated rather than re-derived: the lineage's other judgments are
|
|
1328
|
+
/// superseded and this one becomes active again.
|
|
1329
|
+
fn reusable_judgment(
|
|
1330
|
+
db: &FeltDb,
|
|
1331
|
+
storage: &str,
|
|
1332
|
+
cache_key: &str,
|
|
1333
|
+
lineage: &str,
|
|
1334
|
+
) -> Result<Option<DecisionJudgmentRecord>, DecisionError> {
|
|
1335
|
+
let Some(mut stored) = load_judgment(db, storage, cache_key)? else {
|
|
1336
|
+
return Ok(None);
|
|
1337
|
+
};
|
|
1338
|
+
if stored.status != DecisionStatus::Active {
|
|
1339
|
+
supersede_lineage(db, storage, lineage, &stored.id)?;
|
|
1340
|
+
stored.status = DecisionStatus::Active;
|
|
1341
|
+
stored.superseded_at = None;
|
|
1342
|
+
update_judgment(db, storage, &stored.id, &stored)?;
|
|
1343
|
+
}
|
|
1344
|
+
Ok(Some(stored))
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/// Reuse a durable judgment for a record without invoking the runtime.
|
|
1348
|
+
///
|
|
1349
|
+
/// This is the `reuse-only` path of semantic query composition: the record is
|
|
1350
|
+
/// read and authorized exactly as `decide_record` reads and authorizes it, the
|
|
1351
|
+
/// judgment identity is derived from that authorized state and the runtime's
|
|
1352
|
+
/// metadata, and the stored judgment is returned when one with that identity
|
|
1353
|
+
/// exists. `Ok(None)` means no reusable judgment exists; nothing is executed
|
|
1354
|
+
/// and nothing is written except the reactivation described on
|
|
1355
|
+
/// `reusable_judgment`.
|
|
1356
|
+
pub fn reuse_record_judgment(
|
|
1357
|
+
db: &FeltDb,
|
|
1358
|
+
target: &DecisionRecordRef,
|
|
1359
|
+
definition: &DecisionDefinition,
|
|
1360
|
+
runtime_metadata: &DecisionRuntimeMetadata,
|
|
1361
|
+
options: &DecisionOptions,
|
|
1362
|
+
authorization: Option<(&dyn DecisionAuthorizationGate, &DecisionAuthorizationConfig)>,
|
|
1363
|
+
) -> Result<Option<DecisionEvaluation>, DecisionError> {
|
|
1364
|
+
let definition = validate_decision_definition(definition)?;
|
|
1365
|
+
let record: Value = db
|
|
1366
|
+
.capability(&target.collection)
|
|
1367
|
+
.get(&target.record_id)
|
|
1368
|
+
.map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))?
|
|
1369
|
+
.ok_or_else(|| {
|
|
1370
|
+
DecisionError::new(
|
|
1371
|
+
"DECISION_RECORD_NOT_FOUND",
|
|
1372
|
+
"decision target record not found",
|
|
1373
|
+
)
|
|
1374
|
+
})?;
|
|
1375
|
+
let context = authorize_context(
|
|
1376
|
+
&target.collection,
|
|
1377
|
+
&target.record_id,
|
|
1378
|
+
&record,
|
|
1379
|
+
options,
|
|
1380
|
+
authorization,
|
|
1381
|
+
)?;
|
|
1382
|
+
if context.fields.is_empty() {
|
|
1383
|
+
return Err(DecisionError::new(
|
|
1384
|
+
"DECISION_CONTEXT_EMPTY",
|
|
1385
|
+
"decision refuses to evaluate an empty authorized context",
|
|
1386
|
+
));
|
|
1387
|
+
}
|
|
1388
|
+
let cache_key = decision_cache_key(
|
|
1389
|
+
&target.collection,
|
|
1390
|
+
&target.record_id,
|
|
1391
|
+
&definition,
|
|
1392
|
+
&context,
|
|
1393
|
+
runtime_metadata,
|
|
1394
|
+
schema_version(options),
|
|
1395
|
+
);
|
|
1396
|
+
let lineage = lineage_key(
|
|
1397
|
+
&target.collection,
|
|
1398
|
+
&target.record_id,
|
|
1399
|
+
&definition,
|
|
1400
|
+
runtime_metadata,
|
|
1401
|
+
schema_version(options),
|
|
1402
|
+
);
|
|
1403
|
+
Ok(
|
|
1404
|
+
reusable_judgment(db, decision_collection(options), &cache_key, &lineage)?
|
|
1405
|
+
.map(|stored| stored_to_evaluation(stored, &context, "cache")),
|
|
1406
|
+
)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/// Build the runtime binding a FeltDB deployment configures for semantic
|
|
1410
|
+
/// query composition.
|
|
1411
|
+
///
|
|
1412
|
+
/// A query never names its runtime; the deployment does, and the binding's
|
|
1413
|
+
/// identity becomes part of every judgment the query stage produces. Only the
|
|
1414
|
+
/// deterministic runtime exists in Rust today, so this is the only binding a
|
|
1415
|
+
/// deployment can configure; the recording runtime is a transport test double
|
|
1416
|
+
/// that answers every request identically and is refused here.
|
|
1417
|
+
pub fn configured_decision_runtime(
|
|
1418
|
+
runtime: &DecisionTransportRuntime,
|
|
1419
|
+
) -> Result<Arc<dyn DecisionRuntime + Send + Sync>, DecisionError> {
|
|
1420
|
+
match runtime {
|
|
1421
|
+
DecisionTransportRuntime::Deterministic {
|
|
1422
|
+
metadata,
|
|
1423
|
+
responses,
|
|
1424
|
+
} => Ok(Arc::new(DeterministicDecisionRuntime::new(
|
|
1425
|
+
metadata.clone(),
|
|
1426
|
+
responses.clone(),
|
|
1427
|
+
))),
|
|
1428
|
+
DecisionTransportRuntime::Recording { .. } => Err(DecisionError::new(
|
|
1429
|
+
"DECISION_UNSUPPORTED",
|
|
1430
|
+
"the recording runtime is a transport test double, not a configurable runtime binding",
|
|
1431
|
+
)),
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1315
1435
|
pub fn decide_record(
|
|
1316
1436
|
db: &FeltDb,
|
|
1317
1437
|
target: &DecisionRecordRef,
|
|
@@ -1362,13 +1482,7 @@ pub fn decide_record(
|
|
|
1362
1482
|
schema_version(options),
|
|
1363
1483
|
);
|
|
1364
1484
|
let storage = decision_collection(options);
|
|
1365
|
-
if let Some(
|
|
1366
|
-
if stored.status != DecisionStatus::Active {
|
|
1367
|
-
supersede_lineage(db, storage, &lineage, &stored.id)?;
|
|
1368
|
-
stored.status = DecisionStatus::Active;
|
|
1369
|
-
stored.superseded_at = None;
|
|
1370
|
-
update_judgment(db, storage, &stored.id, &stored)?;
|
|
1371
|
-
}
|
|
1485
|
+
if let Some(stored) = reusable_judgment(db, storage, &cache_key, &lineage)? {
|
|
1372
1486
|
return Ok(stored_to_evaluation(stored, &context, "cache"));
|
|
1373
1487
|
}
|
|
1374
1488
|
|
|
@@ -1537,9 +1651,7 @@ pub fn execute_inline_decision_transport(
|
|
|
1537
1651
|
let runtime = TransportRuntimeHandle::new(&request.runtime);
|
|
1538
1652
|
let authorization_gate = request.authorization.as_ref().map(|authorization| {
|
|
1539
1653
|
(
|
|
1540
|
-
TransportAuthorizationGate
|
|
1541
|
-
allowed_fields: authorization.allowed_fields.iter().cloned().collect(),
|
|
1542
|
-
},
|
|
1654
|
+
TransportAuthorizationGate::new(&authorization.allowed_fields),
|
|
1543
1655
|
DecisionAuthorizationConfig {
|
|
1544
1656
|
principal: authorization.principal.clone(),
|
|
1545
1657
|
},
|