@feltdb/core 0.8.4 → 0.8.6

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.
Files changed (160) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/Cargo.lock +165 -0
  3. package/dist/create/server-source/Cargo.toml +9 -0
  4. package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
  5. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
  6. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
  7. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
  8. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
  9. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
  10. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
  11. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
  12. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
  13. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
  14. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
  15. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
  19. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
  20. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
  21. package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
  22. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
  23. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
  24. package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
  25. package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
  26. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
  27. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
  28. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
  29. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
  30. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
  31. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
  32. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
  33. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
  34. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
  35. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
  36. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
  37. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
  38. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
  39. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
  40. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
  41. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
  42. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
  43. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
  44. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
  45. package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
  46. package/dist/create/server-source/crates/feltdb/src/lib.rs +1648 -46
  47. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
  48. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
  49. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
  50. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
  51. package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
  52. package/dist/create/server-source/crates/feltdb/src/operation.rs +39 -0
  53. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
  54. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
  55. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
  56. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
  57. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
  58. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
  59. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
  60. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
  61. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
  62. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
  63. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
  64. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
  65. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
  66. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
  67. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
  68. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
  69. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
  70. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
  71. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
  72. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
  73. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
  74. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
  75. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
  76. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
  77. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
  78. package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
  79. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
  80. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
  81. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
  82. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
  83. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
  84. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
  85. package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
  86. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
  87. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
  88. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
  89. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
  90. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
  91. package/dist/create/server-source/crates/feltdb/src/state_model.rs +1565 -536
  92. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
  93. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
  94. package/dist/create/server-source/crates/feltdb/src/storage.rs +9 -3
  95. package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
  96. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
  97. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
  98. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
  99. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
  100. package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
  101. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
  102. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  103. package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
  104. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
  105. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +132 -0
  106. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
  107. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +272 -0
  108. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +467 -0
  109. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +309 -0
  110. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +445 -0
  111. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +518 -0
  112. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
  113. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
  114. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
  115. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
  116. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
  117. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +278 -0
  118. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
  119. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
  120. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
  121. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
  122. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
  123. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
  124. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
  125. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
  126. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
  127. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
  128. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
  129. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1365 -0
  130. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
  131. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
  132. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +417 -0
  133. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
  134. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
  135. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
  136. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
  137. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
  138. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
  139. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
  140. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
  141. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
  142. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
  143. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
  144. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
  145. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
  146. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
  147. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +442 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +16 -1
  149. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
  150. package/dist/create/server-source/crates/feltdb-server/src/main.rs +695 -47
  151. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +21 -0
  152. package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-C1VhI-U5.js} +1 -1
  153. package/dist/studio-app/assets/feltdb_wasm_bg-C8HXbAXb.wasm +0 -0
  154. package/dist/studio-app/assets/{index-DwgNAIIX.js → index-Bbos1m2U.js} +1 -1
  155. package/dist/studio-app/index.html +1 -1
  156. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  157. package/dist/workload.d.ts +2 -1
  158. package/dist/workload.d.ts.map +1 -1
  159. package/package.json +1 -1
  160. 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
- StateId, StateRevision, StateStore, StateTopology, Relationship,
18
- SemanticDiff, ConflictClassification, ReconciliationPlan,
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 current = store.current()?;
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.current() to retrieve the working state
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::{ConflictClass, SemanticDiff, StateTopology, Relationship, ConflictClassification, state_model::StateStore};
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(r#"{"data":"test"}"#.to_string(), "test-auth".to_string())
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 current = store.current().expect("Failed to get current state");
119
- assert_eq!(current.id, initial.id);
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 current
141
- assert_eq!(store.current().unwrap().id, initial.id);
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
- // Branch 1: alice updates
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
- store
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 = serde_json::from_str(
185
- r#"{"users":{"alice":100}}"#,
186
- ).unwrap();
187
- let branch1_json: serde_json::Value = serde_json::from_str(
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(r#"{"data":"test"}"#.to_string(), "auth".to_string())
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(r#"{"balance":100}"#.to_string(), "alice".to_string())
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(r#"{"balance":150}"#.to_string(), &initial, "alice".to_string())
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(r#"{"balance":150}"#.to_string(), &initial, "bob".to_string())
258
+ .commit(
259
+ r#"{"balance":150}"#.to_string(),
260
+ &initial,
261
+ "bob".to_string(),
262
+ )
245
263
  .unwrap();
246
-
247
- // Same content, different authorities produce same id
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
- // But authorities are recorded for audit
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, "bob");
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(r#"{"test":"no-git"}"#.to_string(), "auth".to_string())
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.get(&(collection.to_string(), record_id.to_string()))
29
+ self.records
30
+ .get(&(collection.to_string(), record_id.to_string()))
31
31
  }
32
32
 
33
33
  /// Canonical serialization for hashing (deterministic ordering)