@feltdb/core 0.8.0 → 0.8.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 (74) hide show
  1. package/dist/application-development.d.ts +7 -2
  2. package/dist/application-development.d.ts.map +1 -1
  3. package/dist/application-development.js +7 -13
  4. package/dist/application-manifest.d.ts +1 -0
  5. package/dist/application-manifest.d.ts.map +1 -1
  6. package/dist/application-manifest.js +1 -1
  7. package/dist/application-understanding.d.ts +76 -0
  8. package/dist/application-understanding.d.ts.map +1 -0
  9. package/dist/application-understanding.js +282 -0
  10. package/dist/cli/ai-model-config.js +3 -3
  11. package/dist/cli/commands.js +131 -38
  12. package/dist/cli/proposal-client.js +5 -1
  13. package/dist/cli/publish-engine.js +1 -1
  14. package/dist/cli/repository-evidence.js +55 -0
  15. package/dist/cli/source-sync.js +190 -75
  16. package/dist/convergence.d.ts +55 -0
  17. package/dist/convergence.d.ts.map +1 -0
  18. package/dist/convergence.js +63 -0
  19. package/dist/create/managed-account.js +1 -0
  20. package/dist/create/package-versions.js +1 -1
  21. package/dist/create/server-source/crates/feltdb/src/application.rs +4 -0
  22. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +14 -0
  23. package/dist/create/server-source/crates/feltdb-server/src/main.rs +174 -24
  24. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +8 -1
  25. package/dist/create/template/default-project/src/context/AuthContext.tsx +9 -1
  26. package/dist/create/template/default-project/src/pages/SignUp.tsx +1 -1
  27. package/dist/flowspec.d.ts.map +1 -1
  28. package/dist/flowspec.js +6 -1
  29. package/dist/http-client.d.ts +32 -1
  30. package/dist/http-client.d.ts.map +1 -1
  31. package/dist/http-client.js +14 -2
  32. package/dist/http-db.d.ts +3 -0
  33. package/dist/http-db.d.ts.map +1 -1
  34. package/dist/http-db.js +28 -3
  35. package/dist/index-core.d.ts +3 -0
  36. package/dist/index-core.d.ts.map +1 -1
  37. package/dist/index-core.js +3 -0
  38. package/dist/intent.d.ts +98 -0
  39. package/dist/intent.d.ts.map +1 -0
  40. package/dist/intent.js +343 -0
  41. package/dist/module-intent.d.ts +19 -5
  42. package/dist/module-intent.d.ts.map +1 -1
  43. package/dist/module-intent.js +211 -62
  44. package/dist/module.d.ts +41 -12
  45. package/dist/module.d.ts.map +1 -1
  46. package/dist/module.js +543 -38
  47. package/dist/proposal.d.ts +13 -1
  48. package/dist/proposal.d.ts.map +1 -1
  49. package/dist/proposal.js +21 -4
  50. package/dist/studio/components/AskFeltDB.d.ts.map +1 -1
  51. package/dist/studio/components/ProposalBrowser.d.ts +1 -1
  52. package/dist/studio/components/ProposalBrowser.d.ts.map +1 -1
  53. package/dist/studio/components/index.js +1 -1
  54. package/dist/studio/{components-CNSNrmA9.js → components-Duq2xQWt.js} +251 -193
  55. package/dist/studio/index.js +1 -1
  56. package/dist/studio/studio.css +1 -1
  57. package/dist/studio/utils/proposal-api.d.ts +4 -1
  58. package/dist/studio/utils/proposal-api.d.ts.map +1 -1
  59. package/dist/studio/utils/proposal-api.js +3 -0
  60. package/dist/studio-app/assets/{feltdb_wasm-CSB6KVgw.js → feltdb_wasm-bIqcRzAr.js} +1 -1
  61. package/dist/studio-app/assets/feltdb_wasm_bg-BE79okwX.wasm +0 -0
  62. package/dist/studio-app/assets/{index-yazaWMUN.css → index-Cd_NPw14.css} +1 -1
  63. package/dist/studio-app/assets/index-XGYdlElN.js +29 -0
  64. package/dist/studio-app/index.html +2 -2
  65. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  66. package/dist/workspace/local-development-authority.d.ts +2 -0
  67. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  68. package/dist/workspace/local-development-authority.js +65 -2
  69. package/package.json +1 -1
  70. package/dist/studio-app/assets/dist-CCOk39Uc.js +0 -1
  71. package/dist/studio-app/assets/feltdb_wasm_bg-Dd1fnO9U.wasm +0 -0
  72. package/dist/studio-app/assets/index-Bncji3aN.js +0 -43
  73. package/dist/studio-app/assets/lib-B2_7fcn7.js +0 -69
  74. package/dist/studio-app/assets/worker-CARZ5g7K.js +0 -69
@@ -448,6 +448,8 @@ struct ProposalScopeRequest {
448
448
  limit: Option<usize>,
449
449
  #[serde(default)]
450
450
  cursor: Option<String>,
451
+ #[serde(default)]
452
+ current_evidence_hash: Option<String>,
451
453
  }
452
454
  #[derive(Deserialize)]
453
455
  struct CreateProposalRequest {
@@ -472,6 +474,26 @@ struct CreateProposalRequest {
472
474
  expires_at: Option<String>,
473
475
  #[serde(default)]
474
476
  status: Option<String>,
477
+ #[serde(default)]
478
+ understanding: Option<Value>,
479
+ #[serde(default)]
480
+ evidence_hash: Option<String>,
481
+ #[serde(default)]
482
+ understanding_hash: Option<String>,
483
+ }
484
+ #[derive(Deserialize, Serialize)]
485
+ struct ExecuteIntentRequest {
486
+ application_id: String,
487
+ #[serde(default = "production_environment")]
488
+ environment: String,
489
+ #[serde(alias = "raw_request")]
490
+ request: String,
491
+ }
492
+ #[derive(Deserialize)]
493
+ struct IntentHashRequest {
494
+ application_id: String,
495
+ environment: String,
496
+ intent_hash: String,
475
497
  }
476
498
  #[derive(Deserialize)]
477
499
  struct ProposalActorRequest {
@@ -518,6 +540,7 @@ struct SignUpRequest {
518
540
  }
519
541
  #[derive(Deserialize)]
520
542
  struct SignInRequest {
543
+ application_id: Option<String>,
521
544
  email: String,
522
545
  password: String,
523
546
  }
@@ -2219,6 +2242,7 @@ async fn platform_overview(
2219
2242
  let (users, tenants, applications, tenant_memberships, application_memberships) =
2220
2243
  state.tenancy.platform_inventory().map_err(control_error)?;
2221
2244
  let identities = state.identities.list().map_err(control_error)?;
2245
+ let runtime_metrics = state.metrics.snapshot();
2222
2246
  Ok(Json(json!({
2223
2247
  "owner": principal.identity_id,
2224
2248
  "users": users,
@@ -2227,6 +2251,11 @@ async fn platform_overview(
2227
2251
  "tenant_memberships": tenant_memberships,
2228
2252
  "application_memberships": application_memberships,
2229
2253
  "identities": identities,
2254
+ "adoption": {
2255
+ "measured_at": unix_seconds_i64().max(0) as u64,
2256
+ "runtime_process_started_seconds_ago": state.started_at.elapsed().as_secs(),
2257
+ "runtime": runtime_metrics,
2258
+ },
2230
2259
  })))
2231
2260
  }
2232
2261
  async fn platform_update_user(
@@ -3793,6 +3822,9 @@ fn application_scope(
3793
3822
  StatusCode::NOT_FOUND,
3794
3823
  "application not found".into(),
3795
3824
  ))?;
3825
+ if std::env::var("FELTDB_INTENT_AUTHORITY_KEY_ID").as_deref() == Ok(actor) {
3826
+ return Ok(tenant_id);
3827
+ }
3796
3828
  if !state
3797
3829
  .tenancy
3798
3830
  .authorize_application(actor, &tenant_id, application_id, capability)
@@ -4449,13 +4481,13 @@ async fn import_application_draft(
4449
4481
 
4450
4482
  fn runtime_inventory(
4451
4483
  state: &AppState,
4452
- actor: &str,
4484
+ _actor: &str,
4453
4485
  application_id: &str,
4454
4486
  manifest: &ApplicationManifest,
4455
4487
  ) -> Result<RuntimeInventory, ApiError> {
4456
4488
  let connections = state
4457
4489
  .connections
4458
- .list_connections(actor, &state.tenancy, application_id)
4490
+ .application_connections(application_id)
4459
4491
  .map_err(control_error)?;
4460
4492
  let connection_ids = connections
4461
4493
  .into_iter()
@@ -5046,6 +5078,49 @@ async fn inspect_record(
5046
5078
  const PROPOSAL_COLLECTION: &str = "_feltdb_proposals";
5047
5079
  const PROPOSAL_EVENT_COLLECTION: &str = "_feltdb_proposal_events";
5048
5080
  const PROPOSAL_PREVIEW_COLLECTION: &str = "_feltdb_proposal_previews";
5081
+ const CONVERGENCE_COLLECTION: &str = "_feltdb_convergence";
5082
+ const INTENT_COLLECTION: &str = "_feltdb_intents";
5083
+ const INTENT_EVENT_COLLECTION: &str = "_feltdb_intent_events";
5084
+
5085
+ fn require_intent_service(headers: &HeaderMap) -> Result<(), ApiError> {
5086
+ let expected = std::env::var("FELTDB_INTENT_SERVICE_SECRET").map_err(|_| inspection_error(StatusCode::SERVICE_UNAVAILABLE, "intent_service_unconfigured", "Intent Engine service authentication is not configured"))?;
5087
+ let supplied = headers.get("x-feltdb-intent-service").and_then(|value| value.to_str().ok()).unwrap_or_default();
5088
+ if Sha256::digest(supplied.as_bytes()) != Sha256::digest(expected.as_bytes()) {
5089
+ return Err(inspection_error(StatusCode::FORBIDDEN, "forbidden", "invalid Intent Engine service credential"));
5090
+ }
5091
+ Ok(())
5092
+ }
5093
+
5094
+ async fn execute_intent(
5095
+ State(state): State<AppState>, Extension(principal): Extension<Principal>, headers: HeaderMap,
5096
+ Json(input): Json<ExecuteIntentRequest>,
5097
+ ) -> Result<Json<Value>, ApiError> {
5098
+ application_scope(&state, &principal.key_id, &input.application_id, "application:write")?;
5099
+ if input.request.trim().is_empty() { return Err(inspection_error(StatusCode::BAD_REQUEST, "invalid_intent", "request is required")); }
5100
+ let endpoint = std::env::var("FELTDB_INTENT_ENGINE_URL").map_err(|_| inspection_error(StatusCode::SERVICE_UNAVAILABLE, "intent_engine_unavailable", "managed Intent Engine is not configured"))?;
5101
+ let secret = std::env::var("FELTDB_INTENT_SERVICE_SECRET").map_err(|_| inspection_error(StatusCode::SERVICE_UNAVAILABLE, "intent_engine_unavailable", "managed Intent Engine authentication is not configured"))?;
5102
+ let mut request = state.peer_client.post(format!("{}/internal/v1/intents", endpoint.trim_end_matches('/'))).header("x-feltdb-intent-service", secret).json(&input);
5103
+ if let Some(value) = headers.get("idempotency-key") { request = request.header("idempotency-key", value); }
5104
+ let response = request.send().await.map_err(|error| inspection_error(StatusCode::BAD_GATEWAY, "intent_engine_unavailable", error.to_string()))?;
5105
+ let status = response.status(); let value: Value = response.json().await.map_err(|error| inspection_error(StatusCode::BAD_GATEWAY, "invalid_intent_engine_response", error.to_string()))?;
5106
+ if !status.is_success() { let republish=value.get("error").and_then(Value::as_str)==Some("APPLICATION_REPUBLISH_REQUIRED");return Err(inspection_error(if republish {StatusCode::CONFLICT}else{StatusCode::BAD_GATEWAY},if republish{"application_republish_required"}else{"intent_execution_failed"},value.get("message").and_then(Value::as_str).unwrap_or("Intent Engine failed"))); }
5107
+ Ok(Json(value))
5108
+ }
5109
+
5110
+ async fn list_intents(State(state): State<AppState>, Extension(principal): Extension<Principal>, Query(input): Query<ProposalScopeRequest>) -> Result<Json<Value>, ApiError> {
5111
+ application_scope(&state,&principal.key_id,&input.application_id,"application:read")?;
5112
+ let mut intents=state.db.list_collection(INTENT_COLLECTION)?.into_iter().map(|row|row.value).filter(|value|proposal_scope_matches(value,&input.application_id,&input.environment)).collect::<Vec<_>>();
5113
+ intents.sort_by(|a,b|a.get("created_at").and_then(Value::as_str).cmp(&b.get("created_at").and_then(Value::as_str)));for value in &mut intents{if let Some(object)=value.as_object_mut(){object.remove("execution_result");}redact_inspection_value(value);}Ok(Json(json!({"intents":intents})))
5114
+ }
5115
+ async fn get_intent(State(state): State<AppState>, Extension(principal): Extension<Principal>, Path((id,operation)): Path<(String,String)>, Query(input): Query<ProposalScopeRequest>) -> Result<Json<Value>,ApiError>{
5116
+ application_scope(&state,&principal.key_id,&input.application_id,"application:read")?;let intent=required_value(&state,INTENT_COLLECTION,&id,"intent not found")?;if !proposal_scope_matches(&intent,&input.application_id,&input.environment){return Err(ApiError(StatusCode::NOT_FOUND,"intent not found".into()));}
5117
+ let value=match operation.as_str(){"understanding"=>intent.get("understanding").cloned().unwrap_or(Value::Null),"plan"=>intent.get("plan").cloned().unwrap_or(Value::Null),"proposal"=>intent.get("proposal_id").and_then(Value::as_str).map(|id|proposal_value(&state,id)).transpose()?.unwrap_or(Value::Null),"history"=>{let mut events=state.db.list_collection(INTENT_EVENT_COLLECTION)?.into_iter().map(|row|row.value).filter(|value|value.get("intent_id").and_then(Value::as_str)==Some(id.as_str())).collect::<Vec<_>>();events.sort_by(|a,b|a.get("created_at").and_then(Value::as_str).cmp(&b.get("created_at").and_then(Value::as_str)));json!({"events":events})},_=>return Err(ApiError(StatusCode::NOT_FOUND,"intent resource not found".into()))};Ok(Json(value))
5118
+ }
5119
+ async fn get_intent_root(State(state): State<AppState>, Extension(principal): Extension<Principal>, Path(id):Path<String>, Query(input):Query<ProposalScopeRequest>)->Result<Json<Value>,ApiError>{application_scope(&state,&principal.key_id,&input.application_id,"application:read")?;let mut value=required_value(&state,INTENT_COLLECTION,&id,"intent not found")?;if !proposal_scope_matches(&value,&input.application_id,&input.environment){return Err(ApiError(StatusCode::NOT_FOUND,"intent not found".into()));}if let Some(object)=value.as_object_mut(){object.remove("execution_result");}Ok(Json(value))}
5120
+ async fn internal_find_intent(State(state):State<AppState>,headers:HeaderMap,Query(input):Query<IntentHashRequest>)->Result<Json<Value>,ApiError>{require_intent_service(&headers)?;let value=state.db.list_collection(INTENT_COLLECTION)?.into_iter().map(|row|row.value).find(|value|proposal_scope_matches(value,&input.application_id,&input.environment)&&value.get("intent_hash").and_then(Value::as_str)==Some(input.intent_hash.as_str())).unwrap_or(Value::Null);Ok(Json(value))}
5121
+ async fn internal_create_intent(State(state):State<AppState>,headers:HeaderMap,Json(value):Json<Value>)->Result<(StatusCode,Json<Value>),ApiError>{require_intent_service(&headers)?;let id=value.get("id").and_then(Value::as_str).ok_or_else(||inspection_error(StatusCode::BAD_REQUEST,"invalid_intent","intent id is required"))?;insert_canonical(&state,INTENT_COLLECTION,id,value.clone())?;Ok((StatusCode::CREATED,Json(value)))}
5122
+ async fn internal_replace_intent(State(state):State<AppState>,headers:HeaderMap,Path(id):Path<String>,Json(value):Json<Value>)->Result<Json<Value>,ApiError>{require_intent_service(&headers)?;let _=put_canonical(&state,INTENT_COLLECTION,&id,value.clone())?;Ok(Json(value))}
5123
+ async fn internal_create_intent_event(State(state):State<AppState>,headers:HeaderMap,Json(value):Json<Value>)->Result<(StatusCode,Json<Value>),ApiError>{require_intent_service(&headers)?;let id=value.get("id").and_then(Value::as_str).ok_or_else(||inspection_error(StatusCode::BAD_REQUEST,"invalid_intent_event","event id is required"))?;insert_canonical(&state,INTENT_EVENT_COLLECTION,id,value.clone())?;Ok((StatusCode::CREATED,Json(value)))}
5049
5124
 
5050
5125
  fn proposal_contract_snapshot(
5051
5126
  revision: &ApplicationRevision,
@@ -5165,6 +5240,24 @@ async fn create_proposal(
5165
5240
  "proposed_flow and base_flow_hash are required".into(),
5166
5241
  ));
5167
5242
  }
5243
+ if input.metadata.get("created_from").and_then(Value::as_str) == Some("module-intent")
5244
+ && input.understanding.is_none()
5245
+ {
5246
+ return Err(inspection_error(StatusCode::UNPROCESSABLE_ENTITY, "understanding_required", "module proposals require an evidence-backed understanding snapshot"));
5247
+ }
5248
+ if input.understanding.is_some()
5249
+ && (input.evidence_hash.as_deref().is_none_or(|value| !value.starts_with("sha256:"))
5250
+ || input.understanding_hash.as_deref().is_none_or(|value| !value.starts_with("sha256:")))
5251
+ {
5252
+ return Err(inspection_error(StatusCode::UNPROCESSABLE_ENTITY, "invalid_understanding", "understanding requires evidence_hash and understanding_hash fingerprints"));
5253
+ }
5254
+ if let Some(understanding) = input.understanding.as_ref() {
5255
+ if understanding.get("evidence_hash").and_then(Value::as_str) != input.evidence_hash.as_deref()
5256
+ || understanding.get("understanding_hash").and_then(Value::as_str) != input.understanding_hash.as_deref()
5257
+ {
5258
+ return Err(inspection_error(StatusCode::UNPROCESSABLE_ENTITY, "invalid_understanding", "understanding fingerprints must match the Proposal fingerprints"));
5259
+ }
5260
+ }
5168
5261
  let id = format!("p_{}", uuid::Uuid::new_v4().simple());
5169
5262
  let now = unix_seconds_i64();
5170
5263
  let mut value = json!({ "id":id, "application_id":input.application_id, "environment":input.environment,
@@ -5173,6 +5266,7 @@ async fn create_proposal(
5173
5266
  "contract_diff":input.contract_diff.unwrap_or_else(||json!([])), "source_plan":input.source_plan.unwrap_or_else(||json!({"files":[]})), "summary":input.summary, "warnings":input.warnings,
5174
5267
  "created_by":principal.key_id, "created_at":now, "expires_at":input.expires_at, "metadata":input.metadata,
5175
5268
  "parent_proposal_id":input.parent_proposal_id,
5269
+ "understanding":input.understanding, "evidence_hash":input.evidence_hash, "understanding_hash":input.understanding_hash,
5176
5270
  "base_module_versions": revision.manifest.modules.iter().filter_map(|module| Some(json!({
5177
5271
  "id": module.get("id")?, "provider": module.get("provider")?, "version": module.get("version")?
5178
5272
  }))).collect::<Vec<_>>(),
@@ -5325,6 +5419,7 @@ fn proposal_readiness_value(
5325
5419
  state: &AppState,
5326
5420
  tenant: &str,
5327
5421
  value: &Value,
5422
+ current_evidence_hash: Option<&str>,
5328
5423
  ) -> Result<Value, ApiError> {
5329
5424
  let application_id = value.get("application_id").and_then(Value::as_str).unwrap_or("");
5330
5425
  let environment = value.get("environment").and_then(Value::as_str).unwrap_or("production");
@@ -5338,13 +5433,15 @@ fn proposal_readiness_value(
5338
5433
  let contract_changed = current_contract_hash != proposal_contract_hash;
5339
5434
  let flow_changed = current_flow_hash.as_deref().is_some_and(|hash| hash != proposal_flow_hash);
5340
5435
  let modules_changed = value.get("base_module_versions").and_then(Value::as_array).is_some_and(|modules| modules != &current_modules);
5436
+ let evidence_changed = current_evidence_hash.is_some_and(|current| value.get("evidence_hash").and_then(Value::as_str).is_some_and(|base| base != current));
5341
5437
  let expired = proposal_is_expired(value);
5342
5438
  let mut blockers = Vec::new();
5343
5439
  if contract_changed { blockers.push("contract_changed"); }
5344
5440
  if flow_changed { blockers.push("flow_changed"); }
5345
5441
  if modules_changed { blockers.push("module_versions_changed"); }
5442
+ if evidence_changed { blockers.push("evidence_changed"); }
5346
5443
  if expired { blockers.push("expired"); }
5347
- let stale = contract_changed || flow_changed || modules_changed;
5444
+ let stale = contract_changed || flow_changed || modules_changed || evidence_changed;
5348
5445
  Ok(json!({
5349
5446
  "status":value.get("status"), "readiness":if blockers.is_empty(){"ready"}else{"blocked"},
5350
5447
  "blockers":blockers, "notes":[], "current_contract_hash":current_contract_hash,
@@ -5364,7 +5461,29 @@ async fn proposal_status(
5364
5461
  if !proposal_scope_matches(&value, &input.application_id, &input.environment) {
5365
5462
  return Err(inspection_error(StatusCode::NOT_FOUND, "proposal_not_found", "proposal not found"));
5366
5463
  }
5367
- Ok(Json(proposal_readiness_value(&state, &tenant, &value)?))
5464
+ Ok(Json(proposal_readiness_value(&state, &tenant, &value, input.current_evidence_hash.as_deref())?))
5465
+ }
5466
+ async fn record_proposal_convergence(
5467
+ State(state): State<AppState>, Extension(principal): Extension<Principal>, Path(id): Path<String>, Json(mut input): Json<Value>,
5468
+ ) -> Result<(StatusCode, Json<Value>), ApiError> {
5469
+ let application_id=input.get("application_id").and_then(Value::as_str).ok_or_else(||inspection_error(StatusCode::BAD_REQUEST,"invalid_convergence","application_id is required"))?.to_string();
5470
+ let environment=input.get("environment").and_then(Value::as_str).unwrap_or("production").to_string();
5471
+ application_scope(&state,&principal.key_id,&application_id,"application:write")?;let proposal=proposal_value(&state,&id)?;
5472
+ if !proposal_scope_matches(&proposal,&application_id,&environment){return Err(inspection_error(StatusCode::NOT_FOUND,"proposal_not_found","proposal not found"));}
5473
+ if proposal.get("status").and_then(Value::as_str)!=Some("applied"){return Err(inspection_error(StatusCode::CONFLICT,"proposal_not_applied","only an applied Proposal can record convergence"));}
5474
+ if input.get("proposal_id").and_then(Value::as_str)!=Some(id.as_str()){return Err(inspection_error(StatusCode::UNPROCESSABLE_ENTITY,"invalid_convergence","proposal_id must match the route"));}
5475
+ let valid_status=input.get("status").and_then(Value::as_str).is_some_and(|value|matches!(value,"pending"|"checking"|"converged"|"partially_converged"|"diverged"|"blocked"|"unverified"));
5476
+ if !valid_status||input.get("findings").is_none_or(|value|!value.is_array())||input.get("evidence_hash").and_then(Value::as_str).is_none_or(|value|!value.starts_with("sha256:")){return Err(inspection_error(StatusCode::UNPROCESSABLE_ENTITY,"invalid_convergence","status, findings, and evidence_hash are required"));}
5477
+ let convergence_id=format!("cv_{}",uuid::Uuid::new_v4().simple());input["id"]=json!(convergence_id);input["created_at"]=json!(unix_seconds_i64());input["verifier_version"]=json!(1);
5478
+ redact_inspection_value(&mut input);insert_canonical(&state,CONVERGENCE_COLLECTION,&convergence_id,input.clone())?;Ok((StatusCode::CREATED,Json(input)))
5479
+ }
5480
+ async fn proposal_convergence(
5481
+ State(state): State<AppState>, Extension(principal): Extension<Principal>, Path(id): Path<String>, Query(input): Query<ProposalScopeRequest>,
5482
+ ) -> Result<Json<Value>, ApiError> {
5483
+ application_scope(&state,&principal.key_id,&input.application_id,"application:read")?;let proposal=proposal_value(&state,&id)?;
5484
+ if !proposal_scope_matches(&proposal,&input.application_id,&input.environment){return Err(inspection_error(StatusCode::NOT_FOUND,"proposal_not_found","proposal not found"));}
5485
+ let mut values=state.db.list_collection(CONVERGENCE_COLLECTION)?.into_iter().map(|row|row.value).filter(|value|value.get("proposal_id").and_then(Value::as_str)==Some(id.as_str())&&proposal_scope_matches(value,&input.application_id,&input.environment)).collect::<Vec<_>>();
5486
+ values.sort_by_key(|value|value.get("created_at").and_then(Value::as_i64).unwrap_or_default());values.reverse();Ok(Json(json!({"convergence":values})))
5368
5487
  }
5369
5488
  async fn validate_proposal(
5370
5489
  State(state): State<AppState>,
@@ -5501,7 +5620,9 @@ async fn validate_proposal(
5501
5620
  value["proposed_contract"] = serde_json::to_value(&proposed.manifest)
5502
5621
  .map_err(|e| ApiError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
5503
5622
  }
5504
- value["source_plan"] = json!({"files":[{"path":"feltdb.flow","operation":"modify"},{"path":"src/feltdb/generated-contract.ts","operation":"modify"}]});
5623
+ if !value.pointer("/source_plan/files").and_then(Value::as_array).is_some_and(|files| !files.is_empty()) {
5624
+ value["source_plan"] = json!({"files":[{"path":"feltdb.flow","operation":"modify"},{"path":"src/feltdb/generated-contract.ts","operation":"modify"}]});
5625
+ }
5505
5626
  Ok(Json(move_proposal(
5506
5627
  &state,
5507
5628
  &id,
@@ -5546,7 +5667,7 @@ async fn preview_proposal(
5546
5667
  let proposed_manifest: ApplicationManifest = serde_json::from_value(value.get("proposed_contract").cloned().ok_or_else(|| inspection_error(StatusCode::UNPROCESSABLE_ENTITY, "missing_required_semantics", "validated proposal has no proposed contract snapshot"))?).map_err(|error| inspection_error(StatusCode::UNPROCESSABLE_ENTITY, "invalid_proposal", error.to_string()))?;
5547
5668
  let current_revision_id = state.applications.pointers(&input.application_id).get(&input.environment).cloned().ok_or_else(|| inspection_error(StatusCode::NOT_FOUND, "application_not_found", "active application contract not found"))?;
5548
5669
  let current_revision = state.applications.revision(&tenant, &input.application_id, &current_revision_id).ok_or_else(|| inspection_error(StatusCode::NOT_FOUND, "application_not_found", "active application revision not found"))?;
5549
- let readiness = proposal_readiness_value(&state, &tenant, &value)?;
5670
+ let readiness = proposal_readiness_value(&state, &tenant, &value, None)?;
5550
5671
  if readiness.get("readiness").and_then(Value::as_str) != Some("ready") {
5551
5672
  return Err(ApiError::structured(StatusCode::CONFLICT, json!({"code":"preview_not_ready","message":"proposal cannot be previewed","readiness":readiness})));
5552
5673
  }
@@ -5616,7 +5737,7 @@ async fn get_proposal_preview(
5616
5737
  if proposal.get("preview_id") != artifact.get("preview_id") {
5617
5738
  return Err(inspection_error(StatusCode::CONFLICT, "preview_invalidated", "preview has been replaced"));
5618
5739
  }
5619
- if proposal_readiness_value(&state, &tenant, &proposal)?.get("stale").and_then(Value::as_bool) == Some(true) {
5740
+ if proposal_readiness_value(&state, &tenant, &proposal, None)?.get("stale").and_then(Value::as_bool) == Some(true) {
5620
5741
  return Err(inspection_error(StatusCode::CONFLICT, "preview_invalidated", "the application authority changed after preview creation"));
5621
5742
  }
5622
5743
  redact_inspection_value(&mut artifact);
@@ -5696,7 +5817,7 @@ async fn transition_proposal(
5696
5817
  return Err(inspection_error(StatusCode::CONFLICT, "proposal_expired", "proposal has expired"));
5697
5818
  }
5698
5819
  if input.status == "approved" {
5699
- let readiness = proposal_readiness_value(&state, &tenant, &value)?;
5820
+ let readiness = proposal_readiness_value(&state, &tenant, &value, None)?;
5700
5821
  if readiness.get("readiness").and_then(Value::as_str) != Some("ready") {
5701
5822
  return Err(ApiError::structured(StatusCode::CONFLICT, json!({"code":"stale_proposal","message":"proposal is no longer applicable","readiness":readiness})));
5702
5823
  }
@@ -5795,19 +5916,17 @@ async fn auth_sign_up(
5795
5916
  "application not found".into(),
5796
5917
  ));
5797
5918
  }
5798
- let (identity, session, token) = state
5919
+ let (identity, session, token) = match state
5799
5920
  .identities
5800
5921
  .sign_up(&input.email, &input.password, input.display_name)
5801
- .map_err(|error| {
5802
- ApiError(
5803
- if error == "identity already exists" {
5804
- StatusCode::CONFLICT
5805
- } else {
5806
- StatusCode::BAD_REQUEST
5807
- },
5808
- error,
5809
- )
5810
- })?;
5922
+ {
5923
+ Ok(created) => created,
5924
+ Err(error) if error == "identity already exists" => state
5925
+ .identities
5926
+ .sign_in(&input.email, &input.password)
5927
+ .map_err(|_| ApiError(StatusCode::UNAUTHORIZED, "invalid credentials".into()))?,
5928
+ Err(error) => return Err(ApiError(StatusCode::BAD_REQUEST, error)),
5929
+ };
5811
5930
  state
5812
5931
  .tenancy
5813
5932
  .register_platform_user(
@@ -5829,6 +5948,22 @@ async fn auth_sign_in(
5829
5948
  .identities
5830
5949
  .sign_in(&input.email, &input.password)
5831
5950
  .map_err(|_| ApiError(StatusCode::UNAUTHORIZED, "invalid credentials".into()))?;
5951
+ if let Some(application_id) = input.application_id {
5952
+ if state.tenancy.application_tenant(&application_id).is_none() {
5953
+ return Err(ApiError(StatusCode::NOT_FOUND, "application not found".into()));
5954
+ }
5955
+ state
5956
+ .tenancy
5957
+ .register_platform_user(
5958
+ &identity.subject_id,
5959
+ identity.email.as_deref().unwrap_or(&identity.identity_id),
5960
+ )
5961
+ .map_err(control_error)?;
5962
+ state
5963
+ .tenancy
5964
+ .register_application_actor(&identity.subject_id, &application_id)
5965
+ .map_err(control_error)?;
5966
+ }
5832
5967
  Ok(auth_response(identity, session, token))
5833
5968
  }
5834
5969
  async fn auth_session(Extension(principal): Extension<Principal>) -> Json<Value> {
@@ -5872,6 +6007,13 @@ async fn application_openapi() -> Json<Value> {
5872
6007
  "/query": { "post": { "responses": { "200": { "description": "Bounded authorized query" } } } },
5873
6008
  "/transactions": { "post": { "responses": { "200": { "description": "Atomic idempotent mutation" } } } },
5874
6009
  "/events": { "get": { "responses": { "200": { "description": "Application event stream" } } } },
6010
+ "/intents": {
6011
+ "get": { "responses": { "200": { "description": "Durable application Intents" } } },
6012
+ "post": { "parameters": [{ "name":"Idempotency-Key", "in":"header", "required":false, "schema":{"type":"string"} }], "responses": { "200": { "description": "Canonical Intent execution result" } } }
6013
+ },
6014
+ "/intents/{id}": { "get": { "responses": { "200": { "description": "Canonical Intent" } } } },
6015
+ "/intents/{id}/{artifact}": { "get": { "responses": { "200": { "description": "Intent understanding, plan, Proposal, or history" } } } },
6016
+ "/proposals": { "get": { "responses": { "200": { "description": "Durable Proposals" } } }, "post": { "responses": { "201": { "description": "Created Proposal" } } } },
5875
6017
  "/workflows/{name}/run": { "post": { "responses": { "201": { "description": "Workflow execution" } } } },
5876
6018
  "/agents/{name}/run": { "post": { "responses": { "201": { "description": "Agent execution" } } } },
5877
6019
  "/capabilities/{name}/run": { "post": { "responses": { "200": { "description": "Public capability result" } } } }
@@ -7547,6 +7689,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7547
7689
  .route("/v1/application/schema", get(inspect_application_schema))
7548
7690
  .route("/v1/data/{collection}", get(inspect_collection))
7549
7691
  .route("/v1/data/{collection}/{id}", get(inspect_record))
7692
+ .route("/v1/intents", get(list_intents).post(execute_intent))
7693
+ .route("/v1/intents/{id}", get(get_intent_root))
7694
+ .route("/v1/intents/{id}/{operation}", get(get_intent))
7550
7695
  .route("/v1/proposals", get(list_proposals).post(create_proposal))
7551
7696
  .route("/v1/proposals/{id}", get(get_proposal))
7552
7697
  .route("/v1/proposals/{id}/history", get(proposal_history))
@@ -7570,6 +7715,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7570
7715
  "/v1/proposals/{id}/status",
7571
7716
  get(proposal_status).patch(transition_proposal),
7572
7717
  )
7718
+ .route("/v1/proposals/{id}/convergence", get(proposal_convergence).post(record_proposal_convergence))
7573
7719
  .route("/v1/previews/{id}", get(get_proposal_preview))
7574
7720
  .route("/v1/previews/{id}/data/{collection}", get(inspect_proposal_preview_data))
7575
7721
  .route("/v1/previews/{id}/simulate", axum::routing::post(simulate_proposal_preview))
@@ -7956,6 +8102,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7956
8102
  .route("/v1/openapi.json", get(application_openapi))
7957
8103
  .route("/v1/auth/signup", axum::routing::post(auth_sign_up))
7958
8104
  .route("/v1/auth/signin", axum::routing::post(auth_sign_in))
8105
+ .route("/internal/intents/by-hash", get(internal_find_intent))
8106
+ .route("/internal/intents", axum::routing::post(internal_create_intent))
8107
+ .route("/internal/intents/{id}", put(internal_replace_intent))
8108
+ .route("/internal/intent-events", axum::routing::post(internal_create_intent_event))
7959
8109
  .route("/ready", get(readiness))
7960
8110
  .route("/runtime", get(runtime))
7961
8111
  .route("/metrics", get(network_metrics))
@@ -10937,7 +11087,7 @@ async fn list_records(
10937
11087
  State(state): State<AppState>,
10938
11088
  Path(collection): Path<String>,
10939
11089
  ) -> Result<Json<Vec<RecordResponse>>, ApiError> {
10940
- if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION {
11090
+ if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION || collection == CONVERGENCE_COLLECTION || collection == INTENT_COLLECTION || collection == INTENT_EVENT_COLLECTION {
10941
11091
  return Err(ApiError(
10942
11092
  StatusCode::FORBIDDEN,
10943
11093
  "system collection requires proposal service".into(),
@@ -10952,7 +11102,7 @@ async fn get_record(
10952
11102
  State(state): State<AppState>,
10953
11103
  Path((collection, id)): Path<(String, String)>,
10954
11104
  ) -> Result<Json<RecordResponse>, ApiError> {
10955
- if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION {
11105
+ if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION || collection == CONVERGENCE_COLLECTION || collection == INTENT_COLLECTION || collection == INTENT_EVENT_COLLECTION {
10956
11106
  return Err(ApiError(
10957
11107
  StatusCode::FORBIDDEN,
10958
11108
  "system collection requires proposal service".into(),
@@ -11198,7 +11348,7 @@ async fn create_record(
11198
11348
  Path(collection): Path<String>,
11199
11349
  Json(mut record): Json<CreateRecord>,
11200
11350
  ) -> Result<(StatusCode, Json<RecordResponse>), ApiError> {
11201
- if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION {
11351
+ if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION || collection == CONVERGENCE_COLLECTION || collection == INTENT_COLLECTION || collection == INTENT_EVENT_COLLECTION {
11202
11352
  return Err(ApiError(
11203
11353
  StatusCode::FORBIDDEN,
11204
11354
  "system collection requires proposal service".into(),
@@ -11237,7 +11387,7 @@ async fn update_record(
11237
11387
  Path((collection, id)): Path<(String, String)>,
11238
11388
  Json(changes): Json<Map<String, Value>>,
11239
11389
  ) -> Result<Json<RecordResponse>, ApiError> {
11240
- if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION {
11390
+ if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION || collection == CONVERGENCE_COLLECTION || collection == INTENT_COLLECTION || collection == INTENT_EVENT_COLLECTION {
11241
11391
  return Err(ApiError(
11242
11392
  StatusCode::FORBIDDEN,
11243
11393
  "system collection requires proposal service".into(),
@@ -11358,7 +11508,7 @@ async fn delete_record(
11358
11508
  State(state): State<AppState>,
11359
11509
  Path((collection, id)): Path<(String, String)>,
11360
11510
  ) -> Result<StatusCode, ApiError> {
11361
- if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION {
11511
+ if collection == PROPOSAL_COLLECTION || collection == PROPOSAL_EVENT_COLLECTION || collection == CONVERGENCE_COLLECTION || collection == INTENT_COLLECTION || collection == INTENT_EVENT_COLLECTION {
11362
11512
  return Err(ApiError(
11363
11513
  StatusCode::FORBIDDEN,
11364
11514
  "system collection requires proposal service".into(),
@@ -609,7 +609,7 @@ impl TenancyStore {
609
609
  .find(|app| app.id == application_id)?
610
610
  .clone();
611
611
  drop(records);
612
- self.authorize_application(user_id, &app.tenant_id, &app.id, "applications:read")
612
+ self.authorize_application(user_id, &app.tenant_id, &app.id, "application:read")
613
613
  .then_some(app)
614
614
  }
615
615
  pub fn application_tenant(&self, application_id: &str) -> Option<String> {
@@ -1322,7 +1322,14 @@ mod tests {
1322
1322
  .unwrap();
1323
1323
 
1324
1324
  assert!(store.authorize_application("key_runtime", &tenant.id, &app.id, "state:read"));
1325
+ assert_eq!(
1326
+ store
1327
+ .application_for("key_runtime", &app.id)
1328
+ .map(|application| application.id),
1329
+ Some(app.id.clone())
1330
+ );
1325
1331
  assert!(!store.authorize_application("key_runtime", &tenant.id, &app.id, "state:write"));
1332
+ assert!(store.application_for("key_runtime", &other.id).is_none());
1326
1333
  assert!(!store.authorize_application("key_runtime", &tenant.id, &other.id, "state:read"));
1327
1334
  }
1328
1335
  }
@@ -17,7 +17,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
17
17
  const loading = sessionRecords.loading || userRecords.loading || membershipRecords.loading || organizationRecords.loading;
18
18
  const establish = (next: Session) => { sessionStorage.setItem(SESSION_KEY, next.id); setSessionId(next.id); };
19
19
  const value = useMemo<AuthValue>(() => ({ loading, authenticated: Boolean(session && user && membership && organization), session, user, membership, organization,
20
- signUp: async input => { await db.auth.signUp({ email: input.email, password: input.password, display_name: input.name }); establish(await createAccount(input)); },
20
+ signUp: async input => {
21
+ try {
22
+ await db.auth.signUp({ email: input.email, password: input.password, display_name: input.name });
23
+ } catch (error) {
24
+ if ((error as { code?: string }).code !== 'CONFLICT') throw error;
25
+ await db.auth.signIn({ email: input.email, password: input.password });
26
+ }
27
+ establish(await createAccount(input));
28
+ },
21
29
  signIn: async (email, password) => { await db.auth.signIn({ email, password }); establish(await createSession(email)); },
22
30
  signOut: async () => { if (session) await sessions.delete(session.id); await db.auth.signOut(); sessionStorage.removeItem(SESSION_KEY); setSessionId(null); },
23
31
  }), [loading, session, user, membership, organization]);
@@ -3,6 +3,6 @@ import { useAuth } from '../context/AuthContext';
3
3
 
4
4
  export function SignUp() {
5
5
  const { signUp } = useAuth(); const [error, setError] = useState(''); const [busy, setBusy] = useState(false);
6
- async function submit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); setBusy(true); setError(''); const form = new FormData(event.currentTarget); try { await signUp({ name: String(form.get('name')), email: String(form.get('email')), password: String(form.get('password')), organization: String(form.get('organization')) }); } catch { setError('We could not create your account. Check the details and try again.'); setBusy(false); } }
6
+ async function submit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); setBusy(true); setError(''); const form = new FormData(event.currentTarget); try { await signUp({ name: String(form.get('name')), email: String(form.get('email')), password: String(form.get('password')), organization: String(form.get('organization')) }); } catch (error) { setError(error instanceof Error ? error.message : 'We could not create your account. Check the details and try again.'); setBusy(false); } }
7
7
  return <><span className="eyebrow">Create your workspace</span><h2>Start with durable state.</h2><p className="muted">Your actor, account, and organization are established at the FeltDB boundary.</p><form onSubmit={submit}><label>Name<input name="name" autoComplete="name" required placeholder="Ada Lovelace" /></label><label>Work email<input name="email" type="email" autoComplete="email" required placeholder="ada@example.com" /></label><label>Password<input name="password" type="password" autoComplete="new-password" minLength={8} required /></label><label>Organization<input name="organization" required placeholder="Analytical Engines" /></label>{error && <p className="error">{error}</p>}<button disabled={busy}>{busy ? 'Creating…' : 'Create account'}</button></form><p className="switch">Already have an account? <a href="?view=signin">Sign in</a></p></>;
8
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"flowspec.d.ts","sourceRoot":"","sources":["../src/flowspec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flowspec.d.ts","sourceRoot":"","sources":["../src/flowspec.ts"],"names":[],"mappings":"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"}
package/dist/flowspec.js CHANGED
@@ -26,7 +26,12 @@ export function flowSpecToManifest(spec, tenantId, applicationId) {
26
26
  const enumValues = field.type.startsWith('enum(') && field.type.endsWith(')') ? field.type.slice(5, -1).split(',').map(value => value.trim()) : undefined;
27
27
  return { name: field.name, type: reference ? 'reference' : enumValues ? 'enum' : field.type, required: !field.optional, ...(reference ? { reference } : {}), ...(enumValues ? { enum_values: enumValues } : {}) };
28
28
  };
29
- return { application_id: applicationId, tenant_id: tenantId, schema: 1, metadata: { name: spec.app, labels: {} }, collections: spec.collections.map(value => ({ name: value.name, fields: value.fields.map(manifestField) })), indexes: spec.collections.flatMap(collection => collection.indexes.map(index => ({ name: index.name, collection: collection.name, fields: [index.expression], unique: index.kind === 'unique' }))), references: [], queries: [], actions: [], workflows: spec.workflows.map(value => ({ name: value.name, steps: value.steps.map(step => ({ name: step.name, capability: step.statements.find(statement => statement.startsWith('capability '))?.slice('capability '.length) })) })), triggers: spec.triggers.map((value, index) => ({ name: `trigger-${index + 1}`, event: value.event, workflow: spec.workflows[0]?.name || '' })), schedules: spec.schedules.map(value => ({ name: value.name, expression: value.statements[0] || '', workflow: spec.workflows[0]?.name || '' })), agents: spec.agents.map(value => ({ name: value.name, capabilities: capabilities.map(capability => capability.name).filter(name => value.statements.some(statement => statement.includes(name))) })), capabilities, modules: resolveFeltDBModules(spec.modules), connections: [], policies: spec.policies.map(value => ({ name: value.name, resource: value.name, read: policySubject(value.statements, 'read'), write: policySubject(value.statements, 'write'), capabilities: [] })), ui: { routes: [], bindings: [], forms: [], dashboards: [] }, environments: ['development', 'preview', 'staging', 'production'].map(name => ({ name, configuration: {} })), artifacts: [], deployment: { environments: ['development', 'preview', 'staging', 'production'] }, runtime: { contract: 1, minimum_engine: '0.2.0', maximum_engine_exclusive: '2.0.0' } };
29
+ const indexFields = (expression) => {
30
+ const trimmed = expression.trim();
31
+ const match = trimmed.match(/^(?:[A-Za-z_][A-Za-z0-9_]*)?\((.*)\)$/);
32
+ return (match?.[1] ?? trimmed).split(',').map(value => value.trim()).filter(Boolean);
33
+ };
34
+ return { application_id: applicationId, tenant_id: tenantId, schema: 1, metadata: { name: spec.app, labels: {} }, collections: spec.collections.map(value => ({ name: value.name, fields: value.fields.map(manifestField) })), indexes: spec.collections.flatMap(collection => collection.indexes.map(index => ({ name: `${collection.name}.${index.name}`, collection: collection.name, fields: indexFields(index.expression), unique: index.kind === 'unique' }))), references: [], queries: [], actions: [], workflows: spec.workflows.map(value => ({ name: value.name, steps: value.steps.map(step => ({ name: step.name, capability: step.statements.find(statement => statement.startsWith('capability '))?.slice('capability '.length) })) })), triggers: spec.triggers.map((value, index) => ({ name: `trigger-${index + 1}`, event: value.event, workflow: spec.workflows[0]?.name || '' })), schedules: spec.schedules.map(value => ({ name: value.name, expression: value.statements[0] || '', workflow: spec.workflows[0]?.name || '' })), agents: spec.agents.map(value => ({ name: value.name, capabilities: capabilities.map(capability => capability.name).filter(name => value.statements.some(statement => statement.includes(name))) })), capabilities, modules: resolveFeltDBModules(spec.modules), connections: [], policies: spec.policies.map(value => ({ name: value.name, resource: value.name, read: policySubject(value.statements, 'read'), write: policySubject(value.statements, 'write'), capabilities: [] })), ui: { routes: [], bindings: [], forms: [], dashboards: [] }, environments: ['development', 'preview', 'staging', 'production'].map(name => ({ name, configuration: {} })), artifacts: [], deployment: { environments: ['development', 'preview', 'staging', 'production'] }, runtime: { contract: 1, minimum_engine: '0.2.0', maximum_engine_exclusive: '2.0.0' } };
30
35
  }
31
36
  export function manifestToFlowSpec(manifest) {
32
37
  const fieldType = (field) => field.reference ? `ref ${field.reference}` : field.enum_values?.length ? `enum(${field.enum_values.join(',')})` : field.type;
@@ -5,6 +5,9 @@
5
5
  * For applications that only need HTTP access without the full core framework.
6
6
  */
7
7
  import type { FeltDBProposalEvent, FeltDBProposalPreviewArtifact, FeltDBProposalRecord, ProposalReadiness, ProposalStatus } from './proposal.js';
8
+ import type { ApplicationUnderstanding } from './application-understanding.js';
9
+ import type { ConvergenceResult } from './convergence.js';
10
+ import type { ImplementationPlan, Intent, IntentEvent, IntentExecutionResult } from './intent.js';
8
11
  import type { CanonicalQuery, QueryResult, TransactionResult } from './state-contract.js';
9
12
  export interface ClientOptions {
10
13
  url: string;
@@ -106,6 +109,20 @@ export declare class FeltDBClient {
106
109
  readonly capabilities: {
107
110
  run: (name: string, input: unknown) => Promise<unknown>;
108
111
  };
112
+ /** Canonical intent execution and its durable semantic artifacts. */
113
+ readonly intents: {
114
+ execute: (rawRequest: string) => Promise<IntentExecutionResult>;
115
+ list: () => Promise<{
116
+ intents: Intent[];
117
+ }>;
118
+ get: (id: string) => Promise<Intent>;
119
+ understanding: (id: string) => Promise<ApplicationUnderstanding>;
120
+ plan: (id: string) => Promise<ImplementationPlan>;
121
+ proposal: (id: string) => Promise<FeltDBProposalRecord>;
122
+ history: (id: string) => Promise<{
123
+ events: IntentEvent[];
124
+ }>;
125
+ };
109
126
  /** Canonical Proposal lifecycle. Repository application is intentionally absent. */
110
127
  readonly proposals: {
111
128
  create: (proposal: {
@@ -117,6 +134,16 @@ export declare class FeltDBClient {
117
134
  metadata?: Record<string, string>;
118
135
  parent_proposal_id?: string;
119
136
  expires_at?: string | number;
137
+ contract_diff?: string[];
138
+ source_plan?: {
139
+ files: Array<{
140
+ path: string;
141
+ action: string;
142
+ }>;
143
+ };
144
+ understanding?: ApplicationUnderstanding;
145
+ evidence_hash?: string;
146
+ understanding_hash?: string;
120
147
  }) => Promise<FeltDBProposalRecord>;
121
148
  list: (options?: {
122
149
  status?: ProposalStatus;
@@ -133,7 +160,7 @@ export declare class FeltDBClient {
133
160
  history: (id: string) => Promise<{
134
161
  events: FeltDBProposalEvent[];
135
162
  }>;
136
- status: (id: string) => Promise<ProposalReadiness>;
163
+ status: (id: string, currentEvidenceHash?: string) => Promise<ProposalReadiness>;
137
164
  validate: (id: string, proposedManifest?: unknown) => Promise<FeltDBProposalRecord>;
138
165
  createPreview: (id: string) => Promise<FeltDBProposalPreviewArtifact>;
139
166
  preview: (previewId: string) => Promise<FeltDBProposalPreviewArtifact>;
@@ -150,6 +177,10 @@ export declare class FeltDBClient {
150
177
  }>;
151
178
  approve: (id: string, approveAuthorization?: boolean) => Promise<FeltDBProposalRecord>;
152
179
  reject: (id: string, reason?: string) => Promise<FeltDBProposalRecord>;
180
+ convergence: (id: string) => Promise<{
181
+ convergence: ConvergenceResult[];
182
+ }>;
183
+ recordConvergence: (id: string, result: ConvergenceResult) => Promise<ConvergenceResult>;
153
184
  };
154
185
  }
155
186
  export declare function createClient(options: ClientOptions): FeltDBClient;
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}
@@ -99,13 +99,23 @@ export class FeltDBClient {
99
99
  capabilities = {
100
100
  run: (name, input) => this.request(this.scoped(`/v1/capabilities/${encodeURIComponent(name)}/run`), { method: 'POST', body: JSON.stringify(input) }),
101
101
  };
102
+ /** Canonical intent execution and its durable semantic artifacts. */
103
+ intents = {
104
+ execute: (rawRequest) => this.request('/v1/intents', { method: 'POST', body: JSON.stringify(this.scopeBody({ raw_request: rawRequest })) }),
105
+ list: () => this.request(this.scoped('/v1/intents')),
106
+ get: (id) => this.request(this.scoped(`/v1/intents/${encodeURIComponent(id)}`)),
107
+ understanding: (id) => this.request(this.scoped(`/v1/intents/${encodeURIComponent(id)}/understanding`)),
108
+ plan: (id) => this.request(this.scoped(`/v1/intents/${encodeURIComponent(id)}/plan`)),
109
+ proposal: (id) => this.request(this.scoped(`/v1/intents/${encodeURIComponent(id)}/proposal`)),
110
+ history: (id) => this.request(this.scoped(`/v1/intents/${encodeURIComponent(id)}/history`)),
111
+ };
102
112
  /** Canonical Proposal lifecycle. Repository application is intentionally absent. */
103
113
  proposals = {
104
- create: (proposal) => this.request('/v1/proposals', { method: 'POST', body: JSON.stringify(this.scopeBody(proposal)) }),
114
+ create: (proposal) => this.request('/v1/proposals', { method: 'POST', body: JSON.stringify(this.scopeBody({ proposal_version: 1, contract_diff: [], source_plan: { files: [] }, ...proposal })) }),
105
115
  list: (options = {}) => this.request(this.scoped('/v1/proposals', options)),
106
116
  get: (id) => this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}`)),
107
117
  history: (id) => this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/history`)),
108
- status: (id) => this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/status`)),
118
+ status: (id, currentEvidenceHash) => this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/status`, { current_evidence_hash: currentEvidenceHash })),
109
119
  validate: (id, proposedManifest) => this.request(`/v1/proposals/${encodeURIComponent(id)}/validate`, { method: 'POST', body: JSON.stringify(this.scopeBody({ proposed_manifest: proposedManifest })) }),
110
120
  createPreview: (id) => this.request(`/v1/proposals/${encodeURIComponent(id)}/preview`, { method: 'POST', body: JSON.stringify(this.scopeBody()) }),
111
121
  preview: (previewId) => this.request(this.scoped(`/v1/previews/${encodeURIComponent(previewId)}`)),
@@ -113,6 +123,8 @@ export class FeltDBClient {
113
123
  simulatePreview: (previewId, event) => this.request(this.scoped(`/v1/previews/${encodeURIComponent(previewId)}/simulate`), { method: 'POST', body: JSON.stringify({ event }) }),
114
124
  approve: (id, approveAuthorization = false) => this.request(`/v1/proposals/${encodeURIComponent(id)}/approve`, { method: 'POST', body: JSON.stringify(this.scopeBody({ approve_authorization: approveAuthorization })) }),
115
125
  reject: (id, reason) => this.request(`/v1/proposals/${encodeURIComponent(id)}/reject`, { method: 'POST', body: JSON.stringify(this.scopeBody({ reason })) }),
126
+ convergence: (id) => this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/convergence`)),
127
+ recordConvergence: (id, result) => this.request(`/v1/proposals/${encodeURIComponent(id)}/convergence`, { method: 'POST', body: JSON.stringify(this.scopeBody(result)) }),
116
128
  };
117
129
  }
118
130
  export function createClient(options) {