diff --git a/TASKS.md b/TASKS.md index d5f1612..2443050 100644 --- a/TASKS.md +++ b/TASKS.md @@ -44,6 +44,7 @@ Progress: - CI, README, runtime/deploy smoke docs now point to `deploy/community/*` as the Community delivery source of truth - separate `Community` release checklist now exists and explicitly forbids treating future `Enterprise/Cloud` delivery as just another env on the same public manifest - explicit repository split map now defines what goes to `crank-community`, `crank-enterprise`, and `crank-cloud` + - `Community` is now fixed as `REST-only` in product docs, capability model, backend validation, demo seed, and UI protocol expectations - pending: - remaining extension seams and packaging split still need to move from planning into concrete `crank-community` / `crank-enterprise` / `crank-cloud` manifests - next management gate is to actually create the `3` target repositories before physical split starts diff --git a/apps/admin-api/src/app.rs b/apps/admin-api/src/app.rs index 7feed1f..ef3712d 100644 --- a/apps/admin-api/src/app.rs +++ b/apps/admin-api/src/app.rs @@ -1031,10 +1031,7 @@ mod tests { .await; assert_eq!(response["edition"], "community"); - assert_eq!( - response["supported_protocols"], - json!(["rest", "graphql", "grpc"]) - ); + assert_eq!(response["supported_protocols"], json!(["rest"])); assert_eq!(response["supported_security_levels"], json!(["standard"])); assert_eq!( response["machine_access_modes"], @@ -1143,7 +1140,7 @@ mod tests { ) .await; - assert_eq!(response["items"].as_array().unwrap().len(), 3); + assert_eq!(response["items"].as_array().unwrap().len(), 1); assert_eq!( response["items"] .as_array() @@ -1151,7 +1148,7 @@ mod tests { .iter() .map(|item| item["protocol"].as_str().unwrap()) .collect::>(), - vec!["rest", "graphql", "grpc"] + vec!["rest"] ); for item in response["items"].as_array().unwrap() { assert_eq!(item["supports_execution_modes"], json!(["unary"])); @@ -1162,6 +1159,42 @@ mod tests { } } + #[tokio::test(flavor = "multi_thread")] + #[serial] + async fn rejects_graphql_protocol_for_community_operation_create() { + let registry = test_registry().await; + let storage_root = test_storage_root("community_graphql_reject_create"); + let upstream_base_url = spawn_graphql_server().await; + let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await; + let client = authorized_client(&base_url).await; + + let response = client + .post(format!("{base_url}/operations")) + .json(&test_graphql_operation_payload( + &upstream_base_url, + "crm_graphql_not_in_community", + )) + .send() + .await + .unwrap(); + let status = response.status(); + let body = response.json::().await.unwrap(); + + assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); + assert_eq!(body["error"]["code"], "validation_error"); + assert_eq!( + body["error"]["message"], + "protocol graphql is not supported in Community" + ); + assert_eq!( + body["error"]["context"], + json!({ + "protocol": "graphql", + "edition": "community", + }) + ); + } + #[tokio::test(flavor = "multi_thread")] #[serial] async fn rejects_unsupported_protocol_for_community_operation_create() { diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index 3385194..9f96012 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -3,9 +3,8 @@ use std::path::PathBuf; use base64::{ Engine as _, - engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, + engine::general_purpose::URL_SAFE_NO_PAD, }; -use crank_adapter_grpc::test_support as grpc_test_support; use crank_adapter_soap::{SoapServiceSummary, inspect_wsdl}; use crank_core::{ Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AggregationMode, @@ -1574,7 +1573,7 @@ impl AdminService { pub async fn get_capabilities(&self) -> EditionCapabilities { EditionCapabilities { edition: ProductEdition::Community, - supported_protocols: vec![Protocol::Rest, Protocol::Graphql, Protocol::Grpc], + supported_protocols: vec![Protocol::Rest], supported_security_levels: vec![OperationSecurityLevel::Standard], machine_access_modes: vec![MachineAccessMode::StaticAgentKey], limits: EditionLimits { @@ -3909,7 +3908,7 @@ impl AdminService { protocol: Protocol, security_level: OperationSecurityLevel, ) -> Result<(), ApiError> { - let supported_protocols = [Protocol::Rest, Protocol::Graphql, Protocol::Grpc]; + let supported_protocols = [Protocol::Rest]; if !supported_protocols.contains(&protocol) { return Err(ApiError::validation_with_context( format!( @@ -4033,32 +4032,6 @@ impl AdminService { ) .await?; - let graphql_operation = self - .ensure_demo_operation(workspace_id, demo_graphql_operation_payload()) - .await?; - self.ensure_operation_published(workspace_id, &graphql_operation) - .await?; - self.ensure_demo_json_samples( - workspace_id, - &graphql_operation.id, - &demo_graphql_input_sample(), - &demo_graphql_output_sample(), - ) - .await?; - - let grpc_operation = self - .ensure_demo_operation(workspace_id, demo_grpc_operation_payload()) - .await?; - self.ensure_demo_json_samples( - workspace_id, - &grpc_operation.id, - &demo_grpc_input_sample(), - &demo_grpc_output_sample(), - ) - .await?; - self.ensure_demo_grpc_artifacts(workspace_id, &grpc_operation.id) - .await?; - let archived_operation = self .ensure_demo_operation(workspace_id, demo_archived_operation_payload()) .await?; @@ -4071,49 +4044,17 @@ impl AdminService { self.ensure_demo_agent_bindings( workspace_id, &AgentId::new(revops_agent.id.clone()), - vec![ - AgentBindingPayload { - operation_id: rest_operation.id.as_str().to_owned(), - operation_version: rest_operation.current_draft_version, - tool_name: "create_crm_lead".to_owned(), - tool_title: "Create CRM Lead".to_owned(), - tool_description_override: Some( - "Create a new CRM lead in the revenue workspace.".to_owned(), - ), - enabled: true, - }, - AgentBindingPayload { - operation_id: graphql_operation.id.as_str().to_owned(), - operation_version: graphql_operation.current_draft_version, - tool_name: "get_invoice_status".to_owned(), - tool_title: "Get Invoice Status".to_owned(), - tool_description_override: Some( - "Fetch invoice state from billing GraphQL.".to_owned(), - ), - enabled: true, - }, - ], - true, - ) - .await?; - - let support_agent = self - .ensure_demo_agent(workspace_id, demo_support_agent_payload()) - .await?; - self.ensure_demo_agent_bindings( - workspace_id, - &AgentId::new(support_agent.id.clone()), vec![AgentBindingPayload { - operation_id: grpc_operation.id.as_str().to_owned(), - operation_version: grpc_operation.current_draft_version, - tool_name: "lookup_support_ticket".to_owned(), - tool_title: "Lookup Support Ticket".to_owned(), + operation_id: rest_operation.id.as_str().to_owned(), + operation_version: rest_operation.current_draft_version, + tool_name: "create_crm_lead".to_owned(), + tool_title: "Create CRM Lead".to_owned(), tool_description_override: Some( - "Draft support lookup flow backed by unary gRPC.".to_owned(), + "Create a new CRM lead in the revenue workspace.".to_owned(), ), enabled: true, }], - false, + true, ) .await?; @@ -4125,23 +4066,9 @@ impl AdminService { false, ) .await?; - self.ensure_demo_platform_api_key( - workspace_id, - &AgentId::new(support_agent.id.clone()), - "Readonly Export Key", - vec![PlatformApiKeyScope::Read], - true, - ) - .await?; - self.seed_demo_invocation_logs( - workspace_id, - &AgentId::new(revops_agent.id), - &rest_operation.id, - &graphql_operation.id, - &grpc_operation.id, - ) - .await?; + self.seed_demo_invocation_logs(workspace_id, &AgentId::new(revops_agent.id), &rest_operation.id) + .await?; Ok(()) } @@ -4353,49 +4280,6 @@ impl AdminService { Ok(()) } - async fn ensure_demo_grpc_artifacts( - &self, - workspace_id: &WorkspaceId, - operation_id: &OperationId, - ) -> Result<(), ApiError> { - let summary = self.get_operation(workspace_id, operation_id).await?; - let descriptors = self - .registry - .list_descriptor_metadata(operation_id, summary.current_draft_version) - .await?; - let has_proto = descriptors - .iter() - .any(|item| item.descriptor_kind == crank_registry::DescriptorKind::ProtoUpload); - let has_descriptor = descriptors - .iter() - .any(|item| item.descriptor_kind == crank_registry::DescriptorKind::DescriptorSet); - - if !has_proto { - self.upload_proto_file( - workspace_id, - operation_id, - Some("echo.proto"), - DEMO_GRPC_PROTO.as_bytes(), - ) - .await?; - } - - if !has_descriptor { - let descriptor_set = STANDARD - .decode(grpc_test_support::descriptor_set_b64()) - .map_err(|error| ApiError::internal(error.to_string()))?; - self.upload_descriptor_set( - workspace_id, - operation_id, - Some("echo_descriptor.bin"), - &descriptor_set, - ) - .await?; - } - - Ok(()) - } - async fn ensure_demo_agent( &self, workspace_id: &WorkspaceId, @@ -4437,8 +4321,6 @@ impl AdminService { workspace_id: &WorkspaceId, revops_agent_id: &AgentId, rest_operation_id: &OperationId, - graphql_operation_id: &OperationId, - grpc_operation_id: &OperationId, ) -> Result<(), ApiError> { if !self .registry @@ -4467,25 +4349,6 @@ impl AdminService { .current_draft_version, ) .await?; - let graphql_operation = self - .get_operation_version( - workspace_id, - graphql_operation_id, - self.get_operation(workspace_id, graphql_operation_id) - .await? - .current_draft_version, - ) - .await?; - let grpc_operation = self - .get_operation_version( - workspace_id, - grpc_operation_id, - self.get_operation(workspace_id, grpc_operation_id) - .await? - .current_draft_version, - ) - .await?; - self.record_invocation(InvocationRecordRequest { workspace_id, agent_id: Some(revops_agent_id), @@ -4509,78 +4372,6 @@ impl AdminService { response_preview: demo_rest_response_sample(), }) .await?; - self.record_invocation(InvocationRecordRequest { - workspace_id, - agent_id: Some(revops_agent_id), - operation: &graphql_operation.snapshot, - request_id: None, - source: InvocationSource::AgentToolCall, - level: InvocationLevel::Info, - status: InvocationStatus::Ok, - message: "invoice status resolved".to_owned(), - status_code: Some(200), - error_kind: None, - duration_ms: 126, - request_preview: json!({ - "path": {}, - "query": {}, - "headers": { "x-demo-source": "crank-seed" }, - "variables": demo_graphql_variables_sample(), - "grpc": null, - "body": null - }), - response_preview: demo_graphql_response_sample(), - }) - .await?; - self.record_invocation(InvocationRecordRequest { - workspace_id, - agent_id: Some(revops_agent_id), - operation: &graphql_operation.snapshot, - request_id: None, - source: InvocationSource::AgentToolCall, - level: InvocationLevel::Error, - status: InvocationStatus::Error, - message: "billing service returned operation error".to_owned(), - status_code: Some(502), - error_kind: Some("graphql_error".to_owned()), - duration_ms: 412, - request_preview: json!({ - "path": {}, - "query": {}, - "headers": { "x-demo-source": "crank-seed" }, - "variables": { "invoiceId": "inv_4099" }, - "grpc": null, - "body": null - }), - response_preview: json!({ - "errors": [{ "message": "invoice not available" }] - }), - }) - .await?; - self.record_invocation(InvocationRecordRequest { - workspace_id, - agent_id: None, - operation: &grpc_operation.snapshot, - request_id: None, - source: InvocationSource::AdminTestRun, - level: InvocationLevel::Info, - status: InvocationStatus::Ok, - message: "descriptor-backed unary echo tested".to_owned(), - status_code: Some(200), - error_kind: None, - duration_ms: 98, - request_preview: json!({ - "path": {}, - "query": {}, - "headers": {}, - "variables": null, - "grpc": demo_grpc_request_sample(), - "body": null - }), - response_preview: demo_grpc_response_sample(), - }) - .await?; - Ok(()) } @@ -4616,21 +4407,6 @@ impl AdminService { } const DEMO_USER_PASSWORD: &str = "CrankDemoPass123!"; -const DEMO_GRPC_PROTO: &str = r#"syntax = "proto3"; -package echo; - -service EchoService { - rpc UnaryEcho (EchoRequest) returns (EchoResponse); -} - -message EchoRequest { - string message = 1; -} - -message EchoResponse { - string message = 1; -} -"#; fn build_request_preview( mapping: &MappingSet, @@ -4728,21 +4504,6 @@ fn demo_revops_agent_payload() -> AgentPayload { } } -fn demo_support_agent_payload() -> AgentPayload { - AgentPayload { - slug: "support-triage".to_owned(), - display_name: "Support Triage".to_owned(), - description: "Draft support agent focused on unary gRPC workflows.".to_owned(), - instructions: json!({ - "system": "Use support tools carefully and ask for confirmation before mutating state." - }), - tool_selection_policy: json!({ - "max_tools": 4, - "prefer_tag": ["support"] - }), - } -} - fn demo_rest_operation_payload() -> OperationPayload { let input = demo_rest_input_sample(); let request = demo_rest_request_sample(); @@ -4797,115 +4558,6 @@ fn demo_rest_operation_payload() -> OperationPayload { } } -fn demo_graphql_operation_payload() -> OperationPayload { - let input = demo_graphql_input_sample(); - let variables = demo_graphql_variables_sample(); - let data = demo_graphql_data_sample(); - let output = demo_graphql_output_sample(); - - OperationPayload { - name: "billing_get_invoice".to_owned(), - display_name: "Get Invoice Status".to_owned(), - category: "finance".to_owned(), - protocol: Protocol::Graphql, - security_level: OperationSecurityLevel::Standard, - target: Target::Graphql(crank_core::GraphqlTarget { - endpoint: "https://billing.demo.internal/graphql".to_owned(), - operation_type: crank_core::GraphqlOperationType::Query, - operation_name: "InvoiceById".to_owned(), - query_template: "query InvoiceById($invoiceId: ID!) { invoice(id: $invoiceId) { id status total currency customerName } }".to_owned(), - response_path: "$.response.body.data.invoice".to_owned(), - }), - input_schema: Schema::from_json_sample(&input), - output_schema: Schema::from_json_sample(&output), - input_mapping: infer_mapping_from_samples( - &input, - JsonPathRoot::Mcp, - &variables, - JsonPathRoot::RequestVariables, - ), - output_mapping: infer_mapping_from_samples( - &data, - JsonPathRoot::ResponseData, - &output, - JsonPathRoot::Output, - ), - execution_config: crank_core::ExecutionConfig { - timeout_ms: 8_000, - retry_policy: None, - auth_profile_ref: None, - headers: BTreeMap::new(), - protocol_options: None, - streaming: None, - }, - tool_description: crank_core::ToolDescription { - title: "Get Invoice Status".to_owned(), - description: "Fetch invoice state and amount from billing.".to_owned(), - tags: vec!["finance".to_owned(), "billing".to_owned()], - examples: vec![crank_core::ToolExample { - input: json!({ "invoiceId": "inv_1001" }), - }], - }, - } -} - -fn demo_grpc_operation_payload() -> OperationPayload { - let input = demo_grpc_input_sample(); - let request = demo_grpc_request_sample(); - let response = demo_grpc_response_sample(); - let output = demo_grpc_output_sample(); - - OperationPayload { - name: "support_lookup_ticket".to_owned(), - display_name: "Lookup Support Ticket".to_owned(), - category: "support".to_owned(), - protocol: Protocol::Grpc, - security_level: OperationSecurityLevel::Standard, - target: Target::Grpc(crank_core::GrpcTarget { - server_addr: "http://grpc.demo.internal".to_owned(), - package: "echo".to_owned(), - service: "EchoService".to_owned(), - method: "UnaryEcho".to_owned(), - descriptor_ref: crank_core::DescriptorId::new("desc_echo_demo"), - descriptor_set_b64: grpc_test_support::descriptor_set_b64(), - }), - input_schema: Schema::from_json_sample(&input), - output_schema: Schema::from_json_sample(&output), - input_mapping: infer_mapping_from_samples( - &input, - JsonPathRoot::Mcp, - &request, - JsonPathRoot::RequestGrpc, - ), - output_mapping: infer_mapping_from_samples( - &response, - JsonPathRoot::ResponseGrpc, - &output, - JsonPathRoot::Output, - ), - execution_config: crank_core::ExecutionConfig { - timeout_ms: 5_000, - retry_policy: None, - auth_profile_ref: None, - headers: BTreeMap::new(), - protocol_options: Some(crank_core::ProtocolOptions { - grpc: Some(crank_core::GrpcProtocolOptions { use_tls: false }), - websocket: None, - soap: None, - }), - streaming: None, - }, - tool_description: crank_core::ToolDescription { - title: "Lookup Support Ticket".to_owned(), - description: "Draft unary gRPC support lookup using a descriptor set.".to_owned(), - tags: vec!["support".to_owned(), "grpc".to_owned()], - examples: vec![crank_core::ToolExample { - input: json!({ "message": "ticket: T-1001" }), - }], - }, - } -} - fn demo_archived_operation_payload() -> OperationPayload { let input = json!({ "contactId": "contact_123", @@ -4989,50 +4641,6 @@ fn demo_rest_output_sample() -> Value { demo_rest_response_sample() } -fn demo_graphql_input_sample() -> Value { - json!({ "invoiceId": "inv_1001" }) -} - -fn demo_graphql_variables_sample() -> Value { - json!({ "invoiceId": "inv_1001" }) -} - -fn demo_graphql_data_sample() -> Value { - json!({ - "id": "inv_1001", - "status": "paid", - "total": 4200, - "currency": "USD", - "customerName": "Cyberdyne" - }) -} - -fn demo_graphql_response_sample() -> Value { - json!({ - "invoice": demo_graphql_data_sample() - }) -} - -fn demo_graphql_output_sample() -> Value { - demo_graphql_data_sample() -} - -fn demo_grpc_input_sample() -> Value { - json!({ "message": "ticket: T-1001" }) -} - -fn demo_grpc_request_sample() -> Value { - demo_grpc_input_sample() -} - -fn demo_grpc_response_sample() -> Value { - json!({ "message": "ticket: T-1001 is currently open" }) -} - -fn demo_grpc_output_sample() -> Value { - demo_grpc_response_sample() -} - fn default_enabled() -> bool { true } diff --git a/apps/ui/tests/e2e/observability.spec.js b/apps/ui/tests/e2e/observability.spec.js index 8ffa4ec..69f3f1c 100644 --- a/apps/ui/tests/e2e/observability.spec.js +++ b/apps/ui/tests/e2e/observability.spec.js @@ -13,6 +13,8 @@ test('shell and wizard expose stable diagnostics hooks', async ({ page }) => { await page.goto('/wizard/'); await expect(page.locator('[data-testid="wizard-protocol-grid"]')).toBeVisible(); await expect(page.locator('[data-testid="wizard-protocol-rest"]')).toBeVisible(); + await expect(page.locator('[data-testid="wizard-protocol-graphql"]')).toBeHidden(); + await expect(page.locator('[data-testid="wizard-protocol-grpc"]')).toBeHidden(); await expect(page.locator('[data-testid="wizard-protocol-websocket"]')).toBeHidden(); await expect(page.locator('[data-testid="wizard-protocol-soap"]')).toBeHidden(); await expect(page.locator('html')).toHaveAttribute('data-crank-bootstrap-state', 'ready'); diff --git a/apps/ui/tests/e2e/operations.spec.js b/apps/ui/tests/e2e/operations.spec.js index 978493d..65b9d80 100644 --- a/apps/ui/tests/e2e/operations.spec.js +++ b/apps/ui/tests/e2e/operations.spec.js @@ -4,8 +4,8 @@ const { login, localized } = require('./helpers'); test('operations page shows demo catalog and filter works', async ({ page }) => { await login(page); await expect(page.locator('.page-heading')).toHaveText(localized('Operations', 'Операции')); - await expect(page.locator('tbody tr')).toHaveCount(4); - await page.getByPlaceholder(localized('Search operations', 'Поиск операций')).fill('support'); + await expect(page.locator('tbody tr')).toHaveCount(2); + await page.getByPlaceholder(localized('Search operations', 'Поиск операций')).fill('crm'); await expect(page.locator('tbody tr')).toHaveCount(1); - await expect(page.locator('tbody tr').first()).toContainText(/support_lookup_ticket/i); + await expect(page.locator('tbody tr').first()).toContainText(/crm_create_lead/i); }); diff --git a/apps/ui/tests/e2e/wizard.spec.js b/apps/ui/tests/e2e/wizard.spec.js index 6a082aa..d8094ca 100644 --- a/apps/ui/tests/e2e/wizard.spec.js +++ b/apps/ui/tests/e2e/wizard.spec.js @@ -6,7 +6,7 @@ test('wizard loads and protocol selection updates flow', async ({ page }) => { await page.goto('/wizard/'); await expect(page.locator('[data-step-counter]').first()).toContainText(localized('Step', 'Шаг')); await expect(page.locator('#step-panel-1 .step-panel-title')).toContainText(localized('Choose a protocol', 'Выберите протокол')); - await page.getByText(/graphql/i).first().click(); + await page.locator('[data-testid="wizard-protocol-rest"]').click(); await page.locator('#btn-continue').click(); await expect(page.locator('#step-panel-2 .step-panel-title')).toContainText(localized('Select upstream', 'Выберите upstream')); }); @@ -15,13 +15,13 @@ test('community wizard hides premium protocols and explains edition scope', asyn await login(page); await page.goto('/wizard/'); await expect(page.locator('[data-testid="wizard-protocol-rest"]')).toBeVisible(); - await expect(page.locator('[data-testid="wizard-protocol-graphql"]')).toBeVisible(); - await expect(page.locator('[data-testid="wizard-protocol-grpc"]')).toBeVisible(); + await expect(page.locator('[data-testid="wizard-protocol-graphql"]')).toBeHidden(); + await expect(page.locator('[data-testid="wizard-protocol-grpc"]')).toBeHidden(); await expect(page.locator('[data-testid="wizard-protocol-websocket"]')).toBeHidden(); await expect(page.locator('[data-testid="wizard-protocol-soap"]')).toBeHidden(); await expect(page.locator('#wizard-edition-protocol-note')).toContainText(localized('Commercial editions unlock', 'коммерческих редакциях')); - await page.locator('[data-testid="wizard-protocol-grpc"]').click(); + await page.locator('[data-testid="wizard-protocol-rest"]').click(); await page.locator('#btn-continue').click(); await page.locator('#btn-continue').click(); await page.locator('#btn-continue').click(); diff --git a/docs/admin-api.md b/docs/admin-api.md index acb35d4..72f98f1 100644 --- a/docs/admin-api.md +++ b/docs/admin-api.md @@ -68,8 +68,6 @@ - `GET /protocol-capabilities` возвращает только те протоколы и execution capabilities, которые реально доступны в текущей редакции; - для `Community` это означает только: - `REST` - - `GraphQL` - - `gRPC` ### 5.1. Workspaces and members @@ -139,9 +137,7 @@ - если поле не передано, используется `standard` для обратной совместимости с уже существующим YAML и draft payload; - в `Community` backend принимает только: - `REST` - - `GraphQL` - - `gRPC` - - `security_level = standard` +- `security_level = standard` - попытка создать, обновить или импортировать операцию с неподдерживаемым `protocol` или `security_level` должна завершаться `validation_error` еще на стороне `admin-api`, а не только скрываться в UI. ### 5.4. Samples and descriptors diff --git a/docs/commercial-boundaries.md b/docs/commercial-boundaries.md index 03bcd02..7f01e6c 100644 --- a/docs/commercial-boundaries.md +++ b/docs/commercial-boundaries.md @@ -26,7 +26,7 @@ - доменная модель Community; - `admin-api`, `mcp-server` и `ui`, необходимые для Community; -- `REST`, `GraphQL` и `gRPC unary` в открытой редакции; +- `REST` в открытой редакции; - секреты, auth profiles, agent publishing, logs и usage; - статический ключ AI-агента; - контейнерное развертывание Community; @@ -38,6 +38,7 @@ - short-lived token service; - one-time token service; +- `GraphQL` и `gRPC unary`, если они выводятся из Community; - `SSO`, `2FA`, расширенная `RBAC`, `audit log`; - `WebSocket`, `SOAP`, `gRPC streaming`, если они не включаются в Community; - advanced streaming execution modes; diff --git a/docs/frontend-roadmap.md b/docs/frontend-roadmap.md index b104ad0..dfd1746 100644 --- a/docs/frontend-roadmap.md +++ b/docs/frontend-roadmap.md @@ -27,7 +27,7 @@ UI должен честно отражать различия редакций: -- Community не должен показывать доступные к настройке `WebSocket`, `SOAP`, `gRPC streaming`, если они не входят в открытую поставку; +- Community не должен показывать доступные к настройке `GraphQL`, `gRPC`, `WebSocket`, `SOAP`, если они не входят в открытую поставку; - `security_level = elevated` и `security_level = strict` не должны выглядеть рабочими в Community; - multi-user и enterprise controls должны скрываться или отображаться как capability-locked. diff --git a/docs/product-editions.md b/docs/product-editions.md index e2b5449..aef6a34 100644 --- a/docs/product-editions.md +++ b/docs/product-editions.md @@ -42,8 +42,6 @@ Crank развивается как три редакции: - один AI-агент; - протоколы: - `REST / HTTP` - - `GraphQL` - - `gRPC unary` - создание и публикация операций; - журналы вызовов и метрики использования; - секреты и профили аутентификации для внешних сервисов; @@ -58,6 +56,8 @@ Crank развивается как три редакции: - несколько пользователей; - `SSO`, `2FA`, развитая `RBAC`, `audit log`; - короткоживущие и одноразовые машинные токены; +- `GraphQL`; +- `gRPC unary`; - `WebSocket`, `SOAP`, `gRPC streaming`; - режимы `window`, `session`, `async_job`; - биллинг и usage metering для SaaS; @@ -79,6 +79,8 @@ Crank развивается как три редакции: - несколько рабочих областей; - несколько пользователей; - `SSO`, `2FA`, расширенная `RBAC`, `audit log`; +- `GraphQL`; +- `gRPC unary`; - короткоживущие машинные токены; - одноразовые токены для критичных операций; - `security_level = elevated` и `security_level = strict`; @@ -114,7 +116,8 @@ Crank развивается как три редакции: | Workspace count | 1 | many | many | | User count | 1 | many | many | | Agent count | 1 | many | many | -| REST / GraphQL / gRPC unary | yes | yes | yes | +| REST / HTTP | yes | yes | yes | +| GraphQL / gRPC unary | no | yes | yes | | WebSocket / SOAP / gRPC streaming | no | yes | yes | | Streaming modes | no | yes | yes | | Static agent key | yes | yes | yes | diff --git a/docs/refactoring-roadmap.md b/docs/refactoring-roadmap.md index 122723a..e1eaa79 100644 --- a/docs/refactoring-roadmap.md +++ b/docs/refactoring-roadmap.md @@ -82,6 +82,7 @@ Что нужно: - определить Community-supported protocol set; +- зафиксировать `REST` как единственный Community protocol surface; - вынести premium protocols и premium execution modes в private delivery plan; - синхронизировать UI, docs и release process. diff --git a/docs/repository-split-map.md b/docs/repository-split-map.md index 7169585..bab2c16 100644 --- a/docs/repository-split-map.md +++ b/docs/repository-split-map.md @@ -39,7 +39,8 @@ Public repository. - Community runtime; - Community UI; - `admin-api`, `mcp-server`, `ui`; -- `crank-core`, `crank-registry`, `crank-runtime`, adapters, schema and mapping crates; +- `crank-core`, `crank-registry`, `crank-runtime`, schema and mapping crates; +- только `REST`-ориентированный Community protocol surface; - capability model; - Community auth model: - static AI-agent key; @@ -63,6 +64,7 @@ Private repository. В нем должны жить: - self-hosted commercial extensions; +- `GraphQL` и `gRPC unary`, если они окончательно выводятся из Community; - short-lived token service; - one-time token service; - enterprise access/governance: @@ -99,9 +101,12 @@ Private repository. - весь текущий Community runtime и UI - все public extension seams - все capability checks +- `REST`-only product surface Уходит в `crank-enterprise`: +- `crank-adapter-graphql` +- `crank-adapter-grpc` - private auth/token/governance services - enterprise-only runtime additions