create-feltdb 0.11.3 → 0.11.7

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.
@@ -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';
3
+ export const FELTDB_PACKAGE_VERSION = '0.11.7';
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, deterministic_request_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(mut stored) = load_judgment(db, storage, &cache_key)? {
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
  },