@feltdb/core 0.8.1 → 0.8.3
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/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +19 -0
- package/dist/create/server-source/crates/feltdb/src/authority.rs +349 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +32 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +19 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +444 -189
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +244 -51
- package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +297 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +41 -11
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +8 -3
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +1164 -225
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +134 -0
- package/dist/file-db.d.ts +8 -15
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +234 -130
- package/dist/http-db.d.ts +3 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +2 -1
- package/dist/state-contract.d.ts +4 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/state-contract.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-bIqcRzAr.js → feltdb_wasm-DB8cX151.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-ClhDHp0S.wasm +0 -0
- package/dist/studio-app/assets/{index-XGYdlElN.js → index-B0k4UAlI.js} +1 -1
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-BE79okwX.wasm +0 -0
|
@@ -41,6 +41,41 @@ pub struct Application {
|
|
|
41
41
|
pub active_revision: Option<String>,
|
|
42
42
|
#[serde(default)]
|
|
43
43
|
pub environment_revisions: std::collections::BTreeMap<String, String>,
|
|
44
|
+
#[serde(default)]
|
|
45
|
+
pub qualification: Option<QualificationMarker>,
|
|
46
|
+
}
|
|
47
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
48
|
+
pub struct QualificationMarker {
|
|
49
|
+
pub purpose: String,
|
|
50
|
+
pub disposable: bool,
|
|
51
|
+
pub environment: String,
|
|
52
|
+
pub proof_run_id: String,
|
|
53
|
+
pub expires_at: u64,
|
|
54
|
+
}
|
|
55
|
+
impl QualificationMarker {
|
|
56
|
+
fn valid_for_creation(&self, timestamp: u64) -> bool {
|
|
57
|
+
self.purpose == "qualification"
|
|
58
|
+
&& self.disposable
|
|
59
|
+
&& self.environment == "qualification"
|
|
60
|
+
&& !self.proof_run_id.trim().is_empty()
|
|
61
|
+
&& self.expires_at > timestamp
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn authorize_destruction(&self, proof_run_id: &str, timestamp: u64) -> Result<(), String> {
|
|
65
|
+
if self.purpose != "qualification" || !self.disposable {
|
|
66
|
+
return Err("refusing destructive qualification: application is not marked disposable".into());
|
|
67
|
+
}
|
|
68
|
+
if self.environment != "qualification" {
|
|
69
|
+
return Err("refusing destructive qualification: environment is not qualification".into());
|
|
70
|
+
}
|
|
71
|
+
if self.proof_run_id.trim().is_empty() || proof_run_id.trim().is_empty() || self.proof_run_id != proof_run_id {
|
|
72
|
+
return Err("refusing destructive qualification: proof run does not match".into());
|
|
73
|
+
}
|
|
74
|
+
if self.expires_at <= timestamp {
|
|
75
|
+
return Err("refusing destructive qualification: disposable marker has expired".into());
|
|
76
|
+
}
|
|
77
|
+
Ok(())
|
|
78
|
+
}
|
|
44
79
|
}
|
|
45
80
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
46
81
|
pub struct TenantMembership {
|
|
@@ -765,10 +800,25 @@ impl TenancyStore {
|
|
|
765
800
|
actor: &str,
|
|
766
801
|
tenant_id: &str,
|
|
767
802
|
name: &str,
|
|
803
|
+
) -> Result<Application, String> {
|
|
804
|
+
self.create_application_with_qualification(actor, tenant_id, name, None)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
pub fn create_application_with_qualification(
|
|
808
|
+
&self,
|
|
809
|
+
actor: &str,
|
|
810
|
+
tenant_id: &str,
|
|
811
|
+
name: &str,
|
|
812
|
+
qualification: Option<QualificationMarker>,
|
|
768
813
|
) -> Result<Application, String> {
|
|
769
814
|
if !self.authorize_tenant(actor, tenant_id, "applications:write") {
|
|
770
815
|
return Err("forbidden".into());
|
|
771
816
|
}
|
|
817
|
+
if let Some(marker) = qualification.as_ref() {
|
|
818
|
+
if !marker.valid_for_creation(now()) {
|
|
819
|
+
return Err("invalid disposable qualification marker".into());
|
|
820
|
+
}
|
|
821
|
+
}
|
|
772
822
|
let mut records = self
|
|
773
823
|
.records
|
|
774
824
|
.write()
|
|
@@ -794,6 +844,7 @@ impl TenancyStore {
|
|
|
794
844
|
status: application_status(),
|
|
795
845
|
active_revision: None,
|
|
796
846
|
environment_revisions: Default::default(),
|
|
847
|
+
qualification,
|
|
797
848
|
};
|
|
798
849
|
records.application_memberships.push(ApplicationMembership {
|
|
799
850
|
application_id: application.id.clone(),
|
|
@@ -816,6 +867,22 @@ impl TenancyStore {
|
|
|
816
867
|
Ok(application)
|
|
817
868
|
}
|
|
818
869
|
|
|
870
|
+
pub fn delete_qualification_application(
|
|
871
|
+
&self,
|
|
872
|
+
actor: &str,
|
|
873
|
+
application_id: &str,
|
|
874
|
+
proof_run_id: &str,
|
|
875
|
+
) -> Result<(), String> {
|
|
876
|
+
let application = self
|
|
877
|
+
.application_for(actor, application_id)
|
|
878
|
+
.ok_or_else(|| "application not found".to_string())?;
|
|
879
|
+
let marker = application.qualification.ok_or_else(|| {
|
|
880
|
+
"refusing destructive qualification: application is not marked disposable".to_string()
|
|
881
|
+
})?;
|
|
882
|
+
marker.authorize_destruction(proof_run_id, now())?;
|
|
883
|
+
self.delete_application(actor, application_id)
|
|
884
|
+
}
|
|
885
|
+
|
|
819
886
|
pub fn delete_application(&self, actor: &str, application_id: &str) -> Result<(), String> {
|
|
820
887
|
let tenant_id = self
|
|
821
888
|
.application_tenant(application_id)
|
|
@@ -1238,6 +1305,73 @@ mod tests {
|
|
|
1238
1305
|
assert!(!store.authorize_application("user_a", &tenant_a.id, &app_b.id, "state:read"));
|
|
1239
1306
|
}
|
|
1240
1307
|
|
|
1308
|
+
#[test]
|
|
1309
|
+
fn destructive_qualification_requires_an_unexpired_matching_marker() {
|
|
1310
|
+
let valid_marker = QualificationMarker {
|
|
1311
|
+
purpose: "qualification".into(), disposable: true, environment: "qualification".into(), proof_run_id: "proof-1".into(), expires_at: now() + 300,
|
|
1312
|
+
};
|
|
1313
|
+
assert!(valid_marker.authorize_destruction("", now()).unwrap_err().contains("does not match"));
|
|
1314
|
+
assert!(valid_marker.authorize_destruction("proof-other", now()).unwrap_err().contains("does not match"));
|
|
1315
|
+
let mut wrong_environment = valid_marker.clone();
|
|
1316
|
+
wrong_environment.environment = "production".into();
|
|
1317
|
+
assert!(wrong_environment.authorize_destruction("proof-1", now()).unwrap_err().contains("environment"));
|
|
1318
|
+
let mut not_disposable = valid_marker.clone();
|
|
1319
|
+
not_disposable.disposable = false;
|
|
1320
|
+
assert!(not_disposable.authorize_destruction("proof-1", now()).unwrap_err().contains("not marked disposable"));
|
|
1321
|
+
let mut expired = valid_marker.clone();
|
|
1322
|
+
expired.expires_at = now().saturating_sub(1);
|
|
1323
|
+
assert!(expired.authorize_destruction("proof-1", now()).unwrap_err().contains("expired"));
|
|
1324
|
+
assert!(valid_marker.authorize_destruction("proof-1", now()).is_ok());
|
|
1325
|
+
|
|
1326
|
+
let store = store("qualification-guard");
|
|
1327
|
+
let tenant = store.create_tenant("owner", "Qualification").unwrap();
|
|
1328
|
+
let normal = store
|
|
1329
|
+
.create_application("owner", &tenant.id, "Normal")
|
|
1330
|
+
.unwrap();
|
|
1331
|
+
assert!(store
|
|
1332
|
+
.delete_qualification_application("owner", &normal.id, "proof-1")
|
|
1333
|
+
.unwrap_err()
|
|
1334
|
+
.contains("not marked disposable"));
|
|
1335
|
+
assert!(store
|
|
1336
|
+
.create_application_with_qualification(
|
|
1337
|
+
"owner",
|
|
1338
|
+
&tenant.id,
|
|
1339
|
+
"Expired",
|
|
1340
|
+
Some(QualificationMarker {
|
|
1341
|
+
purpose: "qualification".into(),
|
|
1342
|
+
disposable: true,
|
|
1343
|
+
environment: "qualification".into(),
|
|
1344
|
+
proof_run_id: "proof-expired".into(),
|
|
1345
|
+
expires_at: now().saturating_sub(1),
|
|
1346
|
+
}),
|
|
1347
|
+
)
|
|
1348
|
+
.unwrap_err()
|
|
1349
|
+
.contains("invalid disposable"));
|
|
1350
|
+
|
|
1351
|
+
let proof = store
|
|
1352
|
+
.create_application_with_qualification(
|
|
1353
|
+
"owner",
|
|
1354
|
+
&tenant.id,
|
|
1355
|
+
"Proof",
|
|
1356
|
+
Some(QualificationMarker {
|
|
1357
|
+
purpose: "qualification".into(),
|
|
1358
|
+
disposable: true,
|
|
1359
|
+
environment: "qualification".into(),
|
|
1360
|
+
proof_run_id: "proof-1".into(),
|
|
1361
|
+
expires_at: now() + 300,
|
|
1362
|
+
}),
|
|
1363
|
+
)
|
|
1364
|
+
.unwrap();
|
|
1365
|
+
assert!(store
|
|
1366
|
+
.delete_qualification_application("owner", &proof.id, "proof-other")
|
|
1367
|
+
.unwrap_err()
|
|
1368
|
+
.contains("does not match"));
|
|
1369
|
+
store
|
|
1370
|
+
.delete_qualification_application("owner", &proof.id, "proof-1")
|
|
1371
|
+
.unwrap();
|
|
1372
|
+
assert!(store.application_for("owner", &proof.id).is_none());
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1241
1375
|
#[test]
|
|
1242
1376
|
fn invitation_acceptance_and_revocation_change_authorization() {
|
|
1243
1377
|
let store = store("invitation");
|
package/dist/file-db.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class FileJsDb implements JsDb {
|
|
|
25
25
|
private readonly origin;
|
|
26
26
|
private readonly peers;
|
|
27
27
|
private lockFd;
|
|
28
|
+
private lockToken;
|
|
28
29
|
private readonly applied;
|
|
29
30
|
constructor(path: string);
|
|
30
31
|
private loadState;
|
|
@@ -34,6 +35,10 @@ export declare class FileJsDb implements JsDb {
|
|
|
34
35
|
private cleanupStaleLock;
|
|
35
36
|
private acquireLockAsync;
|
|
36
37
|
private releaseLockAsync;
|
|
38
|
+
private ownsLock;
|
|
39
|
+
private acquireLockSync;
|
|
40
|
+
private withMutationLock;
|
|
41
|
+
private refresh;
|
|
37
42
|
private saveState;
|
|
38
43
|
private event;
|
|
39
44
|
/**
|
|
@@ -64,21 +69,9 @@ export declare class FileJsDb implements JsDb {
|
|
|
64
69
|
instance_id(): string;
|
|
65
70
|
get_sequence(): number;
|
|
66
71
|
/**
|
|
67
|
-
* The file runtime
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* A shared directory has no serialization authority over its writers.
|
|
71
|
-
* `insert`, `update`, `delete`, and `commit_transaction` do not take the
|
|
72
|
-
* cross-process lock, and publication is a whole-snapshot rename, so two
|
|
73
|
-
* processes writing the same directory lose data. `state.sequence` therefore
|
|
74
|
-
* cannot distinguish "one writer committed 50" from "two writers committed 50
|
|
75
|
-
* each and one snapshot won" — it produces a plausible but false answer,
|
|
76
|
-
* which is worse than producing none.
|
|
77
|
-
*
|
|
78
|
-
* Measured in tools/cli/test/file-runtime-concurrency.test.mjs and analysed in
|
|
79
|
-
* docs/architecture/freshness-revision-investigation.md. Making this runtime
|
|
80
|
-
* revision-capable requires fixing lock discipline and publication together;
|
|
81
|
-
* advertising `refresh` is correct until then, not a placeholder.
|
|
72
|
+
* The file runtime uses explicit refresh validation. Cross-process mutations
|
|
73
|
+
* are serialized and reads reload the committed snapshot, but the public read
|
|
74
|
+
* contract does not yet return a scoped revision alongside its value.
|
|
82
75
|
*/
|
|
83
76
|
freshness(): FreshnessCapability;
|
|
84
77
|
audit_events(): PortableEvent[];
|
package/dist/file-db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-db.d.ts","sourceRoot":"","sources":["../src/file-db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EACV,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EAG1B,MAAM,0BAA0B,CAAC;AAYlC,OAAO,KAAK,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAkB1F,UAAU,QAAQ;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AA4BrK,qBAAa,QAAS,YAAW,IAAI;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,IAAI,EAAE,MAAM;IAmCxB,OAAO,CAAC,SAAS;IAkCjB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"file-db.d.ts","sourceRoot":"","sources":["../src/file-db.ts"],"names":[],"mappings":"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"}
|