diff --git a/client-gui2 b/client-gui2 index e87bff75..27d9d487 160000 --- a/client-gui2 +++ b/client-gui2 @@ -1 +1 @@ -Subproject commit e87bff759a1e398ca0ac08e3c66921cc2ca98858 +Subproject commit 27d9d487fe2d8bc7ce20a5e1ddb2c72b9ad8b1a3 diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index 3444dbea..64d93d15 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -55,6 +55,17 @@ service TableDefinition { // Copies one complete profile into a new profile without copying table data. rpc CopyProfile(CopyProfileRequest) returns (CopyProfileResponse); + // Serializes selected table structure and every required structural dependency. + // Dynamic table rows and deployment-local security state are never included. + rpc ExportProfileBlueprint(ExportProfileBlueprintRequest) returns (ExportProfileBlueprintResponse); + + // Reconciles a portable blueprint against a destination profile without writing. + rpc PlanProfileBlueprintImport(PlanProfileBlueprintImportRequest) returns (PlanProfileBlueprintImportResponse); + + // Applies a previously reviewed blueprint plan atomically. The plan hash protects + // against both a changed package and destination drift between preview and apply. + rpc ImportProfileBlueprint(ImportProfileBlueprintRequest) returns (ImportProfileBlueprintResponse); + // Returns the unified append-only history for column and option aliases. rpc GetAliasChangeHistory(GetAliasChangeHistoryRequest) returns (GetAliasChangeHistoryResponse); @@ -482,6 +493,56 @@ message CopyProfileResponse { int32 scripts_copied = 4; } +message ExportProfileBlueprintRequest { + string profile_name = 1; + // Empty exports every dynamic table in the profile. Otherwise only these tables + // are created by import; their unselected local dependencies become requirements. + repeated string table_names = 2; +} + +message ExportProfileBlueprintResponse { + string blueprint_json = 1; + string blueprint_sha256 = 2; + int32 tables = 3; + int32 required_local_tables = 4; + int32 required_global_tables = 5; + int32 templates = 6; +} + +message PlanProfileBlueprintImportRequest { + string target_profile_name = 1; + string blueprint_json = 2; +} + +message ProfileBlueprintImportAction { + // One of create, reuse, add, or conflict. + string action = 1; + string resource = 2; + string detail = 3; +} + +message PlanProfileBlueprintImportResponse { + bool can_import = 1; + string plan_sha256 = 2; + repeated ProfileBlueprintImportAction actions = 3; +} + +message ImportProfileBlueprintRequest { + string target_profile_name = 1; + string blueprint_json = 2; + string expected_plan_sha256 = 3; +} + +message ImportProfileBlueprintResponse { + bool success = 1; + string message = 2; + int32 tables_created = 3; + int32 globals_created = 4; + int32 globals_reused = 5; + int32 templates_created = 6; + int32 templates_reused = 7; +} + enum AliasChangeKind { ALIAS_CHANGE_KIND_UNSPECIFIED = 0; ALIAS_CHANGE_KIND_COLUMN = 1; diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index b217b0a0..0bddda4a 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index 7947782a..1770d8c6 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -456,6 +456,89 @@ pub struct CopyProfileResponse { } #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExportProfileBlueprintRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + /// Empty exports every dynamic table in the profile. Otherwise only these tables + /// are created by import; their unselected local dependencies become requirements. + #[prost(string, repeated, tag = "2")] + pub table_names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExportProfileBlueprintResponse { + #[prost(string, tag = "1")] + pub blueprint_json: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub blueprint_sha256: ::prost::alloc::string::String, + #[prost(int32, tag = "3")] + pub tables: i32, + #[prost(int32, tag = "4")] + pub required_local_tables: i32, + #[prost(int32, tag = "5")] + pub required_global_tables: i32, + #[prost(int32, tag = "6")] + pub templates: i32, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PlanProfileBlueprintImportRequest { + #[prost(string, tag = "1")] + pub target_profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub blueprint_json: ::prost::alloc::string::String, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ProfileBlueprintImportAction { + /// One of create, reuse, add, or conflict. + #[prost(string, tag = "1")] + pub action: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub resource: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub detail: ::prost::alloc::string::String, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PlanProfileBlueprintImportResponse { + #[prost(bool, tag = "1")] + pub can_import: bool, + #[prost(string, tag = "2")] + pub plan_sha256: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "3")] + pub actions: ::prost::alloc::vec::Vec, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ImportProfileBlueprintRequest { + #[prost(string, tag = "1")] + pub target_profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub blueprint_json: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub expected_plan_sha256: ::prost::alloc::string::String, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ImportProfileBlueprintResponse { + #[prost(bool, tag = "1")] + pub success: bool, + #[prost(string, tag = "2")] + pub message: ::prost::alloc::string::String, + #[prost(int32, tag = "3")] + pub tables_created: i32, + #[prost(int32, tag = "4")] + pub globals_created: i32, + #[prost(int32, tag = "5")] + pub globals_reused: i32, + #[prost(int32, tag = "6")] + pub templates_created: i32, + #[prost(int32, tag = "7")] + pub templates_reused: i32, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetAliasChangeHistoryRequest { #[prost(string, tag = "1")] pub profile_name: ::prost::alloc::string::String, @@ -1533,6 +1616,98 @@ pub mod table_definition_client { ); self.inner.unary(req, path, codec).await } + /// Serializes selected table structure and every required structural dependency. + /// Dynamic table rows and deployment-local security state are never included. + pub async fn export_profile_blueprint( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.table_definition.TableDefinition/ExportProfileBlueprint", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.table_definition.TableDefinition", + "ExportProfileBlueprint", + ), + ); + self.inner.unary(req, path, codec).await + } + /// Reconciles a portable blueprint against a destination profile without writing. + pub async fn plan_profile_blueprint_import( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.table_definition.TableDefinition/PlanProfileBlueprintImport", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.table_definition.TableDefinition", + "PlanProfileBlueprintImport", + ), + ); + self.inner.unary(req, path, codec).await + } + /// Applies a previously reviewed blueprint plan atomically. The plan hash protects + /// against both a changed package and destination drift between preview and apply. + pub async fn import_profile_blueprint( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.table_definition.TableDefinition/ImportProfileBlueprint", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.table_definition.TableDefinition", + "ImportProfileBlueprint", + ), + ); + self.inner.unary(req, path, codec).await + } /// Returns the unified append-only history for column and option aliases. pub async fn get_alias_change_history( &mut self, @@ -1927,6 +2102,32 @@ pub mod table_definition_server { tonic::Response, tonic::Status, >; + /// Serializes selected table structure and every required structural dependency. + /// Dynamic table rows and deployment-local security state are never included. + async fn export_profile_blueprint( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Reconciles a portable blueprint against a destination profile without writing. + async fn plan_profile_blueprint_import( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Applies a previously reviewed blueprint plan atomically. The plan hash protects + /// against both a changed package and destination drift between preview and apply. + async fn import_profile_blueprint( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Returns the unified append-only history for column and option aliases. async fn get_alias_change_history( &self, @@ -2619,6 +2820,156 @@ pub mod table_definition_server { }; Box::pin(fut) } + "/komp_ac.table_definition.TableDefinition/ExportProfileBlueprint" => { + #[allow(non_camel_case_types)] + struct ExportProfileBlueprintSvc(pub Arc); + impl< + T: TableDefinition, + > tonic::server::UnaryService + for ExportProfileBlueprintSvc { + type Response = super::ExportProfileBlueprintResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::export_profile_blueprint( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ExportProfileBlueprintSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.table_definition.TableDefinition/PlanProfileBlueprintImport" => { + #[allow(non_camel_case_types)] + struct PlanProfileBlueprintImportSvc(pub Arc); + impl< + T: TableDefinition, + > tonic::server::UnaryService< + super::PlanProfileBlueprintImportRequest, + > for PlanProfileBlueprintImportSvc { + type Response = super::PlanProfileBlueprintImportResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::PlanProfileBlueprintImportRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::plan_profile_blueprint_import( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = PlanProfileBlueprintImportSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.table_definition.TableDefinition/ImportProfileBlueprint" => { + #[allow(non_camel_case_types)] + struct ImportProfileBlueprintSvc(pub Arc); + impl< + T: TableDefinition, + > tonic::server::UnaryService + for ImportProfileBlueprintSvc { + type Response = super::ImportProfileBlueprintResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::import_profile_blueprint( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ImportProfileBlueprintSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.table_definition.TableDefinition/GetAliasChangeHistory" => { #[allow(non_camel_case_types)] struct GetAliasChangeHistorySvc(pub Arc); diff --git a/komp-app/src/grpc.rs b/komp-app/src/grpc.rs index d3119a89..ffc3a431 100644 --- a/komp-app/src/grpc.rs +++ b/komp-app/src/grpc.rs @@ -1335,6 +1335,33 @@ impl GrpcClient { .into_inner()) } + pub async fn export_profile_blueprint( + &mut self, + request: common::proto::komp_ac::table_definition::ExportProfileBlueprintRequest, + ) -> Result { + let request = self.authenticated_request(request)?; + Ok(self.table_definition_client.export_profile_blueprint(request).await + .context("gRPC ExportProfileBlueprint call failed")?.into_inner()) + } + + pub async fn plan_profile_blueprint_import( + &mut self, + request: common::proto::komp_ac::table_definition::PlanProfileBlueprintImportRequest, + ) -> Result { + let request = self.authenticated_request(request)?; + Ok(self.table_definition_client.plan_profile_blueprint_import(request).await + .context("gRPC PlanProfileBlueprintImport call failed")?.into_inner()) + } + + pub async fn import_profile_blueprint( + &mut self, + request: common::proto::komp_ac::table_definition::ImportProfileBlueprintRequest, + ) -> Result { + let request = self.authenticated_request(request)?; + Ok(self.table_definition_client.import_profile_blueprint(request).await + .context("gRPC ImportProfileBlueprint call failed")?.into_inner()) + } + pub async fn get_alias_change_history( &mut self, request: common::proto::komp_ac::table_definition::GetAliasChangeHistoryRequest, diff --git a/prod_data_parse_scripts/parse_uctovnictvo.sh b/prod_data_parse_scripts/parse_uctovnictvo.sh index 37e0433a..7ec0e3c2 100755 --- a/prod_data_parse_scripts/parse_uctovnictvo.sh +++ b/prod_data_parse_scripts/parse_uctovnictvo.sh @@ -34,12 +34,20 @@ process_csv() { OFS = FS } + { + sub(/\r$/, "", $0) + } + NR == 1 { print next } { + if ($6 == "- -") { + $6 = "" + } + cleaned = clean_account($10) if (cleaned == "") { @@ -47,6 +55,11 @@ process_csv() { } key = cleaned + + if (key == "000") { + next + } + key_count[key]++ if (!(key in key_order)) { key_order[key] = ++key_total diff --git a/server b/server index cfc19981..9325e5b5 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit cfc199812191284cf00ec06d731565e0e52bffa1 +Subproject commit 9325e5b53776ae14016cb227cba4982237b937ba