@feltdb/core 0.7.4 → 0.8.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/README.md +25 -0
- package/dist/application-development.d.ts +3 -1
- package/dist/application-development.d.ts.map +1 -1
- package/dist/application-development.js +16 -0
- package/dist/application-manifest.d.ts +1 -0
- package/dist/application-manifest.d.ts.map +1 -1
- package/dist/canonical-application.d.ts +1 -0
- package/dist/canonical-application.d.ts.map +1 -1
- package/dist/canonical-application.js +3 -2
- package/dist/cli/cli.js +1 -1
- package/dist/cli/commands.js +225 -42
- package/dist/cli/index.js +1 -1
- package/dist/cli/managed-environment.js +6 -0
- package/dist/cli/managed-project.js +6 -0
- package/dist/cli/proposal-client.js +12 -3
- package/dist/cli/publish-engine.js +7 -4
- package/dist/cli/source-sync.js +47 -3
- package/dist/create/cli.js +10 -1
- package/dist/create/create.js +10 -12
- package/dist/create/managed-account.js +71 -7
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +10 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +20 -2
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +12 -24
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +11 -3
- package/dist/create/server-source/crates/feltdb-server/src/authenticated_principal.rs +0 -1
- package/dist/create/server-source/crates/feltdb-server/src/certification_harness.rs +23 -22
- package/dist/create/server-source/crates/feltdb-server/src/delegation_token.rs +5 -15
- package/dist/create/server-source/crates/feltdb-server/src/durable_operations.rs +13 -28
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +79 -14
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +28 -7
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +5 -5
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +1777 -259
- package/dist/create/server-source/crates/feltdb-server/src/managed_diagnostics.rs +10 -30
- package/dist/create/server-source/crates/feltdb-server/src/membership_policy.rs +12 -4
- package/dist/create/server-source/crates/feltdb-server/src/request_telemetry.rs +8 -6
- package/dist/create/server-source/crates/feltdb-server/src/snapshot_cursor.rs +10 -14
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +183 -21
- package/dist/create/server-source/crates/feltdb-server/src/tenant_policies.rs +9 -21
- package/dist/create/server-source/crates/feltdb-server/src/transaction_idempotency.rs +5 -3
- package/dist/create/server-source/crates/feltdb-server/src/transaction_recovery.rs +9 -8
- package/dist/create/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +1 -4
- package/dist/create/template/default-project/feltdb.flow +5 -0
- package/dist/flowspec.d.ts +1 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +46 -12
- package/dist/http-client.d.ts +135 -14
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +79 -31
- package/dist/index-core.d.ts +2 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +2 -0
- package/dist/module-intent.d.ts +37 -0
- package/dist/module-intent.d.ts.map +1 -0
- package/dist/module-intent.js +81 -0
- package/dist/module.d.ts +76 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +61 -0
- package/dist/proposal.d.ts +80 -4
- package/dist/proposal.d.ts.map +1 -1
- package/dist/proposal.js +132 -5
- package/dist/studio/app.d.ts +3 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/AskFeltDB.d.ts +2 -1
- package/dist/studio/components/AskFeltDB.d.ts.map +1 -1
- package/dist/studio/components/ContractInspector.d.ts +7 -0
- package/dist/studio/components/ContractInspector.d.ts.map +1 -0
- package/dist/studio/components/ManagedInstancePanel.d.ts.map +1 -1
- package/dist/studio/components/ManagedInstancePanel.js +1 -5
- package/dist/studio/components/ModuleExplorer.d.ts +6 -0
- package/dist/studio/components/ModuleExplorer.d.ts.map +1 -0
- package/dist/studio/components/ProposalBrowser.d.ts +10 -1
- package/dist/studio/components/ProposalBrowser.d.ts.map +1 -1
- package/dist/studio/components/StateExplorer.d.ts +6 -7
- package/dist/studio/components/StateExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.d.ts +2 -0
- package/dist/studio/components/index.d.ts.map +1 -1
- package/dist/studio/components/index.js +4 -4
- package/dist/studio/components-CNSNrmA9.js +2787 -0
- package/dist/studio/index.js +193 -147
- package/dist/studio/studio.css +1 -1
- package/dist/studio/utils/index.d.ts +2 -0
- package/dist/studio/utils/index.d.ts.map +1 -1
- package/dist/studio/utils/index.js +14 -12
- package/dist/studio/utils/managed-instance.d.ts +0 -4
- package/dist/studio/utils/managed-instance.d.ts.map +1 -1
- package/dist/studio/utils/managed-instance.js +0 -4
- package/dist/studio/utils/proposal-api.d.ts +53 -0
- package/dist/studio/utils/proposal-api.d.ts.map +1 -0
- package/dist/studio/utils/proposal-api.js +91 -0
- package/dist/studio/utils/service-api.d.ts +81 -0
- package/dist/studio/utils/service-api.d.ts.map +1 -0
- package/dist/studio/utils/service-api.js +51 -0
- package/dist/studio-app/assets/{dist-DA1MYSzp.js → dist-CCOk39Uc.js} +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-B-Ccgccb.js → feltdb_wasm-CSB6KVgw.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-Dd1fnO9U.wasm +0 -0
- package/dist/studio-app/assets/index-Bncji3aN.js +43 -0
- package/dist/studio-app/assets/{index-BRrmvaKw.css → index-yazaWMUN.css} +1 -1
- package/dist/studio-app/index.html +2 -2
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -1
- package/dist/workspace/local-development-authority.js +64 -3
- package/package.json +1 -1
- package/dist/studio/components-Bm4sjhcX.js +0 -2162
- package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
- package/dist/studio-app/assets/index-CEIqowCh.js +0 -42
|
@@ -114,10 +114,7 @@ impl DurableOperationStore {
|
|
|
114
114
|
.map_err(|_| "store lock poisoned".to_string())?;
|
|
115
115
|
|
|
116
116
|
if ops.contains_key(&operation_id) {
|
|
117
|
-
return Err(format!(
|
|
118
|
-
"operation already exists: {}",
|
|
119
|
-
operation_id
|
|
120
|
-
));
|
|
117
|
+
return Err(format!("operation already exists: {}", operation_id));
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
let now = now();
|
|
@@ -158,7 +155,8 @@ impl DurableOperationStore {
|
|
|
158
155
|
.ok_or(format!("operation not found: {}", operation_id))?;
|
|
159
156
|
|
|
160
157
|
// Can only claim Pending or Unknown operations
|
|
161
|
-
if operation.state != OperationState::Pending && operation.state != OperationState::Unknown
|
|
158
|
+
if operation.state != OperationState::Pending && operation.state != OperationState::Unknown
|
|
159
|
+
{
|
|
162
160
|
return Err(format!(
|
|
163
161
|
"cannot claim operation in state {:?}",
|
|
164
162
|
operation.state
|
|
@@ -339,7 +337,8 @@ impl DurableOperationStore {
|
|
|
339
337
|
}
|
|
340
338
|
|
|
341
339
|
// Must be in Claimed or Running state
|
|
342
|
-
if operation.state != OperationState::Claimed && operation.state != OperationState::Running
|
|
340
|
+
if operation.state != OperationState::Claimed && operation.state != OperationState::Running
|
|
341
|
+
{
|
|
343
342
|
return Err(format!(
|
|
344
343
|
"cannot fail operation in state {:?}",
|
|
345
344
|
operation.state
|
|
@@ -356,10 +355,7 @@ impl DurableOperationStore {
|
|
|
356
355
|
}
|
|
357
356
|
|
|
358
357
|
/// Marks an operation as Unknown (when lease expires).
|
|
359
|
-
pub fn mark_unknown(
|
|
360
|
-
&self,
|
|
361
|
-
operation_id: &str,
|
|
362
|
-
) -> Result<DurableOperation, String> {
|
|
358
|
+
pub fn mark_unknown(&self, operation_id: &str) -> Result<DurableOperation, String> {
|
|
363
359
|
let mut ops = self
|
|
364
360
|
.operations
|
|
365
361
|
.write()
|
|
@@ -372,7 +368,8 @@ impl DurableOperationStore {
|
|
|
372
368
|
let now = now();
|
|
373
369
|
|
|
374
370
|
// Can only mark Unknown if in Claimed or Running state
|
|
375
|
-
if operation.state != OperationState::Claimed && operation.state != OperationState::Running
|
|
371
|
+
if operation.state != OperationState::Claimed && operation.state != OperationState::Running
|
|
372
|
+
{
|
|
376
373
|
return Err(format!(
|
|
377
374
|
"cannot mark Unknown operation in state {:?}",
|
|
378
375
|
operation.state
|
|
@@ -484,9 +481,7 @@ impl DurableOperationStore {
|
|
|
484
481
|
.map_err(|_| "store lock poisoned".to_string())?;
|
|
485
482
|
|
|
486
483
|
let before_len = ops.len();
|
|
487
|
-
ops.retain(|_, op|
|
|
488
|
-
!(op.state == OperationState::Completed && op.updated_at < cutoff_time)
|
|
489
|
-
});
|
|
484
|
+
ops.retain(|_, op| !(op.state == OperationState::Completed && op.updated_at < cutoff_time));
|
|
490
485
|
let after_len = ops.len();
|
|
491
486
|
|
|
492
487
|
Ok(before_len - after_len)
|
|
@@ -755,9 +750,7 @@ mod tests {
|
|
|
755
750
|
.claim_operation("op_123", "worker_1".into(), 60)
|
|
756
751
|
.expect("claim failed");
|
|
757
752
|
|
|
758
|
-
let unknown = store
|
|
759
|
-
.mark_unknown("op_123")
|
|
760
|
-
.expect("mark unknown failed");
|
|
753
|
+
let unknown = store.mark_unknown("op_123").expect("mark unknown failed");
|
|
761
754
|
assert_eq!(unknown.state, OperationState::Unknown);
|
|
762
755
|
|
|
763
756
|
let reconciled = store
|
|
@@ -826,9 +819,7 @@ mod tests {
|
|
|
826
819
|
)
|
|
827
820
|
.expect("create failed");
|
|
828
821
|
|
|
829
|
-
let ops = store
|
|
830
|
-
.list_operations("tenant_prod")
|
|
831
|
-
.expect("list failed");
|
|
822
|
+
let ops = store.list_operations("tenant_prod").expect("list failed");
|
|
832
823
|
assert_eq!(ops.len(), 2);
|
|
833
824
|
}
|
|
834
825
|
|
|
@@ -915,9 +906,7 @@ mod tests {
|
|
|
915
906
|
.expect("cleanup failed");
|
|
916
907
|
assert_eq!(deleted, 1);
|
|
917
908
|
|
|
918
|
-
let ops = store
|
|
919
|
-
.list_operations("tenant_prod")
|
|
920
|
-
.expect("list failed");
|
|
909
|
+
let ops = store.list_operations("tenant_prod").expect("list failed");
|
|
921
910
|
assert_eq!(ops.len(), 1);
|
|
922
911
|
assert_eq!(ops[0].operation_id, "op_pending");
|
|
923
912
|
}
|
|
@@ -978,11 +967,7 @@ mod tests {
|
|
|
978
967
|
.expect("start failed");
|
|
979
968
|
|
|
980
969
|
store
|
|
981
|
-
.complete_operation(
|
|
982
|
-
"op_123",
|
|
983
|
-
"worker_1",
|
|
984
|
-
serde_json::json!({"result": "done"}),
|
|
985
|
-
)
|
|
970
|
+
.complete_operation("op_123", "worker_1", serde_json::json!({"result": "done"}))
|
|
986
971
|
.expect("complete failed");
|
|
987
972
|
|
|
988
973
|
let final_op = store.get_operation("op_123").expect("get failed");
|
|
@@ -137,35 +137,93 @@ impl IdentityStore {
|
|
|
137
137
|
.identities
|
|
138
138
|
.clone())
|
|
139
139
|
}
|
|
140
|
-
pub fn sign_up(
|
|
140
|
+
pub fn sign_up(
|
|
141
|
+
&self,
|
|
142
|
+
email: &str,
|
|
143
|
+
password: &str,
|
|
144
|
+
display_name: Option<String>,
|
|
145
|
+
) -> Result<(Identity, IdentitySession, String), String> {
|
|
141
146
|
let normalized = email.trim().to_lowercase();
|
|
142
|
-
if normalized.is_empty() || password.len() < 8 {
|
|
143
|
-
|
|
147
|
+
if normalized.is_empty() || password.len() < 8 {
|
|
148
|
+
return Err("email and a password of at least 8 characters are required".into());
|
|
149
|
+
}
|
|
150
|
+
if self
|
|
151
|
+
.state
|
|
152
|
+
.read()
|
|
153
|
+
.map_err(|_| "identity lock poisoned")?
|
|
154
|
+
.identities
|
|
155
|
+
.iter()
|
|
156
|
+
.any(|value| value.email.as_deref() == Some(&normalized))
|
|
157
|
+
{
|
|
144
158
|
return Err("identity already exists".into());
|
|
145
159
|
}
|
|
146
|
-
let mut identity = self.normalize(
|
|
160
|
+
let mut identity = self.normalize(
|
|
161
|
+
"password",
|
|
162
|
+
&normalized,
|
|
163
|
+
Some(normalized.clone()),
|
|
164
|
+
display_name,
|
|
165
|
+
serde_json::json!({}),
|
|
166
|
+
)?;
|
|
147
167
|
identity.status = IdentityStatus::Active;
|
|
148
168
|
let mut state = self.state.write().map_err(|_| "identity lock poisoned")?;
|
|
149
|
-
if let Some(stored) = state
|
|
150
|
-
|
|
169
|
+
if let Some(stored) = state
|
|
170
|
+
.identities
|
|
171
|
+
.iter_mut()
|
|
172
|
+
.find(|value| value.identity_id == identity.identity_id)
|
|
173
|
+
{
|
|
174
|
+
stored.status = IdentityStatus::Active;
|
|
175
|
+
}
|
|
176
|
+
state
|
|
177
|
+
.password_hashes
|
|
178
|
+
.insert(identity.subject_id.clone(), Self::hash_password(password)?);
|
|
151
179
|
self.persist(&state)?;
|
|
152
180
|
drop(state);
|
|
153
181
|
let (session, token) = self.issue_session(&identity, "password", None, 15 * 60)?;
|
|
154
182
|
Ok((identity, session, token))
|
|
155
183
|
}
|
|
156
|
-
pub fn sign_in(
|
|
184
|
+
pub fn sign_in(
|
|
185
|
+
&self,
|
|
186
|
+
email: &str,
|
|
187
|
+
password: &str,
|
|
188
|
+
) -> Result<(Identity, IdentitySession, String), String> {
|
|
157
189
|
let normalized = email.trim().to_lowercase();
|
|
158
190
|
let state = self.state.read().map_err(|_| "identity lock poisoned")?;
|
|
159
|
-
let identity = state
|
|
160
|
-
|
|
191
|
+
let identity = state
|
|
192
|
+
.identities
|
|
193
|
+
.iter()
|
|
194
|
+
.find(|value| {
|
|
195
|
+
value.email.as_deref() == Some(&normalized)
|
|
196
|
+
&& value.status == IdentityStatus::Active
|
|
197
|
+
})
|
|
198
|
+
.cloned()
|
|
199
|
+
.ok_or("invalid credentials")?;
|
|
200
|
+
let valid = state
|
|
201
|
+
.password_hashes
|
|
202
|
+
.get(&identity.subject_id)
|
|
203
|
+
.is_some_and(|hash| Self::verify_password(hash, password));
|
|
161
204
|
drop(state);
|
|
162
|
-
if !valid {
|
|
205
|
+
if !valid {
|
|
206
|
+
return Err("invalid credentials".into());
|
|
207
|
+
}
|
|
163
208
|
let (session, token) = self.issue_session(&identity, "password", None, 15 * 60)?;
|
|
164
209
|
Ok((identity, session, token))
|
|
165
210
|
}
|
|
166
211
|
pub fn authenticate_session(&self, token: &str) -> Option<IdentitySession> {
|
|
167
212
|
let current = now();
|
|
168
|
-
self.state
|
|
213
|
+
self.state
|
|
214
|
+
.read()
|
|
215
|
+
.ok()?
|
|
216
|
+
.sessions
|
|
217
|
+
.iter()
|
|
218
|
+
.find(|value| {
|
|
219
|
+
value
|
|
220
|
+
.refresh_hash
|
|
221
|
+
.as_deref()
|
|
222
|
+
.is_some_and(|hash| Self::verify_password(hash, token))
|
|
223
|
+
&& value.status == "active"
|
|
224
|
+
&& value.expires_at > current
|
|
225
|
+
})
|
|
226
|
+
.cloned()
|
|
169
227
|
}
|
|
170
228
|
pub fn sessions(&self, subject: &str) -> Result<Vec<IdentitySession>, String> {
|
|
171
229
|
Ok(self
|
|
@@ -282,11 +340,18 @@ mod tests {
|
|
|
282
340
|
fn actor_tokens_are_short_lived_hashed_and_revocable() {
|
|
283
341
|
let p = std::env::temp_dir().join(format!("identity-session-{}.json", id("test")));
|
|
284
342
|
let store = IdentityStore::load(&p).unwrap();
|
|
285
|
-
let (identity, session, token) = store
|
|
343
|
+
let (identity, session, token) = store
|
|
344
|
+
.sign_up("actor@example.com", "correct horse", Some("Actor".into()))
|
|
345
|
+
.unwrap();
|
|
286
346
|
assert!(session.expires_at - session.issued_at <= 15 * 60);
|
|
287
347
|
assert_ne!(session.refresh_hash.as_deref(), Some(token.as_str()));
|
|
288
|
-
assert_eq!(
|
|
289
|
-
|
|
348
|
+
assert_eq!(
|
|
349
|
+
store.authenticate_session(&token).unwrap().subject_id,
|
|
350
|
+
identity.subject_id
|
|
351
|
+
);
|
|
352
|
+
store
|
|
353
|
+
.revoke_session(&identity.subject_id, &session.session_id, "test")
|
|
354
|
+
.unwrap();
|
|
290
355
|
assert!(store.authenticate_session(&token).is_none());
|
|
291
356
|
let _ = std::fs::remove_file(p);
|
|
292
357
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
use axum::{
|
|
2
|
-
extract::{Path, State},
|
|
2
|
+
extract::{Extension, Path, State},
|
|
3
3
|
http::StatusCode,
|
|
4
4
|
Json,
|
|
5
5
|
};
|
|
6
6
|
use serde::{Deserialize, Serialize};
|
|
7
7
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
8
8
|
|
|
9
|
-
use crate::{
|
|
9
|
+
use crate::{
|
|
10
|
+
app_state::AppState,
|
|
11
|
+
auth::{ApiKeySummary, Principal},
|
|
12
|
+
};
|
|
10
13
|
|
|
11
14
|
#[derive(Debug, Serialize)]
|
|
12
15
|
pub struct KeyListResponse {
|
|
@@ -18,6 +21,7 @@ pub struct CreateKeyRequest {
|
|
|
18
21
|
pub id: Option<String>,
|
|
19
22
|
pub namespace: String,
|
|
20
23
|
pub scopes: Vec<String>,
|
|
24
|
+
pub application_id: Option<String>,
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
#[derive(Debug, Serialize)]
|
|
@@ -38,6 +42,7 @@ pub async fn list_keys(
|
|
|
38
42
|
|
|
39
43
|
pub async fn create_key(
|
|
40
44
|
State(state): State<AppState>,
|
|
45
|
+
Extension(principal): Extension<Principal>,
|
|
41
46
|
Json(req): Json<CreateKeyRequest>,
|
|
42
47
|
) -> Result<(StatusCode, Json<CreateKeyResponse>), (StatusCode, String)> {
|
|
43
48
|
let timestamp = SystemTime::now()
|
|
@@ -46,15 +51,31 @@ pub async fn create_key(
|
|
|
46
51
|
.as_secs();
|
|
47
52
|
|
|
48
53
|
let name = req.id.unwrap_or_else(|| format!("key-{}", timestamp));
|
|
54
|
+
let scopes = req.scopes;
|
|
55
|
+
let mut authentication_namespaces = vec![req.namespace];
|
|
56
|
+
if !authentication_namespaces
|
|
57
|
+
.iter()
|
|
58
|
+
.any(|value| value == state.namespace.as_ref())
|
|
59
|
+
{
|
|
60
|
+
authentication_namespaces.push(state.namespace.to_string());
|
|
61
|
+
}
|
|
49
62
|
let key = state
|
|
50
63
|
.keys
|
|
51
|
-
.create(
|
|
52
|
-
name,
|
|
53
|
-
req.scopes,
|
|
54
|
-
vec![req.namespace],
|
|
55
|
-
)
|
|
64
|
+
.create(name, scopes.clone(), authentication_namespaces)
|
|
56
65
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
|
|
57
66
|
|
|
67
|
+
if let Some(application_id) = req.application_id {
|
|
68
|
+
if let Err(error) = state.tenancy.register_application_service(
|
|
69
|
+
&principal.key_id,
|
|
70
|
+
&key.id,
|
|
71
|
+
&application_id,
|
|
72
|
+
scopes,
|
|
73
|
+
) {
|
|
74
|
+
let _ = state.keys.revoke(&key.id);
|
|
75
|
+
return Err((StatusCode::FORBIDDEN, error));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
Ok((
|
|
59
80
|
StatusCode::CREATED,
|
|
60
81
|
Json(CreateKeyResponse {
|
|
@@ -6,17 +6,17 @@ pub mod auth;
|
|
|
6
6
|
pub mod authenticated_principal;
|
|
7
7
|
pub mod authorization;
|
|
8
8
|
pub mod backup;
|
|
9
|
-
pub mod certification_harness;
|
|
10
|
-
pub mod delegation_token;
|
|
11
|
-
pub mod durable_operations;
|
|
12
|
-
pub mod key_management;
|
|
13
9
|
pub mod causal;
|
|
14
10
|
pub mod certification;
|
|
11
|
+
pub mod certification_harness;
|
|
15
12
|
pub mod clock;
|
|
16
13
|
pub mod cluster;
|
|
17
14
|
pub mod connections;
|
|
18
15
|
pub mod content;
|
|
16
|
+
pub mod delegation_token;
|
|
17
|
+
pub mod durable_operations;
|
|
19
18
|
pub mod identity;
|
|
19
|
+
pub mod key_management;
|
|
20
20
|
pub mod key_provider;
|
|
21
21
|
pub mod leases;
|
|
22
22
|
pub mod managed_diagnostics;
|
|
@@ -29,8 +29,8 @@ pub mod releases;
|
|
|
29
29
|
pub mod request_telemetry;
|
|
30
30
|
pub mod sessions;
|
|
31
31
|
pub mod snapshot_cursor;
|
|
32
|
-
pub mod tenant_policies;
|
|
33
32
|
pub mod tenancy;
|
|
33
|
+
pub mod tenant_policies;
|
|
34
34
|
pub mod transaction_idempotency;
|
|
35
35
|
pub mod transaction_recovery;
|
|
36
36
|
pub mod versions;
|