From 6d8fa0de636f5b73f660968f2963c2f98a81b09f Mon Sep 17 00:00:00 2001 From: Priec Date: Tue, 19 May 2026 14:30:56 +0200 Subject: [PATCH] cant change logic once data are in that column --- Cargo.lock | 12 +- client | 2 +- common/build.rs | 16 +++ common/proto/table_definition.proto | 19 ++++ common/proto/table_validation.proto | 17 +++ common/src/proto/descriptor.bin | Bin 73489 -> 75490 bytes common/src/proto/komp_ac.table_definition.rs | 103 ++++++++++++++++++ common/src/proto/komp_ac.table_validation.rs | 109 +++++++++++++++++++ server | 2 +- 9 files changed, 272 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 551131d..ce4e12a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,7 +493,7 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "canvas" -version = "0.6.11" +version = "0.6.12" dependencies = [ "anyhow", "async-trait", @@ -586,7 +586,7 @@ dependencies = [ [[package]] name = "client" -version = "0.6.11" +version = "0.6.12" dependencies = [ "anyhow", "async-trait", @@ -642,7 +642,7 @@ dependencies = [ [[package]] name = "common" -version = "0.6.11" +version = "0.6.12" dependencies = [ "prost 0.13.5", "prost-build 0.14.1", @@ -3117,7 +3117,7 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "search" -version = "0.6.11" +version = "0.6.12" dependencies = [ "anyhow", "common", @@ -3216,7 +3216,7 @@ dependencies = [ [[package]] name = "server" -version = "0.6.11" +version = "0.6.12" dependencies = [ "anyhow", "bcrypt", @@ -4549,7 +4549,7 @@ dependencies = [ [[package]] name = "validation-core" -version = "0.6.11" +version = "0.6.12" dependencies = [ "regex", "serde", diff --git a/client b/client index 4f8c712..3badee2 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 4f8c71274ac36a32cf61715f675da0e3acf80d3b +Subproject commit 3badee28b165e53f9b9968d762a23edeb48a7dcb diff --git a/common/build.rs b/common/build.rs index 9189ae8..aeefe9a 100644 --- a/common/build.rs +++ b/common/build.rs @@ -8,6 +8,10 @@ fn main() -> Result<(), Box> { ".komp_ac.table_validation.FieldValidation", "#[derive(serde::Serialize, serde::Deserialize)]", ) + .field_attribute( + ".komp_ac.table_validation.FieldValidation.locked", + "#[serde(default)]", + ) .type_attribute( ".komp_ac.table_validation.CharacterLimits", "#[derive(serde::Serialize, serde::Deserialize)]", @@ -136,6 +140,14 @@ fn main() -> Result<(), Box> { ".komp_ac.table_validation.ApplyValidationSetResponse", "#[derive(serde::Serialize, serde::Deserialize)]", ) + .type_attribute( + ".komp_ac.table_validation.LockFieldValidationRequest", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) + .type_attribute( + ".komp_ac.table_validation.LockFieldValidationResponse", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) // Enum -> readable strings in JSON ("BYTES", "DISPLAY_WIDTH") .type_attribute( ".komp_ac.table_validation.CountMode", @@ -161,6 +173,10 @@ fn main() -> Result<(), Box> { ".komp_ac.table_definition.PostTableDefinitionRequest", "#[derive(serde::Serialize, serde::Deserialize)]", ) + .type_attribute( + ".komp_ac.table_definition.AddTableColumnsRequest", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) .type_attribute( ".komp_ac.table_definition.TableDefinitionResponse", "#[derive(serde::Serialize, serde::Deserialize)]" diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index aeab0de..d535eca 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -14,6 +14,10 @@ service TableDefinition { // Also inserts metadata and default validation rules. Entirely transactional. rpc PostTableDefinition(PostTableDefinitionRequest) returns (TableDefinitionResponse); + // Appends new user-defined columns to an existing table. + // Existing columns, links, and table logic are never changed by this call. + rpc AddTableColumns(AddTableColumnsRequest) returns (TableDefinitionResponse); + // Lists all profiles (schemas) and their tables with declared dependencies. // This provides a tree-like overview of table relationships. rpc GetProfileTree(komp_ac.common.Empty) returns (ProfileTreeResponse); @@ -72,6 +76,21 @@ message PostTableDefinitionRequest { string profile_name = 5; } +// Defines append-only column additions for an existing table. +message AddTableColumnsRequest { + // Existing profile/schema name. + string profile_name = 1; + + // Existing table name in the profile. + string table_name = 2; + + // New user-defined columns only. Existing columns cannot be changed here. + repeated ColumnDefinition columns = 3; + + // Optional indexes for the new columns only. + repeated string indexes = 4; +} + // Describes one user-defined column for a table. message ColumnDefinition { // Column name that follows the same validation rules as table_name. diff --git a/common/proto/table_validation.proto b/common/proto/table_validation.proto index bcfb8ab..a60015b 100644 --- a/common/proto/table_validation.proto +++ b/common/proto/table_validation.proto @@ -52,6 +52,9 @@ message FieldValidation { // Field must be provided / treated as required by clients and server enforcement layers. bool required = 4; + + // Once locked, this field's validation config cannot be changed. + bool locked = 15; } // Character length counting mode @@ -191,6 +194,9 @@ service TableValidationService { // Snapshot a reusable set onto a concrete table field. rpc ApplyValidationSet(ApplyValidationSetRequest) returns (ApplyValidationSetResponse); + + // Permanently lock one field's validation config. + rpc LockFieldValidation(LockFieldValidationRequest) returns (LockFieldValidationResponse); } message UpdateFieldValidationRequest { @@ -320,3 +326,14 @@ message ApplyValidationSetResponse { string message = 2; FieldValidation validation = 3; } + +message LockFieldValidationRequest { + string profileName = 1; + string tableName = 2; + string dataKey = 3; +} + +message LockFieldValidationResponse { + bool success = 1; + string message = 2; +} diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index fae9cd08cb06abf422896dc7148d26086aaadf8d..0985e4803134cdb8547d97aba3010f586f0b94b6 100644 GIT binary patch delta 12454 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 delta 10968 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 olfj#A3B)n0ZN?kA1C5OT>t<8 diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index a137362..aa908ad 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -43,6 +43,23 @@ pub struct PostTableDefinitionRequest { #[prost(string, tag = "5")] pub profile_name: ::prost::alloc::string::String, } +/// Defines append-only column additions for an existing table. +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AddTableColumnsRequest { + /// Existing profile/schema name. + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + /// Existing table name in the profile. + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + /// New user-defined columns only. Existing columns cannot be changed here. + #[prost(message, repeated, tag = "3")] + pub columns: ::prost::alloc::vec::Vec, + /// Optional indexes for the new columns only. + #[prost(string, repeated, tag = "4")] + pub indexes: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} /// Describes one user-defined column for a table. #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -359,6 +376,37 @@ pub mod table_definition_client { ); self.inner.unary(req, path, codec).await } + /// Appends new user-defined columns to an existing table. + /// Existing columns, links, and table logic are never changed by this call. + pub async fn add_table_columns( + &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::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.table_definition.TableDefinition/AddTableColumns", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.table_definition.TableDefinition", + "AddTableColumns", + ), + ); + self.inner.unary(req, path, codec).await + } /// Lists all profiles (schemas) and their tables with declared dependencies. /// This provides a tree-like overview of table relationships. pub async fn get_profile_tree( @@ -536,6 +584,15 @@ pub mod table_definition_server { tonic::Response, tonic::Status, >; + /// Appends new user-defined columns to an existing table. + /// Existing columns, links, and table logic are never changed by this call. + async fn add_table_columns( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Lists all profiles (schemas) and their tables with declared dependencies. /// This provides a tree-like overview of table relationships. async fn get_profile_tree( @@ -708,6 +765,52 @@ pub mod table_definition_server { }; Box::pin(fut) } + "/komp_ac.table_definition.TableDefinition/AddTableColumns" => { + #[allow(non_camel_case_types)] + struct AddTableColumnsSvc(pub Arc); + impl< + T: TableDefinition, + > tonic::server::UnaryService + for AddTableColumnsSvc { + type Response = super::TableDefinitionResponse; + 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 { + ::add_table_columns(&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 = AddTableColumnsSvc(inner); + let codec = tonic::codec::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/GetProfileTree" => { #[allow(non_camel_case_types)] struct GetProfileTreeSvc(pub Arc); diff --git a/common/src/proto/komp_ac.table_validation.rs b/common/src/proto/komp_ac.table_validation.rs index 51baf6c..80e9326 100644 --- a/common/src/proto/komp_ac.table_validation.rs +++ b/common/src/proto/komp_ac.table_validation.rs @@ -43,6 +43,10 @@ pub struct FieldValidation { /// Field must be provided / treated as required by clients and server enforcement layers. #[prost(bool, tag = "4")] pub required: bool, + /// Once locked, this field's validation config cannot be changed. + #[prost(bool, tag = "15")] + #[serde(default)] + pub locked: bool, } /// Character limit validation (Validation 1). /// These rules map directly to canvas CharacterLimits. @@ -368,6 +372,24 @@ pub struct ApplyValidationSetResponse { #[prost(message, optional, tag = "3")] pub validation: ::core::option::Option, } +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LockFieldValidationRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub data_key: ::prost::alloc::string::String, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LockFieldValidationResponse { + #[prost(bool, tag = "1")] + pub success: bool, + #[prost(string, tag = "2")] + pub message: ::prost::alloc::string::String, +} /// Character length counting mode #[derive(serde::Serialize, serde::Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -877,6 +899,36 @@ pub mod table_validation_service_client { ); self.inner.unary(req, path, codec).await } + /// Permanently lock one field's validation config. + pub async fn lock_field_validation( + &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::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.table_validation.TableValidationService/LockFieldValidation", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.table_validation.TableValidationService", + "LockFieldValidation", + ), + ); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -967,6 +1019,14 @@ pub mod table_validation_service_server { tonic::Response, tonic::Status, >; + /// Permanently lock one field's validation config. + async fn lock_field_validation( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// Service for storing and fetching field-validation definitions. #[derive(Debug)] @@ -1544,6 +1604,55 @@ pub mod table_validation_service_server { }; Box::pin(fut) } + "/komp_ac.table_validation.TableValidationService/LockFieldValidation" => { + #[allow(non_camel_case_types)] + struct LockFieldValidationSvc(pub Arc); + impl< + T: TableValidationService, + > tonic::server::UnaryService + for LockFieldValidationSvc { + type Response = super::LockFieldValidationResponse; + 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 { + ::lock_field_validation( + &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 = LockFieldValidationSvc(inner); + let codec = tonic::codec::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) + } _ => { Box::pin(async move { let mut response = http::Response::new( diff --git a/server b/server index 2f933e4..aa0f9a3 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 2f933e4e34e1417f2eeb1024f98a95e1c598b06f +Subproject commit aa0f9a310897adf1560b3a364ae83d38ae0a0f68