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.
@@ -740,6 +740,12 @@ pub struct CanonicalQuery {
740
740
  pub aggregates: Vec<AggregateDefinition>,
741
741
  #[serde(default)]
742
742
  pub references: Vec<QueryReference>,
743
+ /// A semantic clause is a recognized query concept that this surface does
744
+ /// not compose. It is carried through deserialization so `execute_query`
745
+ /// can refuse it with a structured failure instead of an unknown key
746
+ /// silently disappearing; see `docs/reference/semantic-query.md`.
747
+ #[serde(default, skip_serializing_if = "Option::is_none")]
748
+ pub semantic: Option<Value>,
743
749
  }
744
750
  /// Actual execution timing measurements for a query.
745
751
  /// All fields are MEASURED using Instant::now(), not estimated or derived.
@@ -1198,6 +1204,20 @@ pub fn execute_query(
1198
1204
  ) -> Result<QueryResult, StateFailure> {
1199
1205
  let query_start = Instant::now();
1200
1206
 
1207
+ // Fail closed on semantic intent before any deterministic work. Every query
1208
+ // surface must either implement a recognized semantic clause per the
1209
+ // semantic-query contract or refuse it; this surface refuses, and it never
1210
+ // answers the deterministic part as though the clause had been absent.
1211
+ if let Some(clause) = &query.semantic {
1212
+ return Err(if clause.is_object() {
1213
+ StateFailure::new(
1214
+ "SEMANTIC_UNSUPPORTED",
1215
+ "canonical queries recognize the semantic clause but do not compose semantic decisions; use the bounded authority query",
1216
+ )
1217
+ } else {
1218
+ StateFailure::new("INVALID_QUERY", "semantic clause must be an object")
1219
+ });
1220
+ }
1201
1221
  if query.limit == Some(0) || query.limit.is_some_and(|limit| limit > 1000) {
1202
1222
  return Err(StateFailure::new(
1203
1223
  "INVALID_QUERY",
@@ -3205,6 +3225,7 @@ mod tests {
3205
3225
  field: None,
3206
3226
  }],
3207
3227
  references: vec![],
3228
+ semantic: None,
3208
3229
  },
3209
3230
  None,
3210
3231
  )
@@ -3236,6 +3257,7 @@ mod tests {
3236
3257
  group_by: vec![],
3237
3258
  aggregates: vec![],
3238
3259
  references: vec![],
3260
+ semantic: None,
3239
3261
  },
3240
3262
  None,
3241
3263
  )
@@ -3498,6 +3520,7 @@ mod tests {
3498
3520
  group_by: vec![],
3499
3521
  aggregates: vec![],
3500
3522
  references: vec![],
3523
+ semantic: None,
3501
3524
  },
3502
3525
  None,
3503
3526
  )
@@ -3592,6 +3615,7 @@ mod tests {
3592
3615
  group_by: vec![],
3593
3616
  aggregates: vec![],
3594
3617
  references: vec![],
3618
+ semantic: None,
3595
3619
  },
3596
3620
  None,
3597
3621
  )
@@ -3654,6 +3678,7 @@ mod tests {
3654
3678
  group_by: vec![],
3655
3679
  aggregates: vec![],
3656
3680
  references: vec![],
3681
+ semantic: None,
3657
3682
  },
3658
3683
  None,
3659
3684
  )
@@ -3715,6 +3740,7 @@ mod tests {
3715
3740
  group_by: vec![],
3716
3741
  aggregates: vec![],
3717
3742
  references: vec![],
3743
+ semantic: None,
3718
3744
  },
3719
3745
  None,
3720
3746
  )
@@ -3774,6 +3800,7 @@ mod tests {
3774
3800
  group_by: vec![],
3775
3801
  aggregates: vec![],
3776
3802
  references: vec![],
3803
+ semantic: None,
3777
3804
  },
3778
3805
  None,
3779
3806
  )
@@ -3834,6 +3861,7 @@ mod tests {
3834
3861
  group_by: vec![],
3835
3862
  aggregates: vec![],
3836
3863
  references: vec![],
3864
+ semantic: None,
3837
3865
  };
3838
3866
  let mut ids = Vec::new();
3839
3867
  let first_cursor = loop {
@@ -3888,6 +3916,186 @@ mod tests {
3888
3916
  );
3889
3917
  }
3890
3918
 
3919
+ /// Every query surface must either implement a recognized semantic clause
3920
+ /// per `docs/reference/semantic-query.md` or refuse it. This surface
3921
+ /// refuses: a semantic clause is `SEMANTIC_UNSUPPORTED` before any
3922
+ /// deterministic work, a malformed one is `INVALID_QUERY`, nothing is
3923
+ /// read, nothing is judged, and the same query without the clause still
3924
+ /// answers.
3925
+ #[test]
3926
+ fn semantic_clause_is_recognized_and_refused_never_ignored() {
3927
+ let db = db("semantic-refusal");
3928
+ let schema = schema();
3929
+ execute_transaction(
3930
+ &db,
3931
+ &schema,
3932
+ &TransactionRequest {
3933
+ transaction_id: Some("seed".into()),
3934
+ tenant_id: "tenant".into(),
3935
+ application_id: "app".into(),
3936
+ revision_id: "rev".into(),
3937
+ schema_version: 1,
3938
+ state_namespace: None,
3939
+ causal_parent: None,
3940
+ authorization: auth(),
3941
+ preconditions: vec![],
3942
+ operations: vec![TransactionOperation {
3943
+ kind: TransactionOperationKind::Insert,
3944
+ collection: "incidents".into(),
3945
+ id: "i1".into(),
3946
+ value: serde_json::json!({ "title": "First", "status": "open" }),
3947
+ if_version: None,
3948
+ }],
3949
+ },
3950
+ None,
3951
+ )
3952
+ .unwrap();
3953
+ let context = begin_query_read(&db, &schema, "app", auth()).unwrap();
3954
+ let plain = CanonicalQuery {
3955
+ collection: "incidents".into(),
3956
+ filter: Some(QueryFilter::Eq {
3957
+ field: "status".into(),
3958
+ value: Value::String("open".into()),
3959
+ }),
3960
+ order_by: vec![],
3961
+ limit: Some(10),
3962
+ offset: 0,
3963
+ cursor: None,
3964
+ projection: vec![],
3965
+ group_by: vec![],
3966
+ aggregates: vec![],
3967
+ references: vec![],
3968
+ semantic: None,
3969
+ };
3970
+ let state_before = db.state_rows().unwrap().len();
3971
+
3972
+ // A semantic clause in the bounded query's own grammar, and one that
3973
+ // only expresses semantic intent, are both recognized and refused.
3974
+ for clause in [
3975
+ serde_json::json!({
3976
+ "decision": { "kind": "binary", "predicate": "needs a human" },
3977
+ "filter": { "is": true },
3978
+ }),
3979
+ serde_json::json!({ "decision": "needs_human" }),
3980
+ serde_json::json!({}),
3981
+ ] {
3982
+ let failure = execute_query(
3983
+ &db,
3984
+ &schema,
3985
+ &context,
3986
+ &CanonicalQuery {
3987
+ semantic: Some(clause),
3988
+ ..plain.clone()
3989
+ },
3990
+ None,
3991
+ )
3992
+ .expect_err("semantic intent is never silently dropped");
3993
+ assert_eq!(failure.code, "SEMANTIC_UNSUPPORTED");
3994
+ }
3995
+
3996
+ // A semantic value that is not a clause is a validation error, not a
3997
+ // recognized-but-unsupported operation.
3998
+ for malformed in [
3999
+ serde_json::json!("yes"),
4000
+ serde_json::json!(true),
4001
+ serde_json::json!(1),
4002
+ serde_json::json!(["needs_human"]),
4003
+ ] {
4004
+ let failure = execute_query(
4005
+ &db,
4006
+ &schema,
4007
+ &context,
4008
+ &CanonicalQuery {
4009
+ semantic: Some(malformed),
4010
+ ..plain.clone()
4011
+ },
4012
+ None,
4013
+ )
4014
+ .expect_err("a malformed semantic value is invalid");
4015
+ assert_eq!(failure.code, "INVALID_QUERY");
4016
+ }
4017
+
4018
+ // The clause is refused ahead of every other validation, so a query
4019
+ // that is also otherwise invalid still fails on semantic intent.
4020
+ let failure = execute_query(
4021
+ &db,
4022
+ &schema,
4023
+ &context,
4024
+ &CanonicalQuery {
4025
+ semantic: Some(serde_json::json!({ "decision": "needs_human" })),
4026
+ limit: Some(0),
4027
+ ..plain.clone()
4028
+ },
4029
+ None,
4030
+ )
4031
+ .unwrap_err();
4032
+ assert_eq!(failure.code, "SEMANTIC_UNSUPPORTED");
4033
+
4034
+ // Refusal reads nothing, judges nothing, and writes nothing: no
4035
+ // judgment collection appears and the state is what it was.
4036
+ assert_eq!(db.state_rows().unwrap().len(), state_before);
4037
+ let judgments: Vec<Value> = db
4038
+ .capability(crate::semantic_decision::DEFAULT_DECISION_COLLECTION)
4039
+ .find(|_: &Value| true)
4040
+ .unwrap();
4041
+ assert!(judgments.is_empty());
4042
+
4043
+ // The same query without the clause answers as before.
4044
+ let page = execute_query(&db, &schema, &context, &plain, None).unwrap();
4045
+ assert_eq!(page.records.len(), 1);
4046
+ }
4047
+
4048
+ /// Over the wire, `semantic` is carried into the query rather than lost
4049
+ /// as an unknown key, `null` means absent, and an unrelated unknown key
4050
+ /// keeps its existing behavior of being ignored: only semantic intent is
4051
+ /// recognized, because only semantic intent changes what a query means.
4052
+ #[test]
4053
+ fn semantic_clause_survives_deserialization_and_changes_query_identity() {
4054
+ let plain: CanonicalQuery = serde_json::from_value(serde_json::json!({
4055
+ "collection": "incidents",
4056
+ "limit": 10,
4057
+ }))
4058
+ .unwrap();
4059
+ assert!(plain.semantic.is_none());
4060
+ let with_clause: CanonicalQuery = serde_json::from_value(serde_json::json!({
4061
+ "collection": "incidents",
4062
+ "limit": 10,
4063
+ "semantic": { "decision": "needs_human" },
4064
+ }))
4065
+ .unwrap();
4066
+ assert_eq!(
4067
+ with_clause.semantic,
4068
+ Some(serde_json::json!({ "decision": "needs_human" }))
4069
+ );
4070
+ assert_ne!(
4071
+ query_hash(&plain).unwrap(),
4072
+ query_hash(&with_clause).unwrap(),
4073
+ "a semantic clause is part of what the query is"
4074
+ );
4075
+ let malformed: CanonicalQuery = serde_json::from_value(serde_json::json!({
4076
+ "collection": "incidents",
4077
+ "semantic": "yes",
4078
+ }))
4079
+ .unwrap();
4080
+ assert_eq!(malformed.semantic, Some(Value::String("yes".into())));
4081
+ let null: CanonicalQuery = serde_json::from_value(serde_json::json!({
4082
+ "collection": "incidents",
4083
+ "semantic": null,
4084
+ }))
4085
+ .unwrap();
4086
+ assert!(null.semantic.is_none());
4087
+ let unrelated: CanonicalQuery = serde_json::from_value(serde_json::json!({
4088
+ "collection": "incidents",
4089
+ "limit": 10,
4090
+ "refill": true,
4091
+ }))
4092
+ .unwrap();
4093
+ assert_eq!(query_hash(&unrelated).unwrap(), query_hash(&plain).unwrap());
4094
+ // Serialization of a query without a clause is unchanged, so every
4095
+ // existing query hash and cursor binding is unchanged.
4096
+ assert!(!serde_json::to_string(&plain).unwrap().contains("semantic"));
4097
+ }
4098
+
3891
4099
  #[test]
3892
4100
  fn scoped_index_work_is_independent_of_unrelated_growth() {
3893
4101
  fn examined(unrelated: usize) -> usize {
@@ -3941,6 +4149,7 @@ mod tests {
3941
4149
  group_by: vec![],
3942
4150
  aggregates: vec![],
3943
4151
  references: vec![],
4152
+ semantic: None,
3944
4153
  },
3945
4154
  None,
3946
4155
  )
@@ -4002,6 +4211,7 @@ mod tests {
4002
4211
  group_by: vec![],
4003
4212
  aggregates: vec![],
4004
4213
  references: vec![],
4214
+ semantic: None,
4005
4215
  },
4006
4216
  None,
4007
4217
  )
@@ -4143,6 +4353,7 @@ mod tests {
4143
4353
  group_by: vec![],
4144
4354
  aggregates: vec![],
4145
4355
  references: vec![],
4356
+ semantic: None,
4146
4357
  },
4147
4358
  None,
4148
4359
  )
@@ -4208,6 +4419,7 @@ mod tests {
4208
4419
  group_by: vec![],
4209
4420
  aggregates: vec![],
4210
4421
  references: vec![],
4422
+ semantic: None,
4211
4423
  };
4212
4424
 
4213
4425
  let result = execute_query(&db, &schema, &context, &query, None);
@@ -4246,6 +4458,7 @@ mod tests {
4246
4458
  group_by: vec![],
4247
4459
  aggregates: vec![],
4248
4460
  references: vec![],
4461
+ semantic: None,
4249
4462
  };
4250
4463
 
4251
4464
  let result = execute_query(&db, &schema, &context, &query, None);
@@ -4357,6 +4570,7 @@ mod tests {
4357
4570
  group_by: vec![],
4358
4571
  aggregates: vec![],
4359
4572
  references: vec![],
4573
+ semantic: None,
4360
4574
  };
4361
4575
 
4362
4576
  let result = execute_query(&db, &schema, &context, &query, None);
@@ -4456,6 +4670,7 @@ mod tests {
4456
4670
  group_by: vec![],
4457
4671
  aggregates: vec![],
4458
4672
  references: vec![],
4673
+ semantic: None,
4459
4674
  };
4460
4675
 
4461
4676
  let result = execute_query(&db, &schema, &context, &query, None);
@@ -4587,6 +4802,7 @@ mod tests {
4587
4802
  group_by: vec![],
4588
4803
  aggregates: vec![],
4589
4804
  references: vec![],
4805
+ semantic: None,
4590
4806
  };
4591
4807
 
4592
4808
  let verify_result =
@@ -4705,6 +4921,7 @@ mod tests {
4705
4921
  group_by: vec![],
4706
4922
  aggregates: vec![],
4707
4923
  references: vec![],
4924
+ semantic: None,
4708
4925
  };
4709
4926
 
4710
4927
  let result = execute_query(&db, &schema, &read_context, &query, None);
@@ -4743,6 +4960,7 @@ mod tests {
4743
4960
  group_by: vec![],
4744
4961
  aggregates: vec![],
4745
4962
  references: vec![],
4963
+ semantic: None,
4746
4964
  };
4747
4965
 
4748
4966
  let result = execute_query(&db, &schema, &read_context, &query, None);
@@ -5009,6 +5227,7 @@ mod tests {
5009
5227
  group_by: vec![],
5010
5228
  aggregates: vec![],
5011
5229
  references: vec![],
5230
+ semantic: None,
5012
5231
  };
5013
5232
 
5014
5233
  let alice_result = execute_query(
@@ -5104,6 +5323,7 @@ mod tests {
5104
5323
  group_by: vec![],
5105
5324
  aggregates: vec![],
5106
5325
  references: vec![],
5326
+ semantic: None,
5107
5327
  };
5108
5328
 
5109
5329
  let result = execute_query(
@@ -5191,6 +5411,7 @@ mod tests {
5191
5411
  group_by: vec![],
5192
5412
  aggregates: vec![],
5193
5413
  references: vec![],
5414
+ semantic: None,
5194
5415
  };
5195
5416
 
5196
5417
  let result = execute_query(
@@ -5280,6 +5501,7 @@ mod tests {
5280
5501
  group_by: vec![],
5281
5502
  aggregates: vec![],
5282
5503
  references: vec![],
5504
+ semantic: None,
5283
5505
  };
5284
5506
 
5285
5507
  let result = execute_query(
@@ -336,6 +336,7 @@ fn pr7_phase3_self_authorization_proof() -> Result<(), Box<dyn std::error::Error
336
336
  group_by: vec![],
337
337
  aggregates: vec![],
338
338
  references: vec![],
339
+ semantic: None,
339
340
  };
340
341
 
341
342
  // We'll need to manually filter since the policy evaluation isn't automatic in tests
@@ -361,6 +361,7 @@ fn saas_invitation_lifecycle_phase_3_capability_gap_detection(
361
361
  group_by: vec![],
362
362
  aggregates: vec![],
363
363
  references: vec![],
364
+ semantic: None,
364
365
  };
365
366
 
366
367
  let result =