@feltdb/core 0.15.0 → 0.16.1

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 (248) 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 +20 -2
  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 +356 -24
  17. package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +10 -1
  18. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  19. package/dist/db.d.ts +11 -1
  20. package/dist/db.d.ts.map +1 -1
  21. package/dist/db.js +15 -2
  22. package/dist/error-codes.d.ts +5 -0
  23. package/dist/error-codes.d.ts.map +1 -1
  24. package/dist/error-codes.js +9 -1
  25. package/dist/http-db.d.ts +16 -0
  26. package/dist/http-db.d.ts.map +1 -1
  27. package/dist/http-db.js +36 -6
  28. package/dist/managed-recovery.d.ts +6 -0
  29. package/dist/managed-recovery.d.ts.map +1 -1
  30. package/dist/managed-recovery.js +9 -0
  31. package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-BUIxCuTR.js} +1 -1
  32. package/dist/studio-app/assets/feltdb_wasm_bg-C3D6y6DK.wasm +0 -0
  33. package/dist/studio-app/assets/index-BhXYAhgS.js +37 -0
  34. package/dist/studio-app/index.html +1 -1
  35. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  36. package/dist/wasm/package.json +1 -1
  37. package/dist/workload.d.ts +55 -3
  38. package/dist/workload.d.ts.map +1 -1
  39. package/dist/workload.js +23 -0
  40. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  41. package/dist/workspace/workspace-connection.js +14 -3
  42. package/package.json +1 -1
  43. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  44. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  45. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  46. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  47. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  48. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  49. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  50. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  51. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  52. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  53. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  54. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  55. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  56. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  57. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  58. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  59. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  60. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  61. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  62. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  63. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  64. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  65. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  66. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  67. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  68. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  69. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  70. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  71. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  72. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  73. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  74. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  75. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  76. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  77. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  78. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  79. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  80. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  81. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  82. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  83. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  84. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  85. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  86. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  87. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  88. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  89. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  90. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  91. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  92. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  93. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  94. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  95. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  96. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  97. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  98. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  99. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  100. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  101. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  102. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  103. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  104. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  105. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  106. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  107. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  108. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  109. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  110. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  111. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
  112. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  113. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  114. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  115. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  116. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  117. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  118. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  119. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  120. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  121. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  122. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  123. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  124. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  125. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  126. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  127. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  128. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  129. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  130. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  131. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  132. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  133. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  134. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  135. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  136. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  137. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  138. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  139. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  140. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  141. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  142. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  143. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  144. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  145. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  146. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  147. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  148. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  149. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  150. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  151. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  152. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  153. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  154. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  155. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  156. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  157. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  158. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  159. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  160. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  161. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
  162. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  163. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  164. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  165. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  166. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  167. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  168. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  169. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  170. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  171. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  172. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  173. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  174. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  175. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  176. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  177. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  178. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  179. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  180. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
  181. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  182. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  183. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  184. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  185. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
  186. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  187. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  188. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  189. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  190. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  191. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  192. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  193. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  194. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  195. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  196. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  197. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  198. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  199. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  200. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  201. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  202. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  203. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  204. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  205. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  206. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  207. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  208. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  209. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  210. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  211. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  212. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  213. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  214. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  215. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  216. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  217. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  218. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  219. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  220. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  221. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  222. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  223. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  224. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  225. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  226. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  227. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  228. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  229. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  230. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  231. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  232. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  233. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  234. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  235. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  236. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  237. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  238. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  239. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  240. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  241. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  242. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  243. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  244. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  245. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  246. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  247. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  248. package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
@@ -1,18 +1,7 @@
1
1
  [workspace]
2
- members = ["crates/feltdb", "crates/feltdb-client", "crates/feltdb-server", "crates/feltdb-wasm"]
3
- # The deterministic contracts' WASM implementations are their own
4
- # dependency-free workspaces: they are only ever built for
5
- # wasm32-unknown-unknown, and a second implementation that inherited this
6
- # workspace's dependencies would stop being an independent one.
7
- exclude = [
8
- "crates/feltdb-public-api-check",
9
- "crates/feltdb-trigger-contract-wasm",
10
- "crates/feltdb-state-diff-contract-wasm",
11
- "crates/feltdb-state-conflict-contract-wasm",
12
- "crates/feltdb-agent-transition-contract-wasm",
13
- ]
2
+ members = ["crates/feltdb-server", "crates/feltdb-wasm", "crates/feltdb-client"]
14
3
  resolver = "2"
15
4
 
16
5
  [workspace.package]
17
- version = "0.3.0"
6
+ version = "0.4.2"
18
7
  edition = "2021"
@@ -16,7 +16,7 @@ exclude = ["tests/", "target/"]
16
16
  # The wire types are the server's own: the request and response documents are defined once, in
17
17
  # `feltdb`, and both sides serialize the same structs. Exact, because the modules they come from are
18
18
  # not yet covered by `feltdb`'s compatibility policy; the workspace releases both at one version.
19
- feltdb = { version = "=0.3.0", path = "../feltdb" }
19
+ feltdb = { version = "=0.4.2" }
20
20
  reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
21
21
  serde = { version = "1", features = ["derive"] }
22
22
  serde_json = "1"
@@ -24,7 +24,7 @@ hmac = "0.12"
24
24
  # Exact: the server uses `unstable-internals`, which carries no semver promise, so a release of
25
25
  # this crate is built against the one `feltdb` release it was tested with. The workspace shares one
26
26
  # version, so this moves with every release (a mismatch fails to resolve, it cannot drift silently).
27
- feltdb = { version = "=0.3.0", path = "../feltdb", features = ["unstable-internals"] }
27
+ feltdb = { version = "=0.4.2", features = ["unstable-internals"] }
28
28
  futures-core = "0.3"
29
29
  rand = "0.8"
30
30
  ring = "0.17"
@@ -1094,12 +1094,22 @@ async fn push_sync(
1094
1094
  });
1095
1095
  continue;
1096
1096
  }
1097
- let target_revision = state
1097
+ // The production contract, never one the client names: with nothing promoted there is
1098
+ // no contract to write under.
1099
+ let Some(target_revision) = state
1098
1100
  .applications
1099
1101
  .pointers(&operation.application_id)
1100
1102
  .get("production")
1101
1103
  .cloned()
1102
- .unwrap_or_else(|| operation.application_revision.clone());
1104
+ else {
1105
+ rejections.push(SyncRejection {
1106
+ operation_id: operation.operation_id,
1107
+ code: "SYNC_SCHEMA_MISMATCH".into(),
1108
+ message: "application environment has no active contract".into(),
1109
+ retryable: false,
1110
+ });
1111
+ continue;
1112
+ };
1103
1113
  let Some(revision) = state.applications.revision(
1104
1114
  &operation.tenant_id,
1105
1115
  &operation.application_id,
@@ -1228,6 +1238,24 @@ async fn push_sync(
1228
1238
  .acquire()
1229
1239
  .await
1230
1240
  .expect("transaction admission is never closed");
1241
+ // A promotion between resolving the contract and taking the permit: refuse, write nothing.
1242
+ if ensure_revision_current(
1243
+ &state,
1244
+ &operation.application_id,
1245
+ "production",
1246
+ &revision.revision_id,
1247
+ true,
1248
+ )
1249
+ .is_err()
1250
+ {
1251
+ rejections.push(SyncRejection {
1252
+ operation_id: operation.operation_id,
1253
+ code: "SYNC_SCHEMA_MISMATCH".into(),
1254
+ message: "the production contract changed; nothing was written".into(),
1255
+ retryable: false,
1256
+ });
1257
+ continue;
1258
+ }
1231
1259
  match execute_transaction(&state.db, &schema, &transaction, None) {
1232
1260
  Ok(committed) => {
1233
1261
  let position = state
@@ -1490,6 +1518,16 @@ struct WorkloadControlRequest {
1490
1518
  reason: Option<String>,
1491
1519
  }
1492
1520
  #[derive(Deserialize)]
1521
+ struct WorkloadDecisionRequest {
1522
+ /// A `workload:approve` grant whose subject is the authenticated caller.
1523
+ grant: feltdb::authorization::SignedGrant,
1524
+ /// The `input_hash` of the work the decider reviewed.
1525
+ input_hash: String,
1526
+ /// Required for a rejection; recorded with the decision.
1527
+ #[serde(default)]
1528
+ reason: Option<String>,
1529
+ }
1530
+ #[derive(Deserialize)]
1493
1531
  struct WorkerHeartbeatRequest {
1494
1532
  heartbeat: WorkerHeartbeat,
1495
1533
  grant: feltdb::authorization::SignedGrant,
@@ -1554,8 +1592,16 @@ fn workload_error(error: feltdb::workload::WorkloadError) -> ApiError {
1554
1592
  | "WORKLOAD_RETRY_EXHAUSTED"
1555
1593
  | "WORKLOAD_RESULT_CONFLICT"
1556
1594
  | "WORKLOAD_TIMED_OUT"
1595
+ | "WORKLOAD_APPROVAL_PENDING"
1596
+ | "WORKLOAD_REJECTED"
1597
+ | "WORKLOAD_APPROVAL_DECIDED"
1598
+ | "WORKLOAD_APPROVAL_STALE"
1599
+ | "WORKLOAD_APPROVAL_NOT_ACTIONABLE"
1600
+ | "WORKLOAD_APPROVAL_NOT_REQUIRED"
1557
1601
  | "WORKLOAD_CONFLICT" => StatusCode::CONFLICT,
1558
- "WORKLOAD_UNAUTHORIZED" | "WORKLOAD_CAPABILITY_DENIED" => StatusCode::FORBIDDEN,
1602
+ "WORKLOAD_UNAUTHORIZED"
1603
+ | "WORKLOAD_CAPABILITY_DENIED"
1604
+ | "WORKLOAD_APPROVER_NOT_PERMITTED" => StatusCode::FORBIDDEN,
1559
1605
  "WORKLOAD_STORAGE_FAILURE" => StatusCode::SERVICE_UNAVAILABLE,
1560
1606
  _ => StatusCode::UNPROCESSABLE_ENTITY,
1561
1607
  };
@@ -2082,6 +2128,99 @@ async fn retry_workload(
2082
2128
  .map_err(workload_error)?;
2083
2129
  Ok(StatusCode::NO_CONTENT)
2084
2130
  }
2131
+ async fn approve_workload(
2132
+ State(state): State<AppState>,
2133
+ Extension(principal): Extension<Principal>,
2134
+ Path(id): Path<String>,
2135
+ Json(input): Json<WorkloadDecisionRequest>,
2136
+ ) -> Result<(HeaderMap, Json<Value>), ApiError> {
2137
+ decide_workload(
2138
+ &state,
2139
+ &principal,
2140
+ &id,
2141
+ input,
2142
+ feltdb::workload::ApprovalOutcome::Approved,
2143
+ )
2144
+ }
2145
+ async fn reject_workload(
2146
+ State(state): State<AppState>,
2147
+ Extension(principal): Extension<Principal>,
2148
+ Path(id): Path<String>,
2149
+ Json(input): Json<WorkloadDecisionRequest>,
2150
+ ) -> Result<(HeaderMap, Json<Value>), ApiError> {
2151
+ decide_workload(
2152
+ &state,
2153
+ &principal,
2154
+ &id,
2155
+ input,
2156
+ feltdb::workload::ApprovalOutcome::Rejected,
2157
+ )
2158
+ }
2159
+ /// Commit an approval decision. The caller must be the Durable Work
2160
+ /// authority's client, authenticate as the grant's subject, and hold
2161
+ /// `workload:approve` for this workload; the store then applies the
2162
+ /// workload's approval policy (permitted kinds and approvers, never the
2163
+ /// requester) and binds the decision to the reviewed input.
2164
+ fn decide_workload(
2165
+ state: &AppState,
2166
+ principal: &Principal,
2167
+ id: &str,
2168
+ input: WorkloadDecisionRequest,
2169
+ outcome: feltdb::workload::ApprovalOutcome,
2170
+ ) -> Result<(HeaderMap, Json<Value>), ApiError> {
2171
+ require_work_authority(state)?;
2172
+ let workload = state
2173
+ .workloads
2174
+ .lock()
2175
+ .map_err(|_| {
2176
+ ApiError(
2177
+ StatusCode::INTERNAL_SERVER_ERROR,
2178
+ "workload store unavailable".into(),
2179
+ )
2180
+ })?
2181
+ .fresh(id)
2182
+ .map_err(workload_error)?
2183
+ .clone();
2184
+ let decider = grant_subject(principal);
2185
+ workload_decision(
2186
+ state,
2187
+ decider.clone(),
2188
+ &workload.tenant_id,
2189
+ &workload.application_id,
2190
+ format!("flow://{}/workloads/{id}", workload.application_id),
2191
+ "workload:approve",
2192
+ &input.grant,
2193
+ )?;
2194
+ let mut store = state.workloads.lock().map_err(|_| {
2195
+ ApiError(
2196
+ StatusCode::INTERNAL_SERVER_ERROR,
2197
+ "workload store unavailable".into(),
2198
+ )
2199
+ })?;
2200
+ let (decision, replayed) = store
2201
+ .decide(
2202
+ id,
2203
+ &decider,
2204
+ outcome,
2205
+ input.reason.as_deref(),
2206
+ &input.grant.grant.id,
2207
+ &input.input_hash,
2208
+ unix_seconds_i64(),
2209
+ )
2210
+ .map_err(workload_error)?;
2211
+ let state_now = store.get(id).map_err(workload_error)?.state;
2212
+ let mut headers = HeaderMap::new();
2213
+ if replayed {
2214
+ headers.insert(
2215
+ "idempotent-replay",
2216
+ axum::http::HeaderValue::from_static("true"),
2217
+ );
2218
+ }
2219
+ Ok((
2220
+ headers,
2221
+ Json(json!({"decision": decision, "state": state_now})),
2222
+ ))
2223
+ }
2085
2224
  async fn workload_history(
2086
2225
  State(state): State<AppState>,
2087
2226
  Extension(principal): Extension<Principal>,
@@ -4610,6 +4749,7 @@ async fn promote_environment(
4610
4749
  Path((application_id, environment)): Path<(String, String)>,
4611
4750
  Json(input): Json<MoveEnvironmentRequest>,
4612
4751
  ) -> Result<Json<Value>, ApiError> {
4752
+ let _drain = drain_state_commits(&state).await;
4613
4753
  Ok(Json(move_environment(
4614
4754
  &state,
4615
4755
  &principal,
@@ -4628,6 +4768,7 @@ async fn rollback_environment(
4628
4768
  Path((application_id, environment)): Path<(String, String)>,
4629
4769
  Json(input): Json<RollbackEnvironmentRequest>,
4630
4770
  ) -> Result<Json<Value>, ApiError> {
4771
+ let _drain = drain_state_commits(&state).await;
4631
4772
  Ok(Json(move_environment(
4632
4773
  &state,
4633
4774
  &principal,
@@ -4646,6 +4787,7 @@ async fn promote_application_revision(
4646
4787
  Path((application_id, revision_id)): Path<(String, String)>,
4647
4788
  Json(input): Json<PromoteRevisionRequest>,
4648
4789
  ) -> Result<Json<Value>, ApiError> {
4790
+ let _drain = drain_state_commits(&state).await;
4649
4791
  Ok(Json(move_environment(
4650
4792
  &state,
4651
4793
  &principal,
@@ -4752,6 +4894,97 @@ fn runtime_inventory(
4752
4894
  enforce_availability: true,
4753
4895
  })
4754
4896
  }
4897
+ /// The contract a state request runs under: the revision promoted to `environment`, and
4898
+ /// only that one.
4899
+ ///
4900
+ /// A client names the revision it discovered. Any other revision (one a later promotion
4901
+ /// superseded, or one committed but never promoted) is refused with `REVISION_NOT_ACTIVE`,
4902
+ /// naming the active one, before anything is read or written. Accepting it would apply a
4903
+ /// contract that is not the environment's: fields a newer contract removed, policies it
4904
+ /// tightened, or a revision whose promotion was never approved. The client re-discovers and
4905
+ /// sends its next request under the active revision; nothing is retried under another contract.
4906
+ fn active_revision_contract(
4907
+ state: &AppState,
4908
+ actor: &str,
4909
+ application_id: &str,
4910
+ revision_id: &str,
4911
+ environment: &str,
4912
+ ) -> Result<ApplicationRuntimeContract, ApiError> {
4913
+ application_scope(state, actor, application_id, "application:revision:read")?;
4914
+ let active = state
4915
+ .applications
4916
+ .pointers(application_id)
4917
+ .get(environment)
4918
+ .cloned()
4919
+ .ok_or(ApiError(
4920
+ StatusCode::NOT_FOUND,
4921
+ "application environment has no active contract".into(),
4922
+ ))?;
4923
+ if active != revision_id {
4924
+ return Err(revision_not_active(revision_id, &active, environment));
4925
+ }
4926
+ revision_contract(state, actor, application_id, revision_id, environment)
4927
+ }
4928
+ /// A revision runtime runs in an environment that has a promoted contract only as that
4929
+ /// contract. An environment nothing is promoted to (a preview) runs any committed revision.
4930
+ fn runtime_revision_contract(
4931
+ state: &AppState,
4932
+ actor: &str,
4933
+ application_id: &str,
4934
+ revision_id: &str,
4935
+ environment: &str,
4936
+ ) -> Result<ApplicationRuntimeContract, ApiError> {
4937
+ application_scope(state, actor, application_id, "application:revision:read")?;
4938
+ if let Some(active) = state.applications.pointers(application_id).get(environment) {
4939
+ if active != revision_id {
4940
+ return Err(revision_not_active(revision_id, active, environment));
4941
+ }
4942
+ }
4943
+ revision_contract(state, actor, application_id, revision_id, environment)
4944
+ }
4945
+ /// Commits that write application state each hold one of these permits.
4946
+ const STATE_TRANSACTION_PERMITS: u32 = 2;
4947
+ /// Hold every commit permit while an environment's contract pointer moves: the move waits for
4948
+ /// commits in flight, and no commit starts until it is done. With `ensure_revision_current`
4949
+ /// checked under a permit, a commit and a promotion are ordered: a commit never lands under a
4950
+ /// contract a promotion has already replaced.
4951
+ async fn drain_state_commits(state: &AppState) -> tokio::sync::SemaphorePermit<'_> {
4952
+ state
4953
+ .state_transaction_admission
4954
+ .acquire_many(STATE_TRANSACTION_PERMITS)
4955
+ .await
4956
+ .expect("transaction admission is never closed")
4957
+ }
4958
+ /// Re-check, while holding a commit permit, that `revision_id` is still the environment's
4959
+ /// contract (`require_promoted`: an environment with nothing promoted refuses too).
4960
+ fn ensure_revision_current(
4961
+ state: &AppState,
4962
+ application_id: &str,
4963
+ environment: &str,
4964
+ revision_id: &str,
4965
+ require_promoted: bool,
4966
+ ) -> Result<(), ApiError> {
4967
+ match state.applications.pointers(application_id).get(environment) {
4968
+ Some(active) if active != revision_id => Err(revision_not_active(revision_id, active, environment)),
4969
+ None if require_promoted => Err(ApiError(
4970
+ StatusCode::NOT_FOUND,
4971
+ "application environment has no active contract".into(),
4972
+ )),
4973
+ _ => Ok(()),
4974
+ }
4975
+ }
4976
+ fn revision_not_active(requested: &str, active: &str, environment: &str) -> ApiError {
4977
+ ApiError::structured(
4978
+ StatusCode::CONFLICT,
4979
+ json!({
4980
+ "code": "REVISION_NOT_ACTIVE",
4981
+ "message": format!("revision {requested} is not the contract active in {environment}; {active} is. Nothing was read or written. Whether to send the request again under the active revision is the caller's decision."),
4982
+ "requested_revision_id": requested,
4983
+ "active_revision_id": active,
4984
+ "environment": environment,
4985
+ }),
4986
+ )
4987
+ }
4755
4988
  fn revision_contract(
4756
4989
  state: &AppState,
4757
4990
  actor: &str,
@@ -7299,7 +7532,7 @@ async fn run_revision_runtime(
7299
7532
  &application_id,
7300
7533
  "application:runtime:run",
7301
7534
  )?;
7302
- let contract = revision_contract(
7535
+ let contract = runtime_revision_contract(
7303
7536
  &state,
7304
7537
  &principal.key_id,
7305
7538
  &application_id,
@@ -7364,7 +7597,7 @@ async fn run_runtime_action(
7364
7597
  Json(input): Json<Value>,
7365
7598
  ) -> Result<Json<Value>, ApiError> {
7366
7599
  application_scope(&state, &principal.key_id, &application_id, "state:write")?;
7367
- let contract = revision_contract(
7600
+ let contract = runtime_revision_contract(
7368
7601
  &state,
7369
7602
  &principal.key_id,
7370
7603
  &application_id,
@@ -7463,6 +7696,7 @@ async fn run_runtime_action(
7463
7696
  .acquire()
7464
7697
  .await
7465
7698
  .expect("transaction admission is never closed");
7699
+ ensure_revision_current(&state, &application_id, &environment.environment, &contract.revision_id, false)?;
7466
7700
  Ok(Json(json!(execute_transaction(
7467
7701
  &state.db,
7468
7702
  &contract.state.schema,
@@ -7494,7 +7728,7 @@ async fn run_runtime_query(
7494
7728
  Query(environment): Query<RuntimeEnvironmentRequest>,
7495
7729
  ) -> Result<Json<Value>, ApiError> {
7496
7730
  application_scope(&state, &principal.key_id, &application_id, "state:read")?;
7497
- let contract = revision_contract(
7731
+ let contract = runtime_revision_contract(
7498
7732
  &state,
7499
7733
  &principal.key_id,
7500
7734
  &application_id,
@@ -7596,7 +7830,7 @@ async fn run_runtime_workflow(
7596
7830
  Json(input): Json<StartRun>,
7597
7831
  ) -> Result<(StatusCode, Json<Value>), ApiError> {
7598
7832
  application_scope(&state, &principal.key_id, &application_id, "workflows:run")?;
7599
- let contract = revision_contract(
7833
+ let contract = runtime_revision_contract(
7600
7834
  &state,
7601
7835
  &principal.key_id,
7602
7836
  &application_id,
@@ -7853,7 +8087,7 @@ async fn get_state_version(
7853
8087
  &target.application_id,
7854
8088
  "state:read",
7855
8089
  )?;
7856
- let contract = revision_contract(
8090
+ let contract = active_revision_contract(
7857
8091
  &state,
7858
8092
  &principal.key_id,
7859
8093
  &target.application_id,
@@ -7895,7 +8129,7 @@ async fn execute_canonical_query(
7895
8129
  "state:read",
7896
8130
  )?;
7897
8131
  require_bound_tenant(&headers, &tenant)?;
7898
- let contract = revision_contract(
8132
+ let contract = active_revision_contract(
7899
8133
  &state,
7900
8134
  &principal.key_id,
7901
8135
  &input.application_id,
@@ -8204,7 +8438,7 @@ async fn capture_canonical_read_basis(
8204
8438
  "state:read",
8205
8439
  )?;
8206
8440
  require_bound_tenant(&headers, &tenant)?;
8207
- let contract = revision_contract(
8441
+ let contract = active_revision_contract(
8208
8442
  &state,
8209
8443
  &principal.key_id,
8210
8444
  &input.application_id,
@@ -8235,7 +8469,7 @@ async fn execute_canonical_transaction(
8235
8469
  "state:write",
8236
8470
  )?;
8237
8471
  require_bound_tenant(&headers, &tenant)?;
8238
- let contract = revision_contract(
8472
+ let contract = active_revision_contract(
8239
8473
  &state,
8240
8474
  &principal.key_id,
8241
8475
  &input.application_id,
@@ -8324,6 +8558,7 @@ async fn execute_canonical_transaction(
8324
8558
  .acquire()
8325
8559
  .await
8326
8560
  .expect("transaction admission is never closed");
8561
+ ensure_revision_current(&state, &input.application_id, &input.environment, &contract.revision_id, true)?;
8327
8562
  let result = execute_transaction_with_collection_policies(
8328
8563
  &state.db,
8329
8564
  &contract.state.schema,
@@ -9057,7 +9292,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9057
9292
  peer_client,
9058
9293
  peer_token,
9059
9294
  lifecycle_lock: Arc::new(tokio::sync::Mutex::new(())),
9060
- state_transaction_admission: Arc::new(tokio::sync::Semaphore::new(2)),
9295
+ state_transaction_admission: Arc::new(tokio::sync::Semaphore::new(STATE_TRANSACTION_PERMITS as usize)),
9061
9296
  worker_enabled: config.worker_enabled,
9062
9297
  work_authority: config.work_authority,
9063
9298
  work_authority_conflict: Arc::new(std::sync::atomic::AtomicBool::new(false)),
@@ -9535,6 +9770,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9535
9770
  "/v1/workloads/{id}/retry",
9536
9771
  axum::routing::post(retry_workload),
9537
9772
  )
9773
+ .route(
9774
+ "/v1/workloads/{id}/approve",
9775
+ axum::routing::post(approve_workload),
9776
+ )
9777
+ .route(
9778
+ "/v1/workloads/{id}/reject",
9779
+ axum::routing::post(reject_workload),
9780
+ )
9538
9781
  .route("/v1/workloads/{id}/history", get(workload_history))
9539
9782
  .route("/v1/workloads/{id}/result", get(workload_result))
9540
9783
  .route("/v1/workloads/{id}/eligible-workers", get(eligible_workers))
@@ -13813,7 +14056,7 @@ async fn search_collection(
13813
14056
  Path(collection): Path<String>,
13814
14057
  Json(request): Json<SearchRequest>,
13815
14058
  ) -> Result<Json<Vec<RecordResponse>>, ApiError> {
13816
- validate_segment(&collection)?;
14059
+ validate_collection_name(&collection)?;
13817
14060
  authorize_capability_read(&state, &CapabilityAccess::Principal(&principal), &collection)?;
13818
14061
  if request.query.len() > 512 || request.limit == 0 || request.limit > 500 {
13819
14062
  return Err(ApiError(
@@ -15031,19 +15274,16 @@ async fn execute_bounded_query(
15031
15274
  let request: BoundedQueryRequest = serde_json::from_value(request)
15032
15275
  .map_err(|error| bounded_query_error("INVALID_QUERY", error.to_string()))?;
15033
15276
  let execution = bounded_query_execution(&headers)?;
15034
- validate_segment(&request.collection)?;
15277
+ validate_collection_name(&request.collection)?;
15035
15278
  if request.limit == 0 || request.limit > MAX_BOUNDED_QUERY_LIMIT {
15036
15279
  return Err(bounded_query_error(
15037
15280
  "INVALID_QUERY",
15038
15281
  format!("limit must be between 1 and {MAX_BOUNDED_QUERY_LIMIT}"),
15039
15282
  ));
15040
15283
  }
15041
- if request.order_by.is_empty() {
15042
- return Err(bounded_query_error(
15043
- "INVALID_QUERY",
15044
- "orderBy must contain at least one field",
15045
- ));
15046
- }
15284
+ // `orderBy` is optional, as on every embedded runtime: the record id is always the
15285
+ // final tie-breaker below, so with no ordering field the order is record id
15286
+ // (adrs/canonical-durable-state-api.md, Q1).
15047
15287
  for order in &request.order_by {
15048
15288
  if order.field.trim().is_empty() || !matches!(order.direction.as_str(), "asc" | "desc") {
15049
15289
  return Err(bounded_query_error(
@@ -15442,7 +15682,7 @@ async fn list_records(
15442
15682
  "system collection requires proposal service".into(),
15443
15683
  ));
15444
15684
  }
15445
- validate_segment(&collection)?;
15685
+ validate_collection_name(&collection)?;
15446
15686
  let rows = state.db.list_collection(&collection)?;
15447
15687
  Ok(Json(rows.into_iter().map(record_response).collect()))
15448
15688
  }
@@ -15823,7 +16063,7 @@ async fn create_record(
15823
16063
  "system collection requires proposal service".into(),
15824
16064
  ));
15825
16065
  }
15826
- validate_segment(&collection)?;
16066
+ validate_collection_name(&collection)?;
15827
16067
  let id = record
15828
16068
  .id
15829
16069
  .take()
@@ -17554,11 +17794,103 @@ async fn state_reconciliation_execute(
17554
17794
  }
17555
17795
 
17556
17796
  fn record_key(collection: &str, id: &str) -> Result<String, ApiError> {
17557
- validate_segment(collection)?;
17558
- validate_segment(id)?;
17797
+ validate_collection_name(collection)?;
17798
+ // An agent-state record's id is FeltDB's own (its transition contract defines it);
17799
+ // every other record keeps the plain segment rule.
17800
+ if is_agent_state_collection(collection) {
17801
+ if !feltdb::agent_transition_contract::agent_state_record_id_ok(id) {
17802
+ return Err(ApiError(
17803
+ StatusCode::BAD_REQUEST,
17804
+ "invalid collection or record identifier".to_string(),
17805
+ ));
17806
+ }
17807
+ } else {
17808
+ validate_segment(id)?;
17809
+ }
17559
17810
  Ok(format!("{collection}:{id}"))
17560
17811
  }
17561
17812
 
17813
+ /// A collection name on the namespace data API: a plain segment, or one of FeltDB's own
17814
+ /// agent-state collections (`_feltdb.AgentTask`, `_feltdb.AgentCheckpoint`, …), which
17815
+ /// FeltDB's client writes for `openAgentState`. Exactly `_feltdb.Agent` followed by ASCII
17816
+ /// letters: no other reserved (`_feltdb.Composition…`) or dotted name is accepted, and
17817
+ /// record ids, namespaces, fields and workers keep `validate_segment`. Authorization and
17818
+ /// namespace isolation do not depend on the name.
17819
+ fn validate_collection_name(value: &str) -> Result<(), ApiError> {
17820
+ if validate_segment(value).is_ok() || is_agent_state_collection(value) {
17821
+ return Ok(());
17822
+ }
17823
+ Err(ApiError(
17824
+ StatusCode::BAD_REQUEST,
17825
+ "invalid collection or record identifier".to_string(),
17826
+ ))
17827
+ }
17828
+
17829
+ fn is_agent_state_collection(value: &str) -> bool {
17830
+ value
17831
+ .strip_prefix(feltdb::agent_transition_contract::AGENT_COLLECTION_PREFIX)
17832
+ .is_some_and(|rest| {
17833
+ !rest.is_empty()
17834
+ && rest.len() <= 64
17835
+ && rest.bytes().all(|byte| byte.is_ascii_alphabetic())
17836
+ })
17837
+ }
17838
+
17839
+ #[cfg(test)]
17840
+ mod collection_name_tests {
17841
+ use super::{is_agent_state_collection, record_key, validate_collection_name};
17842
+
17843
+ #[test]
17844
+ fn agent_state_collections_and_plain_names_only() {
17845
+ for ok in [
17846
+ "_feltdb.AgentTask",
17847
+ "_feltdb.AgentCheckpoint",
17848
+ "_feltdb.AgentHandoff",
17849
+ "_feltdb.AgentMemory",
17850
+ "_feltdb.AgentIntent",
17851
+ "tasks",
17852
+ "a-b_c",
17853
+ ] {
17854
+ assert!(validate_collection_name(ok).is_ok(), "{ok}");
17855
+ }
17856
+ for refused in [
17857
+ "",
17858
+ "_feltdb.Agent",
17859
+ "_feltdb.CompositionContract",
17860
+ "_feltdb.StateChange",
17861
+ "_feltdb.AgentTask.x",
17862
+ "_feltdb.Agent..x",
17863
+ "_feltdb.Agent1",
17864
+ "_FELTDB.AgentTask",
17865
+ "a.b",
17866
+ "../x",
17867
+ "_feltdb.AgentTask/x",
17868
+ ] {
17869
+ assert!(validate_collection_name(refused).is_err(), "{refused}");
17870
+ }
17871
+ assert!(!is_agent_state_collection("_feltdb.Agent"));
17872
+ }
17873
+
17874
+ #[test]
17875
+ fn record_ids_follow_their_collection() {
17876
+ assert_eq!(
17877
+ record_key("_feltdb.AgentCheckpoint", "task-1.v2").unwrap(),
17878
+ "_feltdb.AgentCheckpoint:task-1.v2"
17879
+ );
17880
+ assert!(record_key("_feltdb.AgentIntent", "task-1~key").is_ok());
17881
+ for refused in ["a:b", "..", ".", ""] {
17882
+ assert!(
17883
+ record_key("_feltdb.AgentTask", refused).is_err(),
17884
+ "{refused}"
17885
+ );
17886
+ }
17887
+ assert!(record_key("_feltdb.AgentTask", &"x".repeat(258)).is_err());
17888
+ for refused in ["a.b", "a~b", "a:b"] {
17889
+ assert!(record_key("tasks", refused).is_err(), "plain {refused}");
17890
+ }
17891
+ }
17892
+ }
17893
+
17562
17894
  fn validate_segment(value: &str) -> Result<(), ApiError> {
17563
17895
  if value.is_empty()
17564
17896
  || value.len() > 128
@@ -347,8 +347,17 @@ async fn the_bounded_query_pages_in_a_total_order() {
347
347
  .collect();
348
348
  assert_eq!(values, vec![json!(4), json!(2), json!(0)]);
349
349
 
350
+ // No ordering field: record-id order, as on the embedded runtimes.
351
+ let unordered = client.query(&BoundedQuery::new("items", 10)).await.unwrap();
352
+ let ids: Vec<_> = unordered
353
+ .records
354
+ .iter()
355
+ .map(|record| record["recordId"].clone())
356
+ .collect();
357
+ assert_eq!(ids, vec![json!("i-0"), json!("i-1"), json!("i-2"), json!("i-3"), json!("i-4")]);
358
+
350
359
  let invalid = client
351
- .query(&BoundedQuery::new("items", 10))
360
+ .query(&BoundedQuery::new("items", 0))
352
361
  .await
353
362
  .unwrap_err();
354
363
  assert_eq!(invalid.code(), Some("INVALID_QUERY"));
@@ -9,7 +9,7 @@ publish = false
9
9
  crate-type = ["cdylib"]
10
10
 
11
11
  [dependencies]
12
- feltdb = { path = "../feltdb", features = ["unstable-internals"] }
12
+ feltdb = { version = "=0.4.2", features = ["unstable-internals"] }
13
13
  wasm-bindgen = "0.2"
14
14
  wasm-bindgen-futures = "0.4"
15
15
  serde = { version = "1", features = ["derive"] }
package/dist/db.d.ts CHANGED
@@ -19,7 +19,7 @@ import { type RuntimeSnapshotHandle, type SnapshotDefinition } from './runtime-s
19
19
  import type { FlowSpec } from './flowspec.js';
20
20
  import { SyncController, type SyncConfig } from './sync-contract.js';
21
21
  import { type FeltDBDeploymentConfig, type FeltDBDeploymentMode, type FeltDBDeploymentResolution } from './deployment.js';
22
- import { WorkloadClient } from './workload.js';
22
+ import { WorkloadClient, type DurableWorkSupport } from './workload.js';
23
23
  import { MeshClient, WorkerClient, WorkerPoolClient } from './worker.js';
24
24
  import { ArtifactClient } from './artifact.js';
25
25
  import { BundleClient } from './bundle.js';
@@ -95,6 +95,11 @@ export interface FeltDBRuntimeCapabilities {
95
95
  offline: boolean;
96
96
  /** `storeContent` / `acquireContent` are available. */
97
97
  content: boolean;
98
+ /**
99
+ * Durable Work (`db.workloads`): `supported` when this runtime talks to a feltdb-server, `requires-server` when it is embedded and
100
+ * every operation refuses with `CONTRACT_UNSUPPORTED` until a server is configured, `unsupported` when no server can provide it.
101
+ */
102
+ durableWork: DurableWorkSupport;
98
103
  }
99
104
  export interface FeltDBCapabilitySurface {
100
105
  (): FeltDBRuntimeCapabilities;
@@ -283,6 +288,11 @@ export declare class StateFirstDB {
283
288
  readonly authorityScope?: AuthorityScope;
284
289
  private tenantFactory?;
285
290
  private localActorSession?;
291
+ /**
292
+ * Durable Work. Server-backed runtimes talk to the authority with their own credentials; embedded runtimes (memory, file, browser) have
293
+ * no Durable Work engine, so each operation refuses with `CONTRACT_UNSUPPORTED` and a pointer to `feltdb-server`
294
+ * (see `capabilities().durableWork`).
295
+ */
286
296
  readonly workloads: WorkloadClient;
287
297
  readonly artifacts: ArtifactClient;
288
298
  readonly bundle: BundleClient;