@feltdb/core 0.8.4 → 0.8.5
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/Cargo.lock +165 -0
- package/dist/create/server-source/Cargo.toml +9 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
- package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
- package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
- package/dist/create/server-source/crates/feltdb/src/lib.rs +507 -6
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
- package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +326 -6
- package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-DaNwCLRX.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
- package/dist/studio-app/assets/{index-DwgNAIIX.js → index-j8IlhNqJ.js} +1 -1
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workload.d.ts +2 -1
- package/dist/workload.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
//! The deterministic contract behind FeltDB's semantic state diff.
|
|
2
|
+
//!
|
|
3
|
+
//! This is the second specimen of the pattern established by
|
|
4
|
+
//! [`crate::trigger_contract`]: a decision FeltDB already makes, drawn behind a
|
|
5
|
+
//! runtime-independent boundary so that more than one implementation can be
|
|
6
|
+
//! held to it.
|
|
7
|
+
//!
|
|
8
|
+
//! ```text
|
|
9
|
+
//! canonical state A + canonical state B
|
|
10
|
+
//! │
|
|
11
|
+
//! ┌──────────────┴──────────────┐
|
|
12
|
+
//! │ │
|
|
13
|
+
//! native evaluation WASM evaluation
|
|
14
|
+
//! │ │
|
|
15
|
+
//! └──────────────┬──────────────┘
|
|
16
|
+
//! │
|
|
17
|
+
//! canonical diff bytes
|
|
18
|
+
//! ```
|
|
19
|
+
//!
|
|
20
|
+
//! # This module adds no diff semantics
|
|
21
|
+
//!
|
|
22
|
+
//! [`SemanticDiff::compute`] is untouched and remains authoritative. Everything
|
|
23
|
+
//! here is envelope: identity, version, canonical encoding, and the decision to
|
|
24
|
+
//! report an unusable input rather than fail. If this module and
|
|
25
|
+
//! `SemanticDiff::compute` ever disagree, `SemanticDiff::compute` is right.
|
|
26
|
+
//!
|
|
27
|
+
//! # Determinism budget
|
|
28
|
+
//!
|
|
29
|
+
//! Evaluation reads nothing but its two states. No clock, filesystem, network,
|
|
30
|
+
//! random source, process identity, authentication context, authority metadata
|
|
31
|
+
//! or process-global mutable state participates, and the audit that produced
|
|
32
|
+
//! this contract found none in the implementation either.
|
|
33
|
+
//!
|
|
34
|
+
//! # What the audit established about the semantics
|
|
35
|
+
//!
|
|
36
|
+
//! These are properties of the existing implementation, recorded here because
|
|
37
|
+
//! the contract is defined against them rather than against an idea of what a
|
|
38
|
+
//! diff should do:
|
|
39
|
+
//!
|
|
40
|
+
//! - **Arrays are positional.** A reorder is not detected; `[1,2] -> [2,1]`
|
|
41
|
+
//! reports `Changed` at index 0 and at index 1. Elements past the shorter
|
|
42
|
+
//! length become `Added` or `Removed` at their index.
|
|
43
|
+
//! - **Absence and null are different, in both directions.** `missing -> value`
|
|
44
|
+
//! is `Added`, `null -> value` is `Changed`, `value -> missing` is `Removed`,
|
|
45
|
+
//! `value -> null` is `Changed`.
|
|
46
|
+
//! - **`old_value` and `new_value` are `Option`, encoded as `null` when absent.**
|
|
47
|
+
//! That is ambiguous with a JSON null on its own, and `kind` is what
|
|
48
|
+
//! disambiguates: `missing -> null` is `Added` with `new_value: null`.
|
|
49
|
+
//! - **Numbers compare by `serde_json` variant.** `42` and `42.0` differ, so
|
|
50
|
+
//! they report `Changed`; `-0` is a float and so differs from the integer
|
|
51
|
+
//! `0`; `-0.0` and `0.0` are equal under IEEE equality and report nothing.
|
|
52
|
+
//! - **A type transition is one `Changed`,** carrying both whole values, at the
|
|
53
|
+
//! path where it happens — including at the empty path, for the states
|
|
54
|
+
//! themselves.
|
|
55
|
+
//! - **Ordering is total.** Changes sort by path, then by kind. Paths are
|
|
56
|
+
//! unique within one diff, so the ordering does not depend on sort stability.
|
|
57
|
+
|
|
58
|
+
use crate::state_model::SemanticDiff;
|
|
59
|
+
use serde::{Deserialize, Serialize};
|
|
60
|
+
use serde_json::Value;
|
|
61
|
+
use sha2::{Digest, Sha256};
|
|
62
|
+
|
|
63
|
+
/// Stable identity of this contract.
|
|
64
|
+
pub const STATE_DIFF_CONTRACT_ID: &str = "feltdb.state.diff";
|
|
65
|
+
|
|
66
|
+
/// Version of this contract's input shape, output shape and semantics.
|
|
67
|
+
///
|
|
68
|
+
/// The output shape is [`SemanticDiff`]'s own serialization, so a change to
|
|
69
|
+
/// that type is a change to this contract and must bump this.
|
|
70
|
+
pub const STATE_DIFF_CONTRACT_VERSION: u32 = 1;
|
|
71
|
+
|
|
72
|
+
/// The complete deterministic input: two states, and the contract's identity.
|
|
73
|
+
///
|
|
74
|
+
/// Members are declared alphabetically so the canonical struct encoding matches
|
|
75
|
+
/// the sorted-key encoding `serde_json` gives every JSON object.
|
|
76
|
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
77
|
+
#[serde(deny_unknown_fields)]
|
|
78
|
+
pub struct StateDiffInput {
|
|
79
|
+
pub contract: String,
|
|
80
|
+
pub state_a: Value,
|
|
81
|
+
pub state_b: Value,
|
|
82
|
+
pub version: u32,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl StateDiffInput {
|
|
86
|
+
/// Builds an input stamped with this contract's identity and version.
|
|
87
|
+
pub fn new(state_a: Value, state_b: Value) -> Self {
|
|
88
|
+
Self {
|
|
89
|
+
contract: STATE_DIFF_CONTRACT_ID.to_string(),
|
|
90
|
+
state_a,
|
|
91
|
+
state_b,
|
|
92
|
+
version: STATE_DIFF_CONTRACT_VERSION,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Why an input could not be evaluated.
|
|
98
|
+
///
|
|
99
|
+
/// Three codes, matching the vocabulary [`crate::trigger_contract`] uses. As
|
|
100
|
+
/// there, an error carries a code and nothing else: a free-form message would
|
|
101
|
+
/// be a divergence surface between implementations that says nothing about the
|
|
102
|
+
/// semantics under test.
|
|
103
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
104
|
+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
105
|
+
pub enum ContractErrorCode {
|
|
106
|
+
/// The bytes are not a well-formed instance of this contract's input.
|
|
107
|
+
ContractInputInvalid,
|
|
108
|
+
/// The input declares a different contract.
|
|
109
|
+
ContractIdMismatch,
|
|
110
|
+
/// The input declares a version this implementation does not implement.
|
|
111
|
+
ContractVersionUnsupported,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// The error body of a contract output.
|
|
115
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
116
|
+
#[serde(deny_unknown_fields)]
|
|
117
|
+
pub struct ContractErrorBody {
|
|
118
|
+
pub code: ContractErrorCode,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// The complete deterministic output.
|
|
122
|
+
///
|
|
123
|
+
/// Exactly one of `diff` and `error` is present. `diff` is [`SemanticDiff`]
|
|
124
|
+
/// serialized exactly as it already serializes — this contract does not
|
|
125
|
+
/// reshape it, because the existing semantic result *is* the contract.
|
|
126
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
127
|
+
#[serde(deny_unknown_fields)]
|
|
128
|
+
pub struct StateDiffOutput {
|
|
129
|
+
pub contract: String,
|
|
130
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
131
|
+
pub diff: Option<SemanticDiff>,
|
|
132
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
133
|
+
pub error: Option<ContractErrorBody>,
|
|
134
|
+
pub version: u32,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
impl StateDiffOutput {
|
|
138
|
+
fn computed(diff: SemanticDiff) -> Self {
|
|
139
|
+
Self {
|
|
140
|
+
contract: STATE_DIFF_CONTRACT_ID.to_string(),
|
|
141
|
+
diff: Some(diff),
|
|
142
|
+
error: None,
|
|
143
|
+
version: STATE_DIFF_CONTRACT_VERSION,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fn failure(code: ContractErrorCode) -> Self {
|
|
148
|
+
Self {
|
|
149
|
+
contract: STATE_DIFF_CONTRACT_ID.to_string(),
|
|
150
|
+
diff: None,
|
|
151
|
+
error: Some(ContractErrorBody { code }),
|
|
152
|
+
version: STATE_DIFF_CONTRACT_VERSION,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// The runtime that executed a contract, recorded in evidence.
|
|
158
|
+
///
|
|
159
|
+
/// Only [`ContractRuntime::Native`] is wired anywhere. As with the trigger
|
|
160
|
+
/// contract, the WASM variant exists so a recorded runtime can be named
|
|
161
|
+
/// honestly; it is not a selection switch.
|
|
162
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
163
|
+
#[serde(rename_all = "snake_case")]
|
|
164
|
+
pub enum ContractRuntime {
|
|
165
|
+
Native,
|
|
166
|
+
Wasm,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
impl ContractRuntime {
|
|
170
|
+
pub fn as_str(self) -> &'static str {
|
|
171
|
+
match self {
|
|
172
|
+
Self::Native => "native",
|
|
173
|
+
Self::Wasm => "wasm",
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// The deterministic contract, natively implemented.
|
|
179
|
+
///
|
|
180
|
+
/// A thin adapter over the existing implementation, deliberately holding no
|
|
181
|
+
/// diff logic of its own.
|
|
182
|
+
pub fn diff_states(state_a: &Value, state_b: &Value) -> SemanticDiff {
|
|
183
|
+
SemanticDiff::compute(state_a, state_b)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// The canonical byte encoding of a contract input.
|
|
187
|
+
pub fn canonical_input_bytes(input: &StateDiffInput) -> Vec<u8> {
|
|
188
|
+
serde_json::to_vec(input).expect("contract input is always serializable")
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// The canonical byte encoding of a contract output.
|
|
192
|
+
pub fn canonical_output_bytes(output: &StateDiffOutput) -> Vec<u8> {
|
|
193
|
+
serde_json::to_vec(output).expect("contract output is always serializable")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// Content identity of a contract input, for provenance.
|
|
197
|
+
pub fn input_digest(input: &StateDiffInput) -> String {
|
|
198
|
+
format!("sha256:{:x}", Sha256::digest(canonical_input_bytes(input)))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// The runtime-independent entry point: canonical bytes in, canonical bytes out.
|
|
202
|
+
///
|
|
203
|
+
/// This is the exact shape a foreign runtime implements. It never panics and
|
|
204
|
+
/// never returns anything but a canonical output encoding, so an unusable input
|
|
205
|
+
/// is a contract decision rather than a host-specific failure mode.
|
|
206
|
+
pub fn evaluate_canonical(input: &[u8]) -> Vec<u8> {
|
|
207
|
+
canonical_output_bytes(&evaluate_canonical_output(input))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// [`evaluate_canonical`] before encoding, for callers that want the value.
|
|
211
|
+
pub fn evaluate_canonical_output(input: &[u8]) -> StateDiffOutput {
|
|
212
|
+
let Ok(parsed) = serde_json::from_slice::<StateDiffInput>(input) else {
|
|
213
|
+
return StateDiffOutput::failure(ContractErrorCode::ContractInputInvalid);
|
|
214
|
+
};
|
|
215
|
+
if parsed.contract != STATE_DIFF_CONTRACT_ID {
|
|
216
|
+
return StateDiffOutput::failure(ContractErrorCode::ContractIdMismatch);
|
|
217
|
+
}
|
|
218
|
+
if parsed.version != STATE_DIFF_CONTRACT_VERSION {
|
|
219
|
+
return StateDiffOutput::failure(ContractErrorCode::ContractVersionUnsupported);
|
|
220
|
+
}
|
|
221
|
+
StateDiffOutput::computed(diff_states(&parsed.state_a, &parsed.state_b))
|
|
222
|
+
}
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
//! - Explicit reconciliation (no automatic merge)
|
|
15
15
|
|
|
16
16
|
use crate::state_model::{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
StateReconciliationResult, STATE_MODEL_VERSION,
|
|
17
|
+
ConflictClassification, ReconciliationPlan, Relationship, SemanticDiff, StateId,
|
|
18
|
+
StateReconciliationResult, StateRevision, StateStore, StateTopology, STATE_MODEL_VERSION,
|
|
20
19
|
};
|
|
21
20
|
use crate::FeltDb;
|
|
22
21
|
use std::sync::Arc;
|
|
@@ -47,8 +46,8 @@ impl FeltDBStateSystem {
|
|
|
47
46
|
/// ```ignore
|
|
48
47
|
/// let db = FeltDb::open("./data")?;
|
|
49
48
|
/// let store = FeltDBStateSystem::create_store(&Arc::new(db))?;
|
|
50
|
-
/// let initial = store.create(json_string, "app-authority")?;
|
|
51
|
-
/// let
|
|
49
|
+
/// let initial = store.create("docs:1", json_string, "app-authority")?;
|
|
50
|
+
/// let same = store.get(&initial.id).expect("retrieved by identity");
|
|
52
51
|
/// ```
|
|
53
52
|
pub fn create_store(db: &Arc<FeltDb>) -> Result<StateStore, String> {
|
|
54
53
|
StateStore::with_feltdb(db.clone())
|
|
@@ -81,7 +80,9 @@ impl FeltDBStateSystem {
|
|
|
81
80
|
2. Use FeltDBStateSystem::create_store(&Arc::new(db)) to initialize state
|
|
82
81
|
3. Call store.create() for initial state
|
|
83
82
|
4. Call store.commit() for transitions
|
|
84
|
-
5. Call store.
|
|
83
|
+
5. Call store.get(&id) to retrieve a revision by identity
|
|
84
|
+
(the store does not track which revision is "current" — see
|
|
85
|
+
StateStore's own documentation for why that is a separate concern)
|
|
85
86
|
6. Use StateTopology to inspect causal relationships
|
|
86
87
|
7. Use SemanticDiff to compute changes between states
|
|
87
88
|
8. Use ConflictClassification to analyze divergence
|
|
@@ -104,7 +105,10 @@ impl FeltDBStateSystem {
|
|
|
104
105
|
#[cfg(test)]
|
|
105
106
|
mod facade_tests {
|
|
106
107
|
use super::*;
|
|
107
|
-
use crate::{
|
|
108
|
+
use crate::{
|
|
109
|
+
state_model::StateStore, ConflictClass, ConflictClassification, Relationship, SemanticDiff,
|
|
110
|
+
StateTopology,
|
|
111
|
+
};
|
|
108
112
|
use serde_json::json;
|
|
109
113
|
|
|
110
114
|
#[test]
|
|
@@ -112,11 +116,17 @@ mod facade_tests {
|
|
|
112
116
|
// For testing, use new_volatile() - production must use create_store(&db)
|
|
113
117
|
let store = StateStore::new_volatile();
|
|
114
118
|
let initial = store
|
|
115
|
-
.create(
|
|
119
|
+
.create(
|
|
120
|
+
"docs:1",
|
|
121
|
+
r#"{"data":"test"}"#.to_string(),
|
|
122
|
+
"test-auth".to_string(),
|
|
123
|
+
)
|
|
116
124
|
.expect("Failed to create initial state");
|
|
117
|
-
|
|
118
|
-
let
|
|
119
|
-
|
|
125
|
+
|
|
126
|
+
let stored = store
|
|
127
|
+
.get(&initial.id)
|
|
128
|
+
.expect("the revision is retrievable by identity");
|
|
129
|
+
assert_eq!(stored.id, initial.id);
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
#[test]
|
|
@@ -128,19 +138,21 @@ mod facade_tests {
|
|
|
128
138
|
fn test_facade_comprehensive_workflow() {
|
|
129
139
|
// Initialize - for testing, use new_volatile()
|
|
130
140
|
let store = StateStore::new_volatile();
|
|
131
|
-
|
|
141
|
+
|
|
132
142
|
// Create initial state
|
|
133
143
|
let initial = store
|
|
134
144
|
.create(
|
|
145
|
+
"docs:1",
|
|
135
146
|
r#"{"users":{"alice":100}}"#.to_string(),
|
|
136
147
|
"app-auth".to_string(),
|
|
137
148
|
)
|
|
138
149
|
.expect("Failed to create");
|
|
139
150
|
|
|
140
|
-
// Verify
|
|
141
|
-
|
|
151
|
+
// Verify the revision is retrievable by identity. The store tracks no
|
|
152
|
+
// "current" pointer; the caller holds the ids it cares about.
|
|
153
|
+
assert_eq!(store.get(&initial.id).unwrap().id, initial.id);
|
|
142
154
|
|
|
143
|
-
//
|
|
155
|
+
// Divergence 1: alice updates
|
|
144
156
|
let branch1 = store
|
|
145
157
|
.commit(
|
|
146
158
|
r#"{"users":{"alice":150}}"#.to_string(),
|
|
@@ -149,11 +161,7 @@ mod facade_tests {
|
|
|
149
161
|
)
|
|
150
162
|
.expect("Failed branch 1");
|
|
151
163
|
|
|
152
|
-
|
|
153
|
-
.create_branch("alice-branch".to_string(), branch1.id.clone())
|
|
154
|
-
.expect("Failed to create alice-branch");
|
|
155
|
-
|
|
156
|
-
// Branch 2: create from initial
|
|
164
|
+
// Divergence 2: from the same parent
|
|
157
165
|
let branch2 = store
|
|
158
166
|
.commit(
|
|
159
167
|
r#"{"users":{"alice":100,"bob":50}}"#.to_string(),
|
|
@@ -162,10 +170,6 @@ mod facade_tests {
|
|
|
162
170
|
)
|
|
163
171
|
.expect("Failed branch 2");
|
|
164
172
|
|
|
165
|
-
store
|
|
166
|
-
.create_branch("bob-branch".to_string(), branch2.id.clone())
|
|
167
|
-
.expect("Failed to create bob-branch");
|
|
168
|
-
|
|
169
173
|
// Inspect topology
|
|
170
174
|
let mut topology = StateTopology::new();
|
|
171
175
|
topology.add_revision(initial.clone());
|
|
@@ -181,12 +185,10 @@ mod facade_tests {
|
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
// Compute diff using JSON values
|
|
184
|
-
let initial_json: serde_json::Value =
|
|
185
|
-
r#"{"users":{"alice":100}}"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
r#"{"users":{"alice":150}}"#,
|
|
189
|
-
).unwrap();
|
|
188
|
+
let initial_json: serde_json::Value =
|
|
189
|
+
serde_json::from_str(r#"{"users":{"alice":100}}"#).unwrap();
|
|
190
|
+
let branch1_json: serde_json::Value =
|
|
191
|
+
serde_json::from_str(r#"{"users":{"alice":150}}"#).unwrap();
|
|
190
192
|
let diff = SemanticDiff::compute(&initial_json, &branch1_json);
|
|
191
193
|
assert!(!diff.changes.is_empty());
|
|
192
194
|
|
|
@@ -199,16 +201,16 @@ mod facade_tests {
|
|
|
199
201
|
fn test_facade_immutability() {
|
|
200
202
|
let store = StateStore::new_volatile();
|
|
201
203
|
let initial = store
|
|
202
|
-
.create(r#"{"v":1}"#.to_string(), "auth".to_string())
|
|
204
|
+
.create("docs:1", r#"{"v":1}"#.to_string(), "auth".to_string())
|
|
203
205
|
.unwrap();
|
|
204
|
-
|
|
206
|
+
|
|
205
207
|
let initial_id = initial.id.clone();
|
|
206
|
-
|
|
208
|
+
|
|
207
209
|
// Create another state
|
|
208
210
|
let second = store
|
|
209
211
|
.commit(r#"{"v":2}"#.to_string(), &initial, "auth".to_string())
|
|
210
212
|
.unwrap();
|
|
211
|
-
|
|
213
|
+
|
|
212
214
|
// Verify first state is unchanged
|
|
213
215
|
assert_eq!(store.get(&initial_id).unwrap().content, r#"{"v":1}"#);
|
|
214
216
|
assert_eq!(initial_id, initial.id);
|
|
@@ -218,12 +220,16 @@ mod facade_tests {
|
|
|
218
220
|
fn test_facade_restart_recovery() {
|
|
219
221
|
let store1 = StateStore::new_volatile();
|
|
220
222
|
let initial = store1
|
|
221
|
-
.create(
|
|
223
|
+
.create(
|
|
224
|
+
"docs:1",
|
|
225
|
+
r#"{"data":"test"}"#.to_string(),
|
|
226
|
+
"auth".to_string(),
|
|
227
|
+
)
|
|
222
228
|
.unwrap();
|
|
223
|
-
|
|
229
|
+
|
|
224
230
|
// Simulate restart: new store instance
|
|
225
231
|
let _store2 = StateStore::new_volatile();
|
|
226
|
-
|
|
232
|
+
|
|
227
233
|
// Note: In real scenario, store would load from persistent storage
|
|
228
234
|
// Here we demonstrate the API contract: states retrieved by id are valid
|
|
229
235
|
assert!(store1.exists(&initial.id));
|
|
@@ -233,44 +239,62 @@ mod facade_tests {
|
|
|
233
239
|
fn test_facade_authority_neutrality() {
|
|
234
240
|
let store = StateStore::new_volatile();
|
|
235
241
|
let initial = store
|
|
236
|
-
.create(
|
|
242
|
+
.create(
|
|
243
|
+
"docs:1",
|
|
244
|
+
r#"{"balance":100}"#.to_string(),
|
|
245
|
+
"alice".to_string(),
|
|
246
|
+
)
|
|
237
247
|
.unwrap();
|
|
238
|
-
|
|
248
|
+
|
|
239
249
|
let update1 = store
|
|
240
|
-
.commit(
|
|
250
|
+
.commit(
|
|
251
|
+
r#"{"balance":150}"#.to_string(),
|
|
252
|
+
&initial,
|
|
253
|
+
"alice".to_string(),
|
|
254
|
+
)
|
|
241
255
|
.unwrap();
|
|
242
|
-
|
|
256
|
+
|
|
243
257
|
let update2 = store
|
|
244
|
-
.commit(
|
|
258
|
+
.commit(
|
|
259
|
+
r#"{"balance":150}"#.to_string(),
|
|
260
|
+
&initial,
|
|
261
|
+
"bob".to_string(),
|
|
262
|
+
)
|
|
245
263
|
.unwrap();
|
|
246
|
-
|
|
247
|
-
//
|
|
264
|
+
|
|
265
|
+
// The same state, from the same parent, is the same historical fact —
|
|
266
|
+
// so it is one revision, whoever committed it. Authority is recorded
|
|
267
|
+
// but takes no part in identity.
|
|
248
268
|
assert_eq!(update1.id, update2.id);
|
|
249
|
-
|
|
250
|
-
|
|
269
|
+
assert_eq!(update1.content_id, update2.content_id);
|
|
270
|
+
|
|
271
|
+
// And because authority takes no part in identity, the second commit
|
|
272
|
+
// is a no-op that returns what was already recorded. The authority on a
|
|
273
|
+
// revision is whoever first committed that historical fact, not
|
|
274
|
+
// whoever last asked for it.
|
|
251
275
|
assert_eq!(update1.authority, "alice");
|
|
252
|
-
assert_eq!(update2.authority, "
|
|
276
|
+
assert_eq!(update2.authority, "alice");
|
|
253
277
|
}
|
|
254
278
|
|
|
255
279
|
#[test]
|
|
256
280
|
fn test_facade_read_only_operations() {
|
|
257
281
|
let store = StateStore::new_volatile();
|
|
258
282
|
let initial = store
|
|
259
|
-
.create(r#"{"v":1}"#.to_string(), "auth".to_string())
|
|
283
|
+
.create("docs:1", r#"{"v":1}"#.to_string(), "auth".to_string())
|
|
260
284
|
.unwrap();
|
|
261
|
-
|
|
285
|
+
|
|
262
286
|
let update = store
|
|
263
287
|
.commit(r#"{"v":2}"#.to_string(), &initial, "auth".to_string())
|
|
264
288
|
.unwrap();
|
|
265
|
-
|
|
289
|
+
|
|
266
290
|
// Topology operations should not mutate
|
|
267
291
|
let mut topology = StateTopology::new();
|
|
268
292
|
topology.add_revision(initial.clone());
|
|
269
293
|
topology.add_revision(update.clone());
|
|
270
|
-
|
|
294
|
+
|
|
271
295
|
let rel = topology.relationship(&initial.id, &update.id);
|
|
272
296
|
assert!(matches!(rel, Relationship::Ancestor));
|
|
273
|
-
|
|
297
|
+
|
|
274
298
|
// Verify both states still exist unchanged
|
|
275
299
|
assert_eq!(store.get(&initial.id).unwrap().id, initial.id);
|
|
276
300
|
assert_eq!(store.get(&update.id).unwrap().id, update.id);
|
|
@@ -281,9 +305,13 @@ mod facade_tests {
|
|
|
281
305
|
// This test verifies we can use the state system without any .git access
|
|
282
306
|
let store = StateStore::new_volatile();
|
|
283
307
|
let state = store
|
|
284
|
-
.create(
|
|
308
|
+
.create(
|
|
309
|
+
"docs:1",
|
|
310
|
+
r#"{"test":"no-git"}"#.to_string(),
|
|
311
|
+
"auth".to_string(),
|
|
312
|
+
)
|
|
285
313
|
.expect("State creation should work without .git");
|
|
286
|
-
|
|
314
|
+
|
|
287
315
|
// Topology and diff operations should work without git
|
|
288
316
|
let mut topology = StateTopology::new();
|
|
289
317
|
topology.add_revision(state.clone());
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
///
|
|
3
3
|
/// Produces SHA-256 hashes of canonical state serializations.
|
|
4
4
|
/// Invariant: same state → same hash, different state → different hash (except negligible crypto collision)
|
|
5
|
-
|
|
6
5
|
use serde_json::{json, Value};
|
|
7
6
|
use std::collections::BTreeMap;
|
|
8
7
|
|
|
@@ -27,7 +26,8 @@ impl CanonicalState {
|
|
|
27
26
|
|
|
28
27
|
/// Get a record
|
|
29
28
|
pub fn get_record(&self, collection: &str, record_id: &str) -> Option<&Value> {
|
|
30
|
-
self.records
|
|
29
|
+
self.records
|
|
30
|
+
.get(&(collection.to_string(), record_id.to_string()))
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/// Canonical serialization for hashing (deterministic ordering)
|