@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.
- package/dist/agent-state.d.ts +20 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +36 -0
- package/dist/build-identity.json +12 -4
- package/dist/cli/commands.js +187 -4
- package/dist/cli/publish-engine.js +116 -16
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +20 -2
- package/dist/create/build-identity.json +12 -4
- package/dist/create/managed-account.js +93 -35
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +6 -509
- package/dist/create/server-source/Cargo.toml +2 -13
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +356 -24
- package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +10 -1
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
- package/dist/db.d.ts +11 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +15 -2
- package/dist/error-codes.d.ts +5 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +9 -1
- package/dist/http-db.d.ts +16 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +36 -6
- package/dist/managed-recovery.d.ts +6 -0
- package/dist/managed-recovery.d.ts.map +1 -1
- package/dist/managed-recovery.js +9 -0
- package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-BUIxCuTR.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C3D6y6DK.wasm +0 -0
- package/dist/studio-app/assets/index-BhXYAhgS.js +37 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workload.d.ts +55 -3
- package/dist/workload.d.ts.map +1 -1
- package/dist/workload.js +23 -0
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -3
- package/package.json +1 -1
- package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
- package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
- package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
- package/dist/create/server-source/crates/feltdb/README.md +0 -75
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
- package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
- package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
- package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
- package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
- package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
- package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
- package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
- package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
- package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
- package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
- package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
- package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
- package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
- package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
- package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
- package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
- package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
- package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
- package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
- package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
- package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
- package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
- package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
- package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
- package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
- package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
- package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
- package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
- package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
- package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
- package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
- package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
- package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
- package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
- package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
- package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
- package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
- package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
[workspace]
|
|
2
|
-
members = ["crates/feltdb
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17558
|
-
|
|
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",
|
|
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 = {
|
|
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;
|