@feltdb/core 0.15.0 → 0.16.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.
Files changed (247) hide show
  1. package/dist/agent-state.d.ts +20 -0
  2. package/dist/agent-state.d.ts.map +1 -1
  3. package/dist/agent-state.js +36 -0
  4. package/dist/build-identity.json +12 -4
  5. package/dist/cli/commands.js +187 -4
  6. package/dist/cli/publish-engine.js +116 -16
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +3 -0
  9. package/dist/create/build-identity.json +12 -4
  10. package/dist/create/managed-account.js +93 -35
  11. package/dist/create/package-versions.js +1 -1
  12. package/dist/create/server-source/Cargo.lock +6 -509
  13. package/dist/create/server-source/Cargo.toml +2 -13
  14. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  15. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  16. package/dist/create/server-source/crates/feltdb-server/src/main.rs +255 -12
  17. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  18. package/dist/db.d.ts +11 -1
  19. package/dist/db.d.ts.map +1 -1
  20. package/dist/db.js +15 -2
  21. package/dist/error-codes.d.ts +5 -0
  22. package/dist/error-codes.d.ts.map +1 -1
  23. package/dist/error-codes.js +9 -1
  24. package/dist/http-db.d.ts +16 -0
  25. package/dist/http-db.d.ts.map +1 -1
  26. package/dist/http-db.js +36 -6
  27. package/dist/managed-recovery.d.ts +6 -0
  28. package/dist/managed-recovery.d.ts.map +1 -1
  29. package/dist/managed-recovery.js +9 -0
  30. package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-DtybUY0q.js} +1 -1
  31. package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
  32. package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
  33. package/dist/studio-app/index.html +1 -1
  34. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  35. package/dist/wasm/package.json +1 -1
  36. package/dist/workload.d.ts +55 -3
  37. package/dist/workload.d.ts.map +1 -1
  38. package/dist/workload.js +23 -0
  39. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  40. package/dist/workspace/workspace-connection.js +14 -3
  41. package/package.json +1 -1
  42. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  43. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  44. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  45. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  46. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  47. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  48. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  49. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  50. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  51. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  52. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  53. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  54. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  55. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  56. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  57. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  58. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  59. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  60. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  61. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  62. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  63. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  64. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  65. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  66. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  67. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  68. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  69. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  70. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  71. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  72. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  73. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  74. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  75. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  76. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  77. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  78. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  79. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  80. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  81. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  82. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  83. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  84. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  85. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  86. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  87. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  88. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  89. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  90. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  91. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  92. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  93. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  94. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  95. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  96. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  97. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  98. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  99. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  100. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  101. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  102. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  103. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  104. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  105. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  106. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  107. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  108. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  109. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  110. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
  111. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  112. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  113. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  114. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  115. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  116. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  117. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  118. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  119. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  120. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  121. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  122. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  123. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  124. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  125. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  126. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  127. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  128. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  129. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  130. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  131. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  132. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  133. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  134. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  135. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  136. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  137. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  138. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  139. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  140. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  141. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  142. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  143. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  144. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  145. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  146. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  147. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  148. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  149. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  150. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  151. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  152. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  153. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  154. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  155. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  156. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  157. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  158. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  159. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  160. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
  161. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  162. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  163. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  164. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  165. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  166. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  167. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  168. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  169. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  170. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  171. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  172. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  173. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  174. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  175. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  176. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  177. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  178. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  179. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
  180. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  181. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  182. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  183. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  184. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
  185. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  186. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  187. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  188. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  189. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  190. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  191. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  192. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  193. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  194. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  195. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  196. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  197. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  198. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  199. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  200. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  201. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  202. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  203. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  204. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  205. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  206. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  207. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  208. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  209. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  210. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  211. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  212. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  213. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  214. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  215. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  216. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  217. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  218. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  219. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  220. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  221. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  222. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  223. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  224. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  225. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  226. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  227. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  228. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  229. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  230. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  231. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  232. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  233. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  234. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  235. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  236. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  237. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  238. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  239. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  240. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  241. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  242. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  243. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  244. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  245. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  246. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  247. package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
@@ -1,994 +0,0 @@
1
- //! Conformance, determinism and capability proofs for the
2
- //! `feltdb.trigger.match` deterministic contract.
3
- //!
4
- //! The architectural invariant under test is:
5
- //!
6
- //! ```text
7
- //! Contract semantics are authoritative.
8
- //! Runtime implementation is replaceable.
9
- //!
10
- //! NativeContract(input) == WasmContract(input) for every fixture
11
- //! ```
12
- //!
13
- //! The corpus in `tests/fixtures/trigger_contract_corpus.json` is committed
14
- //! rather than generated, and carries the canonical output each case must
15
- //! produce. Three comparisons run over it:
16
- //!
17
- //! 1. native output == committed expectation (runs everywhere);
18
- //! 2. WASM output == committed expectation (needs `wasm32-unknown-unknown`);
19
- //! 3. native output == WASM output, byte for byte.
20
- //!
21
- //! The first makes the suite meaningful even where the WASM toolchain is
22
- //! absent: a change in native semantics is caught by the same bytes the WASM
23
- //! implementation was measured against.
24
-
25
- use feltdb::trigger_contract::{
26
- canonical_input_bytes, evaluate_canonical, TriggerMatchInput, TRIGGER_MATCH_CONTRACT_ID,
27
- TRIGGER_MATCH_CONTRACT_VERSION,
28
- };
29
- use serde::Deserialize;
30
- use serde_json::Value;
31
- use std::path::{Path, PathBuf};
32
- use std::process::Command;
33
- use std::sync::OnceLock;
34
-
35
- // ---------------------------------------------------------------------------
36
- // Corpus
37
- // ---------------------------------------------------------------------------
38
-
39
- #[derive(Debug, Deserialize)]
40
- struct Corpus {
41
- corpus_version: u32,
42
- contract: String,
43
- contract_version: u32,
44
- #[allow(dead_code)]
45
- about: String,
46
- cases: Vec<Case>,
47
- }
48
-
49
- #[derive(Debug, Deserialize)]
50
- struct Case {
51
- name: String,
52
- #[allow(dead_code)]
53
- why: String,
54
- /// A well-formed contract input, canonically encoded by the harness.
55
- #[serde(default)]
56
- input: Option<TriggerMatchInput>,
57
- /// Bytes fed to the contract verbatim, for malformed and non-canonical
58
- /// encodings that a typed value cannot express.
59
- #[serde(default)]
60
- raw_input: Option<String>,
61
- /// The canonical encoding of `input`, recorded so that every consumer of
62
- /// the corpus presents the same bytes.
63
- ///
64
- /// A consumer in another language cannot always reproduce it: JavaScript
65
- /// has one number type, so it cannot encode the difference between `42` and
66
- /// `42.0` that this contract's value equality depends on. Recording the
67
- /// bytes makes the corpus self-describing instead of requiring every reader
68
- /// to own a canonical encoder.
69
- #[serde(default)]
70
- canonical_input: Option<String>,
71
- /// The canonical output both implementations must produce.
72
- #[serde(default)]
73
- expected_output: Option<String>,
74
- }
75
-
76
- impl Case {
77
- /// The exact bytes this case presents to a contract implementation.
78
- fn bytes(&self) -> Vec<u8> {
79
- match (&self.input, &self.raw_input) {
80
- (Some(input), None) => canonical_input_bytes(input),
81
- (None, Some(raw)) => raw.as_bytes().to_vec(),
82
- _ => panic!(
83
- "case {} must have exactly one of input/raw_input",
84
- self.name
85
- ),
86
- }
87
- }
88
-
89
- fn expected(&self) -> &str {
90
- self.expected_output.as_deref().unwrap_or_else(|| {
91
- panic!(
92
- "case {} has no expected_output; regenerate the corpus with \
93
- FELTDB_TRIGGER_CONTRACT_BLESS=1",
94
- self.name
95
- )
96
- })
97
- }
98
- }
99
-
100
- fn corpus_path() -> PathBuf {
101
- Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/trigger_contract_corpus.json")
102
- }
103
-
104
- fn corpus() -> &'static Corpus {
105
- static CORPUS: OnceLock<Corpus> = OnceLock::new();
106
- CORPUS.get_or_init(|| {
107
- let bytes = std::fs::read(corpus_path()).expect("conformance corpus is committed");
108
- serde_json::from_slice(&bytes).expect("conformance corpus is well formed")
109
- })
110
- }
111
-
112
- #[test]
113
- fn corpus_addresses_this_contract_at_this_version() {
114
- let corpus = corpus();
115
- assert_eq!(corpus.corpus_version, 1);
116
- assert_eq!(corpus.contract, TRIGGER_MATCH_CONTRACT_ID);
117
- assert_eq!(corpus.contract_version, TRIGGER_MATCH_CONTRACT_VERSION);
118
- assert!(
119
- corpus.cases.len() >= 90,
120
- "the corpus is the proof; it should not shrink silently"
121
- );
122
- let mut names: Vec<&str> = corpus.cases.iter().map(|case| case.name.as_str()).collect();
123
- names.sort_unstable();
124
- let unique = names.len();
125
- names.dedup();
126
- assert_eq!(unique, names.len(), "case names identify cases");
127
- }
128
-
129
- /// Every required category of behaviour is represented by at least one case.
130
- ///
131
- /// This is a guard against the corpus quietly losing a dimension it was built
132
- /// to cover, not a substitute for reading it.
133
- #[test]
134
- fn corpus_covers_every_required_category() {
135
- let names: Vec<&str> = corpus().cases.iter().map(|c| c.name.as_str()).collect();
136
- for required in [
137
- "collection_matches",
138
- "collection_differs",
139
- "change_kind_update_matches",
140
- "change_kind_insert_against_update_trigger",
141
- "disabled_trigger_rejects_matching_change",
142
- "predicate_matches_string",
143
- "predicate_value_mismatch",
144
- "predicate_absent_matches_any_value",
145
- "value_null_present_with_whole_record_predicate",
146
- "nested_path_matches",
147
- "array_equality_matches",
148
- "integer_equality_matches",
149
- "fanout_first_trigger_matches",
150
- "malformed_not_json",
151
- "empty_object_record",
152
- ] {
153
- assert!(
154
- names.contains(&required),
155
- "corpus lost coverage: {required}"
156
- );
157
- }
158
- }
159
-
160
- /// The committed canonical bytes are what this crate's encoder produces.
161
- ///
162
- /// Consumers outside Rust read `canonical_input` rather than re-canonicalizing,
163
- /// so this is the check that keeps those bytes honest: if canonicalization
164
- /// changed, every other reader of the corpus would silently be testing an
165
- /// encoding FeltDB no longer emits.
166
- #[test]
167
- fn committed_canonical_bytes_match_the_encoder() {
168
- for case in &corpus().cases {
169
- match (&case.input, &case.raw_input) {
170
- (Some(input), None) => assert_eq!(
171
- case.canonical_input.as_deref(),
172
- Some(
173
- String::from_utf8(canonical_input_bytes(input))
174
- .unwrap()
175
- .as_str()
176
- ),
177
- "{} has a stale canonical_input",
178
- case.name
179
- ),
180
- (None, Some(_)) => assert!(
181
- case.canonical_input.is_none(),
182
- "{} is a raw case and must not also record canonical_input",
183
- case.name
184
- ),
185
- _ => panic!(
186
- "case {} must have exactly one of input/raw_input",
187
- case.name
188
- ),
189
- }
190
- }
191
- }
192
-
193
- // ---------------------------------------------------------------------------
194
- // WASM host
195
- // ---------------------------------------------------------------------------
196
-
197
- const WASM_CRATE: &str = "crates/feltdb-trigger-contract-wasm";
198
- const WASM_ARTIFACT: &str =
199
- "target/wasm32-unknown-unknown/release/feltdb_trigger_contract_wasm.wasm";
200
-
201
- fn repository_root() -> &'static Path {
202
- Path::new(env!("CARGO_MANIFEST_DIR"))
203
- .parent()
204
- .and_then(Path::parent)
205
- .expect("the crate lives at <root>/crates/feltdb")
206
- }
207
-
208
- /// Whether this machine can compile the contract to WebAssembly at all.
209
- fn wasm_target_installed() -> bool {
210
- Command::new("rustc")
211
- .args([
212
- "--print",
213
- "target-libdir",
214
- "--target",
215
- "wasm32-unknown-unknown",
216
- ])
217
- .output()
218
- .is_ok_and(|output| output.status.success())
219
- }
220
-
221
- /// Compiles a WASM contract crate and returns the module bytes.
222
- ///
223
- /// A build failure is a defect and panics. A missing `wasm32-unknown-unknown`
224
- /// target is an environment fact, reported by the caller as a skip.
225
- fn build_module(manifest_dir: &Path) -> Vec<u8> {
226
- let output = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
227
- .args(["build", "--release", "--target", "wasm32-unknown-unknown"])
228
- .current_dir(manifest_dir)
229
- // The contract crate is its own workspace; inheriting the outer
230
- // build's target directory or profile would defeat that.
231
- .env_remove("CARGO_TARGET_DIR")
232
- .env_remove("RUSTFLAGS")
233
- .output()
234
- .expect("cargo is runnable");
235
- assert!(
236
- output.status.success(),
237
- "building the WASM contract at {} failed:\n{}",
238
- manifest_dir.display(),
239
- String::from_utf8_lossy(&output.stderr)
240
- );
241
- std::fs::read(manifest_dir.join(WASM_ARTIFACT)).expect("the build emits one module")
242
- }
243
-
244
- /// The committed WASM contract, compiled once for the whole suite.
245
- fn contract_module() -> Option<&'static [u8]> {
246
- static MODULE: OnceLock<Option<Vec<u8>>> = OnceLock::new();
247
- MODULE
248
- .get_or_init(|| {
249
- if let Ok(path) = std::env::var("FELTDB_TRIGGER_CONTRACT_WASM") {
250
- return Some(
251
- std::fs::read(path).expect("FELTDB_TRIGGER_CONTRACT_WASM is readable"),
252
- );
253
- }
254
- if !wasm_target_installed() {
255
- return None;
256
- }
257
- Some(build_module(&repository_root().join(WASM_CRATE)))
258
- })
259
- .as_deref()
260
- }
261
-
262
- /// Announces a skip loudly, so an absent toolchain is never a silent pass.
263
- fn skipped(test: &str) {
264
- eprintln!(
265
- "SKIPPED {test}: the wasm32-unknown-unknown target is not installed, so the WASM \
266
- contract implementation could not be built. Native conformance against the committed \
267
- expectations still ran. Install the target, or set FELTDB_TRIGGER_CONTRACT_WASM to a \
268
- prebuilt module, to execute the cross-runtime proof."
269
- );
270
- }
271
-
272
- /// One instantiation of the WASM contract.
273
- ///
274
- /// The linker is empty and stays empty: the module declares no imports, so
275
- /// there is nothing to satisfy and nothing to grant.
276
- struct WasmContract {
277
- store: wasmi::Store<()>,
278
- memory: wasmi::Memory,
279
- alloc: wasmi::TypedFunc<u32, u32>,
280
- evaluate: wasmi::TypedFunc<(u32, u32), u64>,
281
- free: wasmi::TypedFunc<(u32, u32), ()>,
282
- }
283
-
284
- impl WasmContract {
285
- fn instantiate(bytes: &[u8]) -> Self {
286
- let engine = wasmi::Engine::default();
287
- let module = wasmi::Module::new(&engine, bytes).expect("the module validates");
288
- let mut store = wasmi::Store::new(&engine, ());
289
- let instance = wasmi::Linker::<()>::new(&engine)
290
- .instantiate_and_start(&mut store, &module)
291
- .expect("an empty linker satisfies a module with no imports");
292
- Self {
293
- memory: instance
294
- .get_memory(&store, "memory")
295
- .expect("the module exports its memory"),
296
- alloc: instance
297
- .get_typed_func(&store, "contract_alloc")
298
- .expect("contract_alloc"),
299
- evaluate: instance
300
- .get_typed_func(&store, "contract_evaluate")
301
- .expect("contract_evaluate"),
302
- free: instance
303
- .get_typed_func(&store, "contract_free")
304
- .expect("contract_free"),
305
- store,
306
- }
307
- }
308
-
309
- fn version(bytes: &[u8]) -> u32 {
310
- let engine = wasmi::Engine::default();
311
- let module = wasmi::Module::new(&engine, bytes).unwrap();
312
- let mut store = wasmi::Store::new(&engine, ());
313
- let instance = wasmi::Linker::<()>::new(&engine)
314
- .instantiate_and_start(&mut store, &module)
315
- .unwrap();
316
- instance
317
- .get_typed_func::<(), u32>(&store, "contract_version")
318
- .expect("contract_version")
319
- .call(&mut store, ())
320
- .expect("contract_version is total")
321
- }
322
-
323
- fn evaluate(&mut self, input: &[u8]) -> Vec<u8> {
324
- let len = input.len() as u32;
325
- let ptr = self.alloc.call(&mut self.store, len).expect("allocation");
326
- self.memory
327
- .write(&mut self.store, ptr as usize, input)
328
- .expect("input fits in linear memory");
329
- let packed = self
330
- .evaluate
331
- .call(&mut self.store, (ptr, len))
332
- .expect("the contract never traps on any input");
333
- let out_ptr = (packed >> 32) as usize;
334
- let out_len = (packed & 0xffff_ffff) as usize;
335
- let mut output = vec![0u8; out_len];
336
- self.memory
337
- .read(&self.store, out_ptr, &mut output)
338
- .expect("the output buffer is inside linear memory");
339
- self.free.call(&mut self.store, (ptr, len)).expect("free");
340
- self.free
341
- .call(&mut self.store, (out_ptr as u32, out_len as u32))
342
- .expect("free");
343
- output
344
- }
345
- }
346
-
347
- // ---------------------------------------------------------------------------
348
- // Conformance
349
- // ---------------------------------------------------------------------------
350
-
351
- /// Native evaluation reproduces the committed canonical output for every case.
352
- ///
353
- /// The expectations were recorded from a run in which the native and the WASM
354
- /// implementation already agreed, so this is a full-strength check of native
355
- /// semantics even on a machine with no WASM toolchain.
356
- #[test]
357
- fn native_matches_the_committed_expectations() {
358
- let mut divergent = Vec::new();
359
- for case in &corpus().cases {
360
- let produced = String::from_utf8(evaluate_canonical(&case.bytes())).unwrap();
361
- if produced != case.expected() {
362
- divergent.push(format!(
363
- "{}\n expected: {}\n native: {}",
364
- case.name,
365
- case.expected(),
366
- produced
367
- ));
368
- }
369
- }
370
- assert!(
371
- divergent.is_empty(),
372
- "native contract diverged from the committed expectations:\n{}",
373
- divergent.join("\n")
374
- );
375
- }
376
-
377
- /// The proof: one corpus, two implementations, byte-identical canonical output.
378
- #[test]
379
- fn native_and_wasm_agree_on_every_fixture() {
380
- let Some(bytes) = contract_module() else {
381
- return skipped("native_and_wasm_agree_on_every_fixture");
382
- };
383
- let mut wasm = WasmContract::instantiate(bytes);
384
-
385
- println!(
386
- "{:<52} {:<7} {:<7} {}",
387
- "fixture", "native", "wasm", "equal"
388
- );
389
- let mut divergent = Vec::new();
390
- for case in &corpus().cases {
391
- let input = case.bytes();
392
- let native = evaluate_canonical(&input);
393
- let produced = wasm.evaluate(&input);
394
- let equal = native == produced;
395
- println!(
396
- "{:<52} {:<7} {:<7} {}",
397
- case.name,
398
- summarize(&native),
399
- summarize(&produced),
400
- if equal { "yes" } else { "NO" }
401
- );
402
- if !equal {
403
- divergent.push(format!(
404
- "{}\n input: {}\n native: {}\n wasm: {}",
405
- case.name,
406
- String::from_utf8_lossy(&input),
407
- String::from_utf8_lossy(&native),
408
- String::from_utf8_lossy(&produced)
409
- ));
410
- }
411
- }
412
- assert!(
413
- divergent.is_empty(),
414
- "native and WASM contract implementations diverged:\n{}",
415
- divergent.join("\n")
416
- );
417
- }
418
-
419
- /// The WASM implementation reproduces the committed canonical output too.
420
- ///
421
- /// Separate from the pairwise comparison on purpose: two implementations can
422
- /// agree with each other while both having moved.
423
- #[test]
424
- fn wasm_matches_the_committed_expectations() {
425
- let Some(bytes) = contract_module() else {
426
- return skipped("wasm_matches_the_committed_expectations");
427
- };
428
- let mut wasm = WasmContract::instantiate(bytes);
429
- for case in &corpus().cases {
430
- assert_eq!(
431
- String::from_utf8(wasm.evaluate(&case.bytes())).unwrap(),
432
- case.expected(),
433
- "WASM contract diverged from the committed expectation for {}",
434
- case.name
435
- );
436
- }
437
- }
438
-
439
- /// Both implementations answer for the same contract at the same version.
440
- #[test]
441
- fn both_implementations_declare_the_same_contract_version() {
442
- let Some(bytes) = contract_module() else {
443
- return skipped("both_implementations_declare_the_same_contract_version");
444
- };
445
- assert_eq!(WasmContract::version(bytes), TRIGGER_MATCH_CONTRACT_VERSION);
446
- for case in &corpus().cases {
447
- let output: Value = serde_json::from_slice(&evaluate_canonical(&case.bytes())).unwrap();
448
- assert_eq!(output["contract"], TRIGGER_MATCH_CONTRACT_ID);
449
- assert_eq!(output["version"], TRIGGER_MATCH_CONTRACT_VERSION);
450
- }
451
- }
452
-
453
- /// A short rendering of an output, for the printed conformance table.
454
- fn summarize(output: &[u8]) -> String {
455
- let parsed: Value = match serde_json::from_slice(output) {
456
- Ok(value) => value,
457
- Err(_) => return "?".into(),
458
- };
459
- if let Some(matched) = parsed["outcome"]["matched"].as_bool() {
460
- return if matched { "match" } else { "no-match" }.into();
461
- }
462
- "error".into()
463
- }
464
-
465
- // ---------------------------------------------------------------------------
466
- // Determinism
467
- // ---------------------------------------------------------------------------
468
-
469
- /// The same input, in the same instance, produces the same output every time.
470
- #[test]
471
- fn wasm_is_deterministic_across_repeated_calls() {
472
- let Some(bytes) = contract_module() else {
473
- return skipped("wasm_is_deterministic_across_repeated_calls");
474
- };
475
- let mut wasm = WasmContract::instantiate(bytes);
476
- for case in &corpus().cases {
477
- let input = case.bytes();
478
- let first = wasm.evaluate(&input);
479
- for round in 1..16 {
480
- assert_eq!(
481
- wasm.evaluate(&input),
482
- first,
483
- "{} changed answer on repeat {round} within one instance",
484
- case.name
485
- );
486
- }
487
- }
488
- }
489
-
490
- /// The same input produces the same output in a fresh instance.
491
- ///
492
- /// Each instance gets its own `Store`, so its linear memory, allocator state
493
- /// and call history all start over. An implementation that carried state
494
- /// between decisions would show it here.
495
- #[test]
496
- fn wasm_is_deterministic_across_fresh_instances() {
497
- let Some(bytes) = contract_module() else {
498
- return skipped("wasm_is_deterministic_across_fresh_instances");
499
- };
500
- let mut baseline = WasmContract::instantiate(bytes);
501
- let expected: Vec<Vec<u8>> = corpus()
502
- .cases
503
- .iter()
504
- .map(|case| baseline.evaluate(&case.bytes()))
505
- .collect();
506
-
507
- for round in 0..4 {
508
- let mut fresh = WasmContract::instantiate(bytes);
509
- for (case, want) in corpus().cases.iter().zip(&expected) {
510
- assert_eq!(
511
- &fresh.evaluate(&case.bytes()),
512
- want,
513
- "{} changed answer in fresh instance {round}",
514
- case.name
515
- );
516
- }
517
- }
518
- }
519
-
520
- /// Order of evaluation does not affect any answer.
521
- ///
522
- /// The corpus is replayed forwards and backwards through one instance. A
523
- /// decision that depended on what had been evaluated before would differ.
524
- #[test]
525
- fn wasm_answers_do_not_depend_on_evaluation_order() {
526
- let Some(bytes) = contract_module() else {
527
- return skipped("wasm_answers_do_not_depend_on_evaluation_order");
528
- };
529
- let mut forwards = WasmContract::instantiate(bytes);
530
- let ordered: Vec<Vec<u8>> = corpus()
531
- .cases
532
- .iter()
533
- .map(|case| forwards.evaluate(&case.bytes()))
534
- .collect();
535
-
536
- let mut backwards = WasmContract::instantiate(bytes);
537
- for (index, case) in corpus().cases.iter().enumerate().rev() {
538
- assert_eq!(
539
- backwards.evaluate(&case.bytes()),
540
- ordered[index],
541
- "{} answered differently when the corpus ran in reverse",
542
- case.name
543
- );
544
- }
545
- }
546
-
547
- /// The same input produces the same output in a separate OS process.
548
- ///
549
- /// `cargo test` may run the tests above in one process on one thread pool.
550
- /// This re-runs the corpus in a child process with an independently created
551
- /// engine, which is the strongest process boundary this harness can establish
552
- /// without shipping a second binary.
553
- #[test]
554
- fn wasm_is_deterministic_across_process_boundaries() {
555
- let Some(bytes) = contract_module() else {
556
- return skipped("wasm_is_deterministic_across_process_boundaries");
557
- };
558
- let mut here = WasmContract::instantiate(bytes);
559
- let mine: Vec<Vec<u8>> = corpus()
560
- .cases
561
- .iter()
562
- .map(|case| here.evaluate(&case.bytes()))
563
- .collect();
564
-
565
- let executable = std::env::current_exe().expect("the test binary has a path");
566
- let output = Command::new(executable)
567
- .args([
568
- "--exact",
569
- "wasm_is_deterministic_across_fresh_instances",
570
- "--nocapture",
571
- ])
572
- .output()
573
- .expect("the test binary re-runs");
574
- assert!(
575
- output.status.success(),
576
- "the corpus did not reproduce in a separate process:\n{}",
577
- String::from_utf8_lossy(&output.stdout)
578
- );
579
-
580
- // And the parent's own answers still match the committed expectations, so
581
- // the child agreeing with itself is not the whole claim.
582
- for (case, produced) in corpus().cases.iter().zip(&mine) {
583
- assert_eq!(String::from_utf8_lossy(produced), case.expected());
584
- }
585
- }
586
-
587
- // ---------------------------------------------------------------------------
588
- // Runtime restrictions
589
- // ---------------------------------------------------------------------------
590
-
591
- /// The module imports nothing.
592
- ///
593
- /// This is the whole capability argument and it is deliberately narrow. A
594
- /// WebAssembly module reaches the outside world only through its imports.
595
- /// With an empty import section there is no clock, no filesystem handle, no
596
- /// socket and no entropy source in scope — not because they were denied at
597
- /// call time, but because no such function is linked. It says nothing about
598
- /// hostile modules, which is a different claim this suite does not make.
599
- #[test]
600
- fn wasm_module_declares_no_imports() {
601
- let Some(bytes) = contract_module() else {
602
- return skipped("wasm_module_declares_no_imports");
603
- };
604
- let engine = wasmi::Engine::default();
605
- let module = wasmi::Module::new(&engine, bytes).expect("the module validates");
606
- let imports: Vec<String> = module
607
- .imports()
608
- .map(|import: wasmi::ImportType| format!("{}::{}", import.module(), import.name()))
609
- .collect();
610
- assert!(
611
- imports.is_empty(),
612
- "the contract module must import nothing; it imports {imports:?}"
613
- );
614
- }
615
-
616
- /// An empty linker instantiates the module.
617
- ///
618
- /// The companion to the import check, from the host's side: nothing is
619
- /// provided, and instantiation still succeeds, so no capability is being
620
- /// supplied implicitly by the runtime.
621
- #[test]
622
- fn wasm_module_instantiates_with_no_host_functions() {
623
- let Some(bytes) = contract_module() else {
624
- return skipped("wasm_module_instantiates_with_no_host_functions");
625
- };
626
- let engine = wasmi::Engine::default();
627
- let module = wasmi::Module::new(&engine, bytes).unwrap();
628
- let mut store = wasmi::Store::new(&engine, ());
629
- wasmi::Linker::<()>::new(&engine)
630
- .instantiate_and_start(&mut store, &module)
631
- .expect("no host function is required");
632
- }
633
-
634
- /// The module exports the contract ABI and nothing that could act on FeltDB.
635
- ///
636
- /// The exported surface is memory plus four functions. There is no export that
637
- /// writes state, creates work, or names a FeltDB concept: the module's only
638
- /// possible effect on FeltDB is the bytes it returns.
639
- #[test]
640
- fn wasm_module_exports_only_the_contract_abi() {
641
- let Some(bytes) = contract_module() else {
642
- return skipped("wasm_module_exports_only_the_contract_abi");
643
- };
644
- let engine = wasmi::Engine::default();
645
- let module = wasmi::Module::new(&engine, bytes).unwrap();
646
- let mut functions: Vec<String> = module
647
- .exports()
648
- .filter(|export| export.ty().func().is_some())
649
- .map(|export| export.name().to_string())
650
- .collect();
651
- functions.sort();
652
- assert_eq!(
653
- functions,
654
- vec![
655
- "contract_alloc".to_string(),
656
- "contract_evaluate".to_string(),
657
- "contract_free".to_string(),
658
- "contract_version".to_string(),
659
- ]
660
- );
661
- }
662
-
663
- /// WASI is not present, under any of the names a runtime would look for.
664
- #[test]
665
- fn wasm_module_requires_no_wasi() {
666
- let Some(bytes) = contract_module() else {
667
- return skipped("wasm_module_requires_no_wasi");
668
- };
669
- let text = String::from_utf8_lossy(bytes);
670
- for forbidden in [
671
- "wasi_snapshot_preview1",
672
- "wasi_unstable",
673
- "clock_time_get",
674
- "random_get",
675
- "fd_read",
676
- "fd_write",
677
- "sock_recv",
678
- "path_open",
679
- ] {
680
- assert!(
681
- !text.contains(forbidden),
682
- "the contract module names {forbidden}, which it has no reason to"
683
- );
684
- }
685
- }
686
-
687
- /// The contract never traps, on any corpus input.
688
- ///
689
- /// A trap would be a runtime-specific outcome rather than a contract decision,
690
- /// and the two implementations could not be compared through it.
691
- #[test]
692
- fn wasm_contract_returns_a_decision_for_every_input_including_malformed() {
693
- let Some(bytes) = contract_module() else {
694
- return skipped("wasm_contract_returns_a_decision_for_every_input_including_malformed");
695
- };
696
- let mut wasm = WasmContract::instantiate(bytes);
697
- for case in &corpus().cases {
698
- let output = wasm.evaluate(&case.bytes());
699
- let parsed: Value =
700
- serde_json::from_slice(&output).expect("every output is canonical JSON");
701
- assert_eq!(parsed["contract"], TRIGGER_MATCH_CONTRACT_ID);
702
- assert!(
703
- parsed.get("outcome").is_some() ^ parsed.get("error").is_some(),
704
- "{} produced neither or both of outcome and error",
705
- case.name
706
- );
707
- }
708
- // Inputs no fixture would contain, to show the trap-freedom is not a
709
- // property of the corpus.
710
- for hostile in [
711
- vec![0u8; 0],
712
- vec![0xff; 64],
713
- b"{".repeat(4096),
714
- b"[".repeat(4096),
715
- vec![b'"'; 1024],
716
- ] {
717
- let output = wasm.evaluate(&hostile);
718
- assert!(serde_json::from_slice::<Value>(&output).is_ok());
719
- }
720
- }
721
-
722
- // ---------------------------------------------------------------------------
723
- // Mutation: does the suite actually detect divergence?
724
- // ---------------------------------------------------------------------------
725
-
726
- /// A deliberate change to the WASM implementation's source.
727
- struct Mutation {
728
- name: &'static str,
729
- file: &'static str,
730
- from: &'static str,
731
- to: &'static str,
732
- }
733
-
734
- const MUTATIONS: &[Mutation] = &[
735
- Mutation {
736
- name: "predicate comparison always succeeds",
737
- file: "src/lib.rs",
738
- from: " if !resolved.equals(predicate.equals) {",
739
- to: " if false {",
740
- },
741
- Mutation {
742
- name: "collection comparison is case-insensitive",
743
- file: "src/lib.rs",
744
- from: " if change.collection != trigger.source_collection {",
745
- to: " if !change.collection.eq_ignore_ascii_case(trigger.source_collection) {",
746
- },
747
- Mutation {
748
- name: "disabled triggers are accepted",
749
- file: "src/lib.rs",
750
- from: " if !trigger.enabled {",
751
- to: " if false {",
752
- },
753
- Mutation {
754
- name: "a null record value counts as absent",
755
- file: "src/lib.rs",
756
- from: " let Some(value) = change.value else {",
757
- to: " let Some(value) = change.value.filter(|value| **value != Json::Null) else {",
758
- },
759
- Mutation {
760
- name: "change kind is never compared",
761
- file: "src/lib.rs",
762
- from: " if change.change_kind != trigger.change_kind {",
763
- to: " if false {",
764
- },
765
- Mutation {
766
- name: "output encoding renames a member",
767
- file: "src/lib.rs",
768
- from: r#"out.extend_from_slice(b"\",\"outcome\":{\"matched\":");"#,
769
- to: r#"out.extend_from_slice(b"\",\"result\":{\"matched\":");"#,
770
- },
771
- Mutation {
772
- name: "integers and floats compare equal",
773
- file: "src/json.rs",
774
- from: " (Json::Float(left), Json::Float(right)) => left == right,",
775
- to: " (Json::Float(left), Json::Float(right)) => left == right,\n (Json::PosInt(left), Json::Float(right)) => *left as f64 == *right,\n (Json::Float(left), Json::PosInt(right)) => *left == *right as f64,",
776
- },
777
- ];
778
-
779
- /// Every deliberate mutation of the WASM contract is caught by the corpus.
780
- ///
781
- /// Without this the conformance suite proves only that two implementations
782
- /// agree, not that agreement was ever in question. Each mutant is built from a
783
- /// copy of the real source with one edit applied, and must fail against the
784
- /// committed expectations.
785
- #[test]
786
- fn deliberate_wasm_mutations_are_detected() {
787
- if contract_module().is_none() {
788
- return skipped("deliberate_wasm_mutations_are_detected");
789
- }
790
- let source = repository_root().join(WASM_CRATE);
791
- let scratch = tempfile::tempdir().expect("a scratch directory");
792
-
793
- let mut undetected = Vec::new();
794
- for mutation in MUTATIONS {
795
- let root = scratch.path().join(mutation.name.replace(' ', "-"));
796
- copy_crate(&source, &root);
797
- let target = root.join(mutation.file);
798
- let original = std::fs::read_to_string(&target).expect("the mutated file exists");
799
- assert!(
800
- original.matches(mutation.from).count() == 1,
801
- "mutation {:?} no longer applies: its anchor is not present exactly once in {}. \
802
- The mutation suite must be updated with the implementation it mutates.",
803
- mutation.name,
804
- mutation.file
805
- );
806
- std::fs::write(&target, original.replace(mutation.from, mutation.to)).unwrap();
807
-
808
- let mut mutant = WasmContract::instantiate(&build_module(&root));
809
- let caught: Vec<&str> = corpus()
810
- .cases
811
- .iter()
812
- .filter(|case| {
813
- String::from_utf8_lossy(&mutant.evaluate(&case.bytes())) != case.expected()
814
- })
815
- .map(|case| case.name.as_str())
816
- .collect();
817
- match caught.first() {
818
- Some(first) => println!(
819
- "mutation {:?} caught by {} fixtures, first: {first}",
820
- mutation.name,
821
- caught.len()
822
- ),
823
- None => undetected.push(mutation.name),
824
- }
825
- }
826
- assert!(
827
- undetected.is_empty(),
828
- "the corpus does not distinguish these mutations from correct behaviour: {undetected:?}"
829
- );
830
- }
831
-
832
- /// Copies the contract crate's source, without its build directory.
833
- fn copy_crate(from: &Path, to: &Path) {
834
- std::fs::create_dir_all(to.join("src")).unwrap();
835
- std::fs::copy(from.join("Cargo.toml"), to.join("Cargo.toml")).unwrap();
836
- for entry in std::fs::read_dir(from.join("src")).unwrap() {
837
- let entry = entry.unwrap();
838
- std::fs::copy(entry.path(), to.join("src").join(entry.file_name())).unwrap();
839
- }
840
- }
841
-
842
- /// Mutating the canonical encoding is detected where the encoding is
843
- /// semantically load-bearing, and not where it is not.
844
- ///
845
- /// This is the honest shape of the result. Three encoder mutations are applied
846
- /// to well-formed cases and fed to both implementations:
847
- ///
848
- /// - conflating an absent record value with `null` changes a decision, and
849
- /// both implementations change it together, so only the committed
850
- /// expectations catch it;
851
- /// - dropping the contract identity turns every case into an input error,
852
- /// likewise caught by the expectations; and
853
- /// - emitting members in non-sorted order changes nothing, because JSON object
854
- /// equality is by member and both parsers agree. The suite correctly does
855
- /// not flag it, and the contract does not claim byte-canonical input.
856
- #[test]
857
- fn canonicalization_mutations_are_detected_where_they_matter() {
858
- let cases: Vec<(&Case, Value)> = corpus()
859
- .cases
860
- .iter()
861
- .filter_map(|case| {
862
- let input = case.input.as_ref()?;
863
- Some((case, serde_json::to_value(input).unwrap()))
864
- })
865
- .collect();
866
- assert!(cases.len() > 40, "most of the corpus is typed input");
867
-
868
- // 1. Absence conflated with null.
869
- let mut changed = 0;
870
- for (case, encoded) in &cases {
871
- let mut mutated = encoded.clone();
872
- if mutated["change"].get("value").is_none() {
873
- mutated["change"]["value"] = Value::Null;
874
- }
875
- let produced = String::from_utf8(evaluate_canonical(
876
- serde_json::to_vec(&mutated).unwrap().as_slice(),
877
- ))
878
- .unwrap();
879
- if produced != case.expected() {
880
- changed += 1;
881
- }
882
- }
883
- assert!(
884
- changed > 0,
885
- "an encoder that writes null for an absent record value must change a decision"
886
- );
887
-
888
- // 2. Contract identity dropped.
889
- for (case, encoded) in cases.iter().take(1) {
890
- let mut mutated = encoded.clone();
891
- mutated.as_object_mut().unwrap().remove("contract");
892
- let produced = String::from_utf8(evaluate_canonical(
893
- serde_json::to_vec(&mutated).unwrap().as_slice(),
894
- ))
895
- .unwrap();
896
- assert_ne!(produced, case.expected());
897
- assert!(produced.contains("CONTRACT_INPUT_INVALID"));
898
- }
899
-
900
- // 3. Member order, which carries no meaning.
901
- for (case, encoded) in &cases {
902
- let produced =
903
- String::from_utf8(evaluate_canonical(encode_reversed(encoded).as_bytes())).unwrap();
904
- assert_eq!(
905
- produced,
906
- case.expected(),
907
- "{} changed answer under a reordered encoding; object member order is \
908
- not part of this contract's semantics",
909
- case.name
910
- );
911
- }
912
- }
913
-
914
- /// Encodes a value with every object's members in reverse sorted order.
915
- ///
916
- /// `serde_json::Map` re-sorts on insert, so a non-canonical member order has to
917
- /// be produced as text rather than as a value.
918
- fn encode_reversed(value: &Value) -> String {
919
- match value {
920
- Value::Object(members) => {
921
- let mut parts: Vec<String> = members
922
- .iter()
923
- .map(|(key, value)| {
924
- format!(
925
- "{}:{}",
926
- serde_json::to_string(key).unwrap(),
927
- encode_reversed(value)
928
- )
929
- })
930
- .collect();
931
- parts.reverse();
932
- format!("{{{}}}", parts.join(","))
933
- }
934
- Value::Array(items) => format!(
935
- "[{}]",
936
- items
937
- .iter()
938
- .map(encode_reversed)
939
- .collect::<Vec<_>>()
940
- .join(",")
941
- ),
942
- other => serde_json::to_string(other).unwrap(),
943
- }
944
- }
945
-
946
- // ---------------------------------------------------------------------------
947
- // Corpus maintenance
948
- // ---------------------------------------------------------------------------
949
-
950
- /// Records the canonical output of every case, after both implementations agree.
951
- ///
952
- /// Ignored by default: it writes the corpus. Run it deliberately with
953
- /// `FELTDB_TRIGGER_CONTRACT_BLESS=1 cargo test -p feltdb --test
954
- /// trigger_contract_conformance -- bless --ignored`. It refuses to record
955
- /// anything unless the WASM implementation is available and agrees, so an
956
- /// expectation can never be blessed from one implementation alone.
957
- #[test]
958
- #[ignore = "rewrites the committed corpus"]
959
- fn bless() {
960
- assert_eq!(
961
- std::env::var("FELTDB_TRIGGER_CONTRACT_BLESS").as_deref(),
962
- Ok("1"),
963
- "set FELTDB_TRIGGER_CONTRACT_BLESS=1 to rewrite the corpus"
964
- );
965
- let bytes = contract_module().expect(
966
- "blessing requires the WASM implementation: an expectation recorded from the native \
967
- implementation alone would prove nothing",
968
- );
969
- let mut wasm = WasmContract::instantiate(bytes);
970
-
971
- let mut document: Value =
972
- serde_json::from_slice(&std::fs::read(corpus_path()).unwrap()).unwrap();
973
- for (index, case) in corpus().cases.iter().enumerate() {
974
- let input = case.bytes();
975
- let native = evaluate_canonical(&input);
976
- let produced = wasm.evaluate(&input);
977
- assert_eq!(
978
- String::from_utf8_lossy(&native),
979
- String::from_utf8_lossy(&produced),
980
- "refusing to record {}: the implementations disagree",
981
- case.name
982
- );
983
- if case.input.is_some() {
984
- document["cases"][index]["canonical_input"] =
985
- Value::String(String::from_utf8(input).unwrap());
986
- }
987
- document["cases"][index]["expected_output"] =
988
- Value::String(String::from_utf8(native).unwrap());
989
- }
990
- let mut text = serde_json::to_string_pretty(&document).unwrap();
991
- text.push('\n');
992
- std::fs::write(corpus_path(), text).unwrap();
993
- println!("recorded {} expectations", corpus().cases.len());
994
- }