From 94b3f642cf9b35c1203a773180dbe62a4db7fbc5 Mon Sep 17 00:00:00 2001 From: Robert Laszczak Date: Wed, 22 Apr 2026 15:21:30 +0200 Subject: [PATCH] hotfix: reset-exercise and merge-conflict 'replace with example solution' no longer delete inherited project files when the previous exercise is text-only --- trainings/api/protobuf/server.proto | 13 + trainings/exercise_replace.go | 28 +- trainings/exercise_replace_test.go | 52 --- trainings/exercises.go | 45 --- trainings/genproto/server.pb.go | 474 +++++++++++++++++---------- trainings/genproto/server_grpc.pb.go | 69 +++- trainings/next.go | 65 ++-- trainings/reset.go | 42 +-- 8 files changed, 420 insertions(+), 368 deletions(-) diff --git a/trainings/api/protobuf/server.proto b/trainings/api/protobuf/server.proto index f4f6f4c..b01e343 100644 --- a/trainings/api/protobuf/server.proto +++ b/trainings/api/protobuf/server.proto @@ -26,6 +26,8 @@ service Trainings { rpc GetGoldenSolution(GetGoldenSolutionRequest) returns (GetGoldenSolutionResponse) {}; + rpc GetExerciseStartState(GetExerciseStartStateRequest) returns (GetExerciseStartStateResponse) {}; + rpc GetAgentInstructions(GetAgentInstructionsRequest) returns (GetAgentInstructionsResponse) {}; rpc Ping(google.protobuf.Empty) returns (google.protobuf.Empty) {}; @@ -267,6 +269,17 @@ message GetGoldenSolutionResponse { repeated File files = 2; } +message GetExerciseStartStateRequest { + string training_name = 1; + string exercise_id = 2; + string token = 3; +} + +message GetExerciseStartStateResponse { + string dir = 1; + repeated File files = 2; +} + message GetAgentInstructionsRequest { string training_name = 1; string token = 2; diff --git a/trainings/exercise_replace.go b/trainings/exercise_replace.go index 93078e7..05983f6 100644 --- a/trainings/exercise_replace.go +++ b/trainings/exercise_replace.go @@ -31,35 +31,13 @@ func replaceExerciseFiles( return files.NewFilesSilentDeleteUnused().WriteExerciseFiles(replacementFiles, trainingRootFs, exerciseDir) } -// mergeStartStateFiles returns the starting state of an exercise: -// golden(prev) with scaffold(current) overlaid on top. Scaffold wins on path -// collisions because it represents the delta from end-of-prev to start-of-current. -// Pass nil goldenFiles for the first exercise. -func mergeStartStateFiles( - goldenFiles []*genproto.File, - scaffoldFiles []*genproto.File, -) []*genproto.File { - byPath := make(map[string]*genproto.File, len(goldenFiles)+len(scaffoldFiles)) - for _, f := range goldenFiles { - byPath[f.Path] = f - } - for _, f := range scaffoldFiles { - byPath[f.Path] = f // scaffold wins on collisions - } - merged := make([]*genproto.File, 0, len(byPath)) - for _, f := range byPath { - merged = append(merged, f) - } - return merged -} - // replaceExerciseFilesAndCommit is the complete orchestration for replacing // the user's exercise files with a given file list: save backup → write files // (1:1, deletes extras) → stage → commit. ALL callers that replace the user's // solution with example / start-state content MUST go through this function: -// - overrideWithGolden ('s' action): files = golden(current) -// - g during next/merge-conflict: files = golden(prev) + scaffold(current) -// - resetCleanFiles: files = golden(prev) + scaffold(current) +// - overrideWithGolden ('s' action): files = golden(current) via GetGoldenSolution +// - g during next/merge-conflict: files = start state via GetExerciseStartState +// - resetCleanFiles: files = start state via GetExerciseStartState // // The backup branch is REQUIRED — destructive ops must always be recoverable. // If saveToBackupBranch returns errBackupAborted, that error is returned directly diff --git a/trainings/exercise_replace_test.go b/trainings/exercise_replace_test.go index f32d5f7..b86dba2 100644 --- a/trainings/exercise_replace_test.go +++ b/trainings/exercise_replace_test.go @@ -11,58 +11,6 @@ import ( "github.com/ThreeDotsLabs/cli/trainings/genproto" ) -func TestMergeStartStateFiles(t *testing.T) { - t.Run("first exercise (nil golden) returns scaffold as-is", func(t *testing.T) { - scaffold := []*genproto.File{ - {Path: "a.txt", Content: "A"}, - {Path: "b.txt", Content: "B"}, - } - merged := mergeStartStateFiles(nil, scaffold) - assertFilesByPath(t, merged, map[string]string{ - "a.txt": "A", - "b.txt": "B", - }) - }) - - t.Run("scaffold wins on path collision", func(t *testing.T) { - golden := []*genproto.File{ - {Path: "shared.txt", Content: "from golden"}, - {Path: "golden-only.txt", Content: "G"}, - } - scaffold := []*genproto.File{ - {Path: "shared.txt", Content: "from scaffold"}, // overrides golden - {Path: "scaffold-only.txt", Content: "S"}, - } - merged := mergeStartStateFiles(golden, scaffold) - assertFilesByPath(t, merged, map[string]string{ - "shared.txt": "from scaffold", - "golden-only.txt": "G", - "scaffold-only.txt": "S", - }) - }) - - t.Run("regression: golden with filled-in placeholder is preserved when scaffold does not redeliver it", func(t *testing.T) { - // This is the 0001_init_orders.up.sql scenario. - // Earlier exercises scaffolded the file as empty; the user filled it in. - // By a later exercise, the scaffold no longer includes that file — only - // the prev-exercise golden does. The start state must preserve the - // filled-in content. - golden := []*genproto.File{ - {Path: "migrations/0001_init.sql", Content: "CREATE TABLE ..."}, - {Path: "common.go", Content: "package common"}, - } - scaffold := []*genproto.File{ - {Path: "new_for_this_exercise.go", Content: "package new"}, - } - merged := mergeStartStateFiles(golden, scaffold) - assertFilesByPath(t, merged, map[string]string{ - "migrations/0001_init.sql": "CREATE TABLE ...", // survives from golden - "common.go": "package common", - "new_for_this_exercise.go": "package new", - }) - }) -} - func TestReplaceExerciseFiles_is1to1(t *testing.T) { // The invariant: after replaceExerciseFiles, exerciseDir contains exactly // the replacement files — any extras are deleted. This is load-bearing diff --git a/trainings/exercises.go b/trainings/exercises.go index 9b004fd..25a4b2a 100644 --- a/trainings/exercises.go +++ b/trainings/exercises.go @@ -24,48 +24,3 @@ func (h *Handlers) fetchGoldenFiles( } return resp.Files, nil } - -// resolvePreviousExercise returns the ID and module/exercise path of the exercise -// immediately preceding currentExerciseID in the training's order. Returns -// ("", "", nil) when currentExerciseID is the first exercise. -// -// One GetExercises call per invocation — callers may cache the result per-command -// if they need it more than once, but the common case (single reset / single 'g') -// only needs it once. -func (h *Handlers) resolvePreviousExercise( - ctx context.Context, - trainingName, token, currentExerciseID string, -) (prevExerciseID, prevModuleExercisePath string, err error) { - resp, err := h.newGrpcClient().GetExercises(ctx, &genproto.GetExercisesRequest{ - TrainingName: trainingName, - Token: token, - }) - if err != nil { - return "", "", fmt.Errorf("failed to list exercises: %w", err) - } - - type flatExercise struct { - id string - moduleExercisePath string - } - var flat []flatExercise - for _, module := range resp.Modules { - for _, exercise := range module.Exercises { - flat = append(flat, flatExercise{ - id: exercise.Id, - moduleExercisePath: module.Name + "/" + exercise.Name, - }) - } - } - - for i, e := range flat { - if e.id == currentExerciseID { - if i == 0 { - return "", "", nil // first exercise — no predecessor - } - return flat[i-1].id, flat[i-1].moduleExercisePath, nil - } - } - - return "", "", fmt.Errorf("exercise %s not found in training %s", currentExerciseID, trainingName) -} diff --git a/trainings/genproto/server.pb.go b/trainings/genproto/server.pb.go index 10ef871..ca122a8 100644 --- a/trainings/genproto/server.pb.go +++ b/trainings/genproto/server.pb.go @@ -7,14 +7,13 @@ package genproto import ( - reflect "reflect" - sync "sync" - unsafe "unsafe" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" ) const ( @@ -1872,6 +1871,118 @@ func (x *GetGoldenSolutionResponse) GetFiles() []*File { return nil } +type GetExerciseStartStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + TrainingName string `protobuf:"bytes,1,opt,name=training_name,json=trainingName,proto3" json:"training_name,omitempty"` + ExerciseId string `protobuf:"bytes,2,opt,name=exercise_id,json=exerciseId,proto3" json:"exercise_id,omitempty"` + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetExerciseStartStateRequest) Reset() { + *x = GetExerciseStartStateRequest{} + mi := &file_server_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetExerciseStartStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetExerciseStartStateRequest) ProtoMessage() {} + +func (x *GetExerciseStartStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_server_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetExerciseStartStateRequest.ProtoReflect.Descriptor instead. +func (*GetExerciseStartStateRequest) Descriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{30} +} + +func (x *GetExerciseStartStateRequest) GetTrainingName() string { + if x != nil { + return x.TrainingName + } + return "" +} + +func (x *GetExerciseStartStateRequest) GetExerciseId() string { + if x != nil { + return x.ExerciseId + } + return "" +} + +func (x *GetExerciseStartStateRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type GetExerciseStartStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Dir string `protobuf:"bytes,1,opt,name=dir,proto3" json:"dir,omitempty"` + Files []*File `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetExerciseStartStateResponse) Reset() { + *x = GetExerciseStartStateResponse{} + mi := &file_server_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetExerciseStartStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetExerciseStartStateResponse) ProtoMessage() {} + +func (x *GetExerciseStartStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_server_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetExerciseStartStateResponse.ProtoReflect.Descriptor instead. +func (*GetExerciseStartStateResponse) Descriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{31} +} + +func (x *GetExerciseStartStateResponse) GetDir() string { + if x != nil { + return x.Dir + } + return "" +} + +func (x *GetExerciseStartStateResponse) GetFiles() []*File { + if x != nil { + return x.Files + } + return nil +} + type GetAgentInstructionsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` TrainingName string `protobuf:"bytes,1,opt,name=training_name,json=trainingName,proto3" json:"training_name,omitempty"` @@ -1882,7 +1993,7 @@ type GetAgentInstructionsRequest struct { func (x *GetAgentInstructionsRequest) Reset() { *x = GetAgentInstructionsRequest{} - mi := &file_server_proto_msgTypes[30] + mi := &file_server_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1894,7 +2005,7 @@ func (x *GetAgentInstructionsRequest) String() string { func (*GetAgentInstructionsRequest) ProtoMessage() {} func (x *GetAgentInstructionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[30] + mi := &file_server_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1907,7 +2018,7 @@ func (x *GetAgentInstructionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgentInstructionsRequest.ProtoReflect.Descriptor instead. func (*GetAgentInstructionsRequest) Descriptor() ([]byte, []int) { - return file_server_proto_rawDescGZIP(), []int{30} + return file_server_proto_rawDescGZIP(), []int{32} } func (x *GetAgentInstructionsRequest) GetTrainingName() string { @@ -1933,7 +2044,7 @@ type GetAgentInstructionsResponse struct { func (x *GetAgentInstructionsResponse) Reset() { *x = GetAgentInstructionsResponse{} - mi := &file_server_proto_msgTypes[31] + mi := &file_server_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1945,7 +2056,7 @@ func (x *GetAgentInstructionsResponse) String() string { func (*GetAgentInstructionsResponse) ProtoMessage() {} func (x *GetAgentInstructionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[31] + mi := &file_server_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1958,7 +2069,7 @@ func (x *GetAgentInstructionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAgentInstructionsResponse.ProtoReflect.Descriptor instead. func (*GetAgentInstructionsResponse) Descriptor() ([]byte, []int) { - return file_server_proto_rawDescGZIP(), []int{31} + return file_server_proto_rawDescGZIP(), []int{33} } func (x *GetAgentInstructionsResponse) GetAgentInstructions() string { @@ -1978,7 +2089,7 @@ type NextExerciseResponse_Module struct { func (x *NextExerciseResponse_Module) Reset() { *x = NextExerciseResponse_Module{} - mi := &file_server_proto_msgTypes[32] + mi := &file_server_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1990,7 +2101,7 @@ func (x *NextExerciseResponse_Module) String() string { func (*NextExerciseResponse_Module) ProtoMessage() {} func (x *NextExerciseResponse_Module) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[32] + mi := &file_server_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2031,7 +2142,7 @@ type NextExerciseResponse_Exercise struct { func (x *NextExerciseResponse_Exercise) Reset() { *x = NextExerciseResponse_Exercise{} - mi := &file_server_proto_msgTypes[33] + mi := &file_server_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2043,7 +2154,7 @@ func (x *NextExerciseResponse_Exercise) String() string { func (*NextExerciseResponse_Exercise) ProtoMessage() {} func (x *NextExerciseResponse_Exercise) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[33] + mi := &file_server_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2090,7 +2201,7 @@ type ExerciseSolution_Module struct { func (x *ExerciseSolution_Module) Reset() { *x = ExerciseSolution_Module{} - mi := &file_server_proto_msgTypes[34] + mi := &file_server_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2102,7 +2213,7 @@ func (x *ExerciseSolution_Module) String() string { func (*ExerciseSolution_Module) ProtoMessage() {} func (x *ExerciseSolution_Module) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[34] + mi := &file_server_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2143,7 +2254,7 @@ type ExerciseSolution_Exercise struct { func (x *ExerciseSolution_Exercise) Reset() { *x = ExerciseSolution_Exercise{} - mi := &file_server_proto_msgTypes[35] + mi := &file_server_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2155,7 +2266,7 @@ func (x *ExerciseSolution_Exercise) String() string { func (*ExerciseSolution_Exercise) ProtoMessage() {} func (x *ExerciseSolution_Exercise) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[35] + mi := &file_server_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2203,7 +2314,7 @@ type GetExercisesResponse_Module struct { func (x *GetExercisesResponse_Module) Reset() { *x = GetExercisesResponse_Module{} - mi := &file_server_proto_msgTypes[37] + mi := &file_server_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2215,7 +2326,7 @@ func (x *GetExercisesResponse_Module) String() string { func (*GetExercisesResponse_Module) ProtoMessage() {} func (x *GetExercisesResponse_Module) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[37] + mi := &file_server_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2263,7 +2374,7 @@ type GetExercisesResponse_Exercise struct { func (x *GetExercisesResponse_Exercise) Reset() { *x = GetExercisesResponse_Exercise{} - mi := &file_server_proto_msgTypes[38] + mi := &file_server_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2275,7 +2386,7 @@ func (x *GetExercisesResponse_Exercise) String() string { func (*GetExercisesResponse_Exercise) ProtoMessage() {} func (x *GetExercisesResponse_Exercise) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[38] + mi := &file_server_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2323,7 +2434,7 @@ type GetSolutionsResponse_Solution struct { func (x *GetSolutionsResponse_Solution) Reset() { *x = GetSolutionsResponse_Solution{} - mi := &file_server_proto_msgTypes[39] + mi := &file_server_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2335,7 +2446,7 @@ func (x *GetSolutionsResponse_Solution) String() string { func (*GetSolutionsResponse_Solution) ProtoMessage() {} func (x *GetSolutionsResponse_Solution) ProtoReflect() protoreflect.Message { - mi := &file_server_proto_msgTypes[39] + mi := &file_server_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2659,90 +2770,108 @@ var file_server_proto_rawDesc = string([]byte{ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x69, 0x72, 0x12, 0x1b, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, - 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, - 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, - 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x22, 0x4d, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, - 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2a, - 0x41, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x66, 0x66, 0x69, - 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x41, 0x53, 0x59, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, - 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x41, 0x52, 0x44, - 0x10, 0x03, 0x32, 0xfb, 0x07, 0x0a, 0x09, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x73, - 0x12, 0x25, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x0c, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x72, - 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x15, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x15, 0x2e, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x4e, 0x65, - 0x78, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x14, 0x2e, 0x4e, 0x65, 0x78, - 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x15, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0e, 0x56, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x16, 0x2e, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x78, 0x65, 0x72, - 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, - 0x12, 0x3d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x14, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x49, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, - 0x6c, 0x65, 0x73, 0x12, 0x18, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x13, 0x47, 0x65, - 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, - 0x73, 0x12, 0x1b, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, - 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, - 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x73, 0x12, 0x14, - 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, - 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, - 0x0b, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x13, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x15, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x43, 0x61, - 0x6e, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x17, 0x2e, - 0x43, 0x61, 0x6e, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x43, 0x61, 0x6e, 0x53, 0x6b, 0x69, 0x70, - 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, - 0x73, 0x65, 0x12, 0x14, 0x2e, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x53, 0x6b, 0x69, 0x70, 0x45, - 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x4c, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x53, 0x6f, - 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6c, 0x64, - 0x65, 0x6e, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x53, 0x6f, 0x6c, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x55, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, - 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, - 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, - 0x68, 0x72, 0x65, 0x65, 0x44, 0x6f, 0x74, 0x73, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x63, 0x6c, 0x69, - 0x2f, 0x74, 0x64, 0x6c, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, - 0x67, 0x73, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x7a, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, + 0x72, 0x63, 0x69, 0x73, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x65, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x4e, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, + 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x64, 0x69, 0x72, 0x12, 0x1b, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x22, 0x58, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x4d, 0x0a, 0x1c, + 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x12, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, + 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x41, 0x0a, 0x12, 0x54, + 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, + 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x45, 0x41, 0x53, 0x59, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, + 0x41, 0x4c, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x41, 0x52, 0x44, 0x10, 0x03, 0x32, 0xd5, + 0x08, 0x0a, 0x09, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x25, 0x0a, 0x04, + 0x49, 0x6e, 0x69, 0x74, 0x12, 0x0c, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x72, 0x61, + 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x15, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x72, 0x61, + 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x54, 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, + 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x14, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, 0x65, + 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x4e, + 0x65, 0x78, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, + 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x16, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3d, 0x0a, 0x0c, + 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x2e, 0x47, + 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x10, 0x47, + 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, + 0x18, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, + 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1b, 0x2e, + 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x47, 0x65, 0x74, + 0x41, 0x6c, 0x6c, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x73, 0x12, 0x14, 0x2e, 0x47, 0x65, 0x74, + 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0b, 0x47, 0x65, 0x74, + 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, + 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, + 0x4e, 0x65, 0x78, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x43, 0x61, 0x6e, 0x53, 0x6b, 0x69, + 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x17, 0x2e, 0x43, 0x61, 0x6e, 0x53, + 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x43, 0x61, 0x6e, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, + 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, + 0x0a, 0x0c, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x12, 0x14, + 0x2e, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x53, 0x6b, 0x69, 0x70, 0x45, 0x78, 0x65, 0x72, 0x63, + 0x69, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, + 0x11, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x53, 0x6f, + 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x47, 0x65, 0x74, 0x47, 0x6f, 0x6c, 0x64, 0x65, 0x6e, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x15, 0x47, + 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, + 0x73, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, + 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x2e, + 0x47, 0x65, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x47, 0x65, + 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x04, + 0x50, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x72, 0x65, 0x65, 0x44, 0x6f, 0x74, 0x73, 0x4c, 0x61, + 0x62, 0x73, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x74, 0x64, 0x6c, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -2758,7 +2887,7 @@ func file_server_proto_rawDescGZIP() []byte { } var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 40) +var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 42) var file_server_proto_goTypes = []any{ (TrainingDifficulty)(0), // 0: TrainingDifficulty (NextExerciseResponse_TrainingStatus)(0), // 1: NextExerciseResponse.TrainingStatus @@ -2792,80 +2921,85 @@ var file_server_proto_goTypes = []any{ (*GetSolutionsResponse)(nil), // 29: GetSolutionsResponse (*GetGoldenSolutionRequest)(nil), // 30: GetGoldenSolutionRequest (*GetGoldenSolutionResponse)(nil), // 31: GetGoldenSolutionResponse - (*GetAgentInstructionsRequest)(nil), // 32: GetAgentInstructionsRequest - (*GetAgentInstructionsResponse)(nil), // 33: GetAgentInstructionsResponse - (*NextExerciseResponse_Module)(nil), // 34: NextExerciseResponse.Module - (*NextExerciseResponse_Exercise)(nil), // 35: NextExerciseResponse.Exercise - (*ExerciseSolution_Module)(nil), // 36: ExerciseSolution.Module - (*ExerciseSolution_Exercise)(nil), // 37: ExerciseSolution.Exercise - nil, // 38: VerifyExerciseResponse.MetadataEntry - (*GetExercisesResponse_Module)(nil), // 39: GetExercisesResponse.Module - (*GetExercisesResponse_Exercise)(nil), // 40: GetExercisesResponse.Exercise - (*GetSolutionsResponse_Solution)(nil), // 41: GetSolutionsResponse.Solution - (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 43: google.protobuf.Empty + (*GetExerciseStartStateRequest)(nil), // 32: GetExerciseStartStateRequest + (*GetExerciseStartStateResponse)(nil), // 33: GetExerciseStartStateResponse + (*GetAgentInstructionsRequest)(nil), // 34: GetAgentInstructionsRequest + (*GetAgentInstructionsResponse)(nil), // 35: GetAgentInstructionsResponse + (*NextExerciseResponse_Module)(nil), // 36: NextExerciseResponse.Module + (*NextExerciseResponse_Exercise)(nil), // 37: NextExerciseResponse.Exercise + (*ExerciseSolution_Module)(nil), // 38: ExerciseSolution.Module + (*ExerciseSolution_Exercise)(nil), // 39: ExerciseSolution.Exercise + nil, // 40: VerifyExerciseResponse.MetadataEntry + (*GetExercisesResponse_Module)(nil), // 41: GetExercisesResponse.Module + (*GetExercisesResponse_Exercise)(nil), // 42: GetExercisesResponse.Exercise + (*GetSolutionsResponse_Solution)(nil), // 43: GetSolutionsResponse.Solution + (*timestamppb.Timestamp)(nil), // 44: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 45: google.protobuf.Empty } var file_server_proto_depIdxs = []int32{ 4, // 0: GetTrainingsResponse.trainings:type_name -> Training 1, // 1: NextExerciseResponse.training_status:type_name -> NextExerciseResponse.TrainingStatus 13, // 2: NextExerciseResponse.files_to_create:type_name -> File - 35, // 3: NextExerciseResponse.exercise:type_name -> NextExerciseResponse.Exercise + 37, // 3: NextExerciseResponse.exercise:type_name -> NextExerciseResponse.Exercise 0, // 4: NextExerciseResponse.training_difficulty:type_name -> TrainingDifficulty - 42, // 5: NextExerciseResponse.next_batch_date:type_name -> google.protobuf.Timestamp + 44, // 5: NextExerciseResponse.next_batch_date:type_name -> google.protobuf.Timestamp 13, // 6: ExerciseSolution.files:type_name -> File - 36, // 7: ExerciseSolution.module:type_name -> ExerciseSolution.Module - 37, // 8: ExerciseSolution.exercise:type_name -> ExerciseSolution.Exercise - 42, // 9: ExerciseSolution.completed_at:type_name -> google.protobuf.Timestamp - 42, // 10: ExerciseSolution.submitted_at:type_name -> google.protobuf.Timestamp + 38, // 7: ExerciseSolution.module:type_name -> ExerciseSolution.Module + 39, // 8: ExerciseSolution.exercise:type_name -> ExerciseSolution.Exercise + 44, // 9: ExerciseSolution.completed_at:type_name -> google.protobuf.Timestamp + 44, // 10: ExerciseSolution.submitted_at:type_name -> google.protobuf.Timestamp 13, // 11: NextExercise.files_to_create:type_name -> File 13, // 12: VerifyExerciseRequest.files:type_name -> File - 38, // 13: VerifyExerciseResponse.metadata:type_name -> VerifyExerciseResponse.MetadataEntry + 40, // 13: VerifyExerciseResponse.metadata:type_name -> VerifyExerciseResponse.MetadataEntry 15, // 14: VerifyExerciseResponse.suite_result:type_name -> SuiteResult 16, // 15: SuiteResult.scenarios:type_name -> ScenarioResult 13, // 16: GetSolutionFilesResponse.files_to_create:type_name -> File 10, // 17: GetAllSolutionFilesResponse.solutions:type_name -> ExerciseSolution - 39, // 18: GetExercisesResponse.modules:type_name -> GetExercisesResponse.Module - 41, // 19: GetSolutionsResponse.solutions:type_name -> GetSolutionsResponse.Solution + 41, // 18: GetExercisesResponse.modules:type_name -> GetExercisesResponse.Module + 43, // 19: GetSolutionsResponse.solutions:type_name -> GetSolutionsResponse.Solution 13, // 20: GetGoldenSolutionResponse.files:type_name -> File - 34, // 21: NextExerciseResponse.Exercise.module:type_name -> NextExerciseResponse.Module - 36, // 22: ExerciseSolution.Exercise.module:type_name -> ExerciseSolution.Module - 40, // 23: GetExercisesResponse.Module.exercises:type_name -> GetExercisesResponse.Exercise - 42, // 24: GetSolutionsResponse.Solution.executed_at:type_name -> google.protobuf.Timestamp - 2, // 25: Trainings.Init:input_type -> InitRequest - 43, // 26: Trainings.GetTrainings:input_type -> google.protobuf.Empty - 6, // 27: Trainings.StartTraining:input_type -> StartTrainingRequest - 8, // 28: Trainings.NextExercise:input_type -> NextExerciseRequest - 12, // 29: Trainings.VerifyExercise:input_type -> VerifyExerciseRequest - 28, // 30: Trainings.GetSolutions:input_type -> GetSolutionsRequest - 17, // 31: Trainings.GetSolutionFiles:input_type -> GetSolutionFilesRequest - 19, // 32: Trainings.GetAllSolutionFiles:input_type -> GetAllSolutionFilesRequest - 21, // 33: Trainings.GetExercises:input_type -> GetExercisesRequest - 23, // 34: Trainings.GetExercise:input_type -> GetExerciseRequest - 26, // 35: Trainings.CanSkipExercise:input_type -> CanSkipExerciseRequest - 24, // 36: Trainings.SkipExercise:input_type -> SkipExerciseRequest - 30, // 37: Trainings.GetGoldenSolution:input_type -> GetGoldenSolutionRequest - 32, // 38: Trainings.GetAgentInstructions:input_type -> GetAgentInstructionsRequest - 43, // 39: Trainings.Ping:input_type -> google.protobuf.Empty - 3, // 40: Trainings.Init:output_type -> InitResponse - 5, // 41: Trainings.GetTrainings:output_type -> GetTrainingsResponse - 7, // 42: Trainings.StartTraining:output_type -> StartTrainingResponse - 9, // 43: Trainings.NextExercise:output_type -> NextExerciseResponse - 14, // 44: Trainings.VerifyExercise:output_type -> VerifyExerciseResponse - 29, // 45: Trainings.GetSolutions:output_type -> GetSolutionsResponse - 18, // 46: Trainings.GetSolutionFiles:output_type -> GetSolutionFilesResponse - 20, // 47: Trainings.GetAllSolutionFiles:output_type -> GetAllSolutionFilesResponse - 22, // 48: Trainings.GetExercises:output_type -> GetExercisesResponse - 9, // 49: Trainings.GetExercise:output_type -> NextExerciseResponse - 27, // 50: Trainings.CanSkipExercise:output_type -> CanSkipExerciseResponse - 25, // 51: Trainings.SkipExercise:output_type -> SkipExerciseResponse - 31, // 52: Trainings.GetGoldenSolution:output_type -> GetGoldenSolutionResponse - 33, // 53: Trainings.GetAgentInstructions:output_type -> GetAgentInstructionsResponse - 43, // 54: Trainings.Ping:output_type -> google.protobuf.Empty - 40, // [40:55] is the sub-list for method output_type - 25, // [25:40] is the sub-list for method input_type - 25, // [25:25] is the sub-list for extension type_name - 25, // [25:25] is the sub-list for extension extendee - 0, // [0:25] is the sub-list for field type_name + 13, // 21: GetExerciseStartStateResponse.files:type_name -> File + 36, // 22: NextExerciseResponse.Exercise.module:type_name -> NextExerciseResponse.Module + 38, // 23: ExerciseSolution.Exercise.module:type_name -> ExerciseSolution.Module + 42, // 24: GetExercisesResponse.Module.exercises:type_name -> GetExercisesResponse.Exercise + 44, // 25: GetSolutionsResponse.Solution.executed_at:type_name -> google.protobuf.Timestamp + 2, // 26: Trainings.Init:input_type -> InitRequest + 45, // 27: Trainings.GetTrainings:input_type -> google.protobuf.Empty + 6, // 28: Trainings.StartTraining:input_type -> StartTrainingRequest + 8, // 29: Trainings.NextExercise:input_type -> NextExerciseRequest + 12, // 30: Trainings.VerifyExercise:input_type -> VerifyExerciseRequest + 28, // 31: Trainings.GetSolutions:input_type -> GetSolutionsRequest + 17, // 32: Trainings.GetSolutionFiles:input_type -> GetSolutionFilesRequest + 19, // 33: Trainings.GetAllSolutionFiles:input_type -> GetAllSolutionFilesRequest + 21, // 34: Trainings.GetExercises:input_type -> GetExercisesRequest + 23, // 35: Trainings.GetExercise:input_type -> GetExerciseRequest + 26, // 36: Trainings.CanSkipExercise:input_type -> CanSkipExerciseRequest + 24, // 37: Trainings.SkipExercise:input_type -> SkipExerciseRequest + 30, // 38: Trainings.GetGoldenSolution:input_type -> GetGoldenSolutionRequest + 32, // 39: Trainings.GetExerciseStartState:input_type -> GetExerciseStartStateRequest + 34, // 40: Trainings.GetAgentInstructions:input_type -> GetAgentInstructionsRequest + 45, // 41: Trainings.Ping:input_type -> google.protobuf.Empty + 3, // 42: Trainings.Init:output_type -> InitResponse + 5, // 43: Trainings.GetTrainings:output_type -> GetTrainingsResponse + 7, // 44: Trainings.StartTraining:output_type -> StartTrainingResponse + 9, // 45: Trainings.NextExercise:output_type -> NextExerciseResponse + 14, // 46: Trainings.VerifyExercise:output_type -> VerifyExerciseResponse + 29, // 47: Trainings.GetSolutions:output_type -> GetSolutionsResponse + 18, // 48: Trainings.GetSolutionFiles:output_type -> GetSolutionFilesResponse + 20, // 49: Trainings.GetAllSolutionFiles:output_type -> GetAllSolutionFilesResponse + 22, // 50: Trainings.GetExercises:output_type -> GetExercisesResponse + 9, // 51: Trainings.GetExercise:output_type -> NextExerciseResponse + 27, // 52: Trainings.CanSkipExercise:output_type -> CanSkipExerciseResponse + 25, // 53: Trainings.SkipExercise:output_type -> SkipExerciseResponse + 31, // 54: Trainings.GetGoldenSolution:output_type -> GetGoldenSolutionResponse + 33, // 55: Trainings.GetExerciseStartState:output_type -> GetExerciseStartStateResponse + 35, // 56: Trainings.GetAgentInstructions:output_type -> GetAgentInstructionsResponse + 45, // 57: Trainings.Ping:output_type -> google.protobuf.Empty + 42, // [42:58] is the sub-list for method output_type + 26, // [26:42] is the sub-list for method input_type + 26, // [26:26] is the sub-list for extension type_name + 26, // [26:26] is the sub-list for extension extendee + 0, // [0:26] is the sub-list for field type_name } func init() { file_server_proto_init() } @@ -2879,7 +3013,7 @@ func file_server_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_server_proto_rawDesc), len(file_server_proto_rawDesc)), NumEnums: 2, - NumMessages: 40, + NumMessages: 42, NumExtensions: 0, NumServices: 1, }, diff --git a/trainings/genproto/server_grpc.pb.go b/trainings/genproto/server_grpc.pb.go index c62c65e..5b66995 100644 --- a/trainings/genproto/server_grpc.pb.go +++ b/trainings/genproto/server_grpc.pb.go @@ -8,7 +8,6 @@ package genproto import ( context "context" - grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -21,21 +20,22 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - Trainings_Init_FullMethodName = "/Trainings/Init" - Trainings_GetTrainings_FullMethodName = "/Trainings/GetTrainings" - Trainings_StartTraining_FullMethodName = "/Trainings/StartTraining" - Trainings_NextExercise_FullMethodName = "/Trainings/NextExercise" - Trainings_VerifyExercise_FullMethodName = "/Trainings/VerifyExercise" - Trainings_GetSolutions_FullMethodName = "/Trainings/GetSolutions" - Trainings_GetSolutionFiles_FullMethodName = "/Trainings/GetSolutionFiles" - Trainings_GetAllSolutionFiles_FullMethodName = "/Trainings/GetAllSolutionFiles" - Trainings_GetExercises_FullMethodName = "/Trainings/GetExercises" - Trainings_GetExercise_FullMethodName = "/Trainings/GetExercise" - Trainings_CanSkipExercise_FullMethodName = "/Trainings/CanSkipExercise" - Trainings_SkipExercise_FullMethodName = "/Trainings/SkipExercise" - Trainings_GetGoldenSolution_FullMethodName = "/Trainings/GetGoldenSolution" - Trainings_GetAgentInstructions_FullMethodName = "/Trainings/GetAgentInstructions" - Trainings_Ping_FullMethodName = "/Trainings/Ping" + Trainings_Init_FullMethodName = "/Trainings/Init" + Trainings_GetTrainings_FullMethodName = "/Trainings/GetTrainings" + Trainings_StartTraining_FullMethodName = "/Trainings/StartTraining" + Trainings_NextExercise_FullMethodName = "/Trainings/NextExercise" + Trainings_VerifyExercise_FullMethodName = "/Trainings/VerifyExercise" + Trainings_GetSolutions_FullMethodName = "/Trainings/GetSolutions" + Trainings_GetSolutionFiles_FullMethodName = "/Trainings/GetSolutionFiles" + Trainings_GetAllSolutionFiles_FullMethodName = "/Trainings/GetAllSolutionFiles" + Trainings_GetExercises_FullMethodName = "/Trainings/GetExercises" + Trainings_GetExercise_FullMethodName = "/Trainings/GetExercise" + Trainings_CanSkipExercise_FullMethodName = "/Trainings/CanSkipExercise" + Trainings_SkipExercise_FullMethodName = "/Trainings/SkipExercise" + Trainings_GetGoldenSolution_FullMethodName = "/Trainings/GetGoldenSolution" + Trainings_GetExerciseStartState_FullMethodName = "/Trainings/GetExerciseStartState" + Trainings_GetAgentInstructions_FullMethodName = "/Trainings/GetAgentInstructions" + Trainings_Ping_FullMethodName = "/Trainings/Ping" ) // TrainingsClient is the client API for Trainings service. @@ -55,6 +55,7 @@ type TrainingsClient interface { CanSkipExercise(ctx context.Context, in *CanSkipExerciseRequest, opts ...grpc.CallOption) (*CanSkipExerciseResponse, error) SkipExercise(ctx context.Context, in *SkipExerciseRequest, opts ...grpc.CallOption) (*SkipExerciseResponse, error) GetGoldenSolution(ctx context.Context, in *GetGoldenSolutionRequest, opts ...grpc.CallOption) (*GetGoldenSolutionResponse, error) + GetExerciseStartState(ctx context.Context, in *GetExerciseStartStateRequest, opts ...grpc.CallOption) (*GetExerciseStartStateResponse, error) GetAgentInstructions(ctx context.Context, in *GetAgentInstructionsRequest, opts ...grpc.CallOption) (*GetAgentInstructionsResponse, error) Ping(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) } @@ -206,6 +207,16 @@ func (c *trainingsClient) GetGoldenSolution(ctx context.Context, in *GetGoldenSo return out, nil } +func (c *trainingsClient) GetExerciseStartState(ctx context.Context, in *GetExerciseStartStateRequest, opts ...grpc.CallOption) (*GetExerciseStartStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetExerciseStartStateResponse) + err := c.cc.Invoke(ctx, Trainings_GetExerciseStartState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *trainingsClient) GetAgentInstructions(ctx context.Context, in *GetAgentInstructionsRequest, opts ...grpc.CallOption) (*GetAgentInstructionsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetAgentInstructionsResponse) @@ -243,6 +254,7 @@ type TrainingsServer interface { CanSkipExercise(context.Context, *CanSkipExerciseRequest) (*CanSkipExerciseResponse, error) SkipExercise(context.Context, *SkipExerciseRequest) (*SkipExerciseResponse, error) GetGoldenSolution(context.Context, *GetGoldenSolutionRequest) (*GetGoldenSolutionResponse, error) + GetExerciseStartState(context.Context, *GetExerciseStartStateRequest) (*GetExerciseStartStateResponse, error) GetAgentInstructions(context.Context, *GetAgentInstructionsRequest) (*GetAgentInstructionsResponse, error) Ping(context.Context, *emptypb.Empty) (*emptypb.Empty, error) } @@ -293,6 +305,9 @@ func (UnimplementedTrainingsServer) SkipExercise(context.Context, *SkipExerciseR func (UnimplementedTrainingsServer) GetGoldenSolution(context.Context, *GetGoldenSolutionRequest) (*GetGoldenSolutionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetGoldenSolution not implemented") } +func (UnimplementedTrainingsServer) GetExerciseStartState(context.Context, *GetExerciseStartStateRequest) (*GetExerciseStartStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExerciseStartState not implemented") +} func (UnimplementedTrainingsServer) GetAgentInstructions(context.Context, *GetAgentInstructionsRequest) (*GetAgentInstructionsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAgentInstructions not implemented") } @@ -546,6 +561,24 @@ func _Trainings_GetGoldenSolution_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Trainings_GetExerciseStartState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetExerciseStartStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TrainingsServer).GetExerciseStartState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Trainings_GetExerciseStartState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TrainingsServer).GetExerciseStartState(ctx, req.(*GetExerciseStartStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Trainings_GetAgentInstructions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetAgentInstructionsRequest) if err := dec(in); err != nil { @@ -637,6 +670,10 @@ var Trainings_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetGoldenSolution", Handler: _Trainings_GetGoldenSolution_Handler, }, + { + MethodName: "GetExerciseStartState", + Handler: _Trainings_GetExerciseStartState_Handler, + }, { MethodName: "GetAgentInstructions", Handler: _Trainings_GetAgentInstructions_Handler, diff --git a/trainings/next.go b/trainings/next.go index add1add..94bd1aa 100644 --- a/trainings/next.go +++ b/trainings/next.go @@ -136,16 +136,16 @@ func (h *Handlers) setExercise(ctx context.Context, fs *afero.BasePathFs, exerci if writeFiles { gitOps := h.newGitOps() if gitOps.Enabled() && !exercise.IsTextOnly { - // Read current (soon-to-be-previous) exercise config for init chain - // and for fetching "golden of prev" if 'g' is chosen on conflict. - var prevModuleExercisePath, prevExerciseID string + // Read current (soon-to-be-previous) exercise config for init chain continuity. + // "golden of prev" for the 'g' merge-conflict path is now resolved server-side + // via GetExerciseStartState(currentExerciseID), so we no longer thread it here. + var prevModuleExercisePath string if files.DirOrFileExists(fs, ".tdl-exercise") { prevCfg := h.config.ExerciseConfig(fs) prevModuleExercisePath = prevCfg.ModuleExercisePath() - prevExerciseID = prevCfg.ExerciseID } - if err := h.setExerciseWithGit(ctx, gitOps, fs, exercise, trainingRoot, prevModuleExercisePath, prevExerciseID); err != nil { + if err := h.setExerciseWithGit(ctx, gitOps, fs, exercise, trainingRoot, prevModuleExercisePath); err != nil { return false, fmt.Errorf("git flow failed: %w", err) } // Git handled file writing; just write the exercise config @@ -267,7 +267,6 @@ func (h *Handlers) setExerciseWithGit( exercise *genproto.NextExerciseResponse, trainingRoot string, prevModuleExercisePath string, - prevExerciseID string, ) error { exerciseDir := exercise.Dir moduleExercisePath := moduleExercisePathFromResponse(exercise) @@ -367,11 +366,11 @@ func (h *Handlers) setExerciseWithGit( if conflictPrompt == 'g' && len(previewedConflictFiles) > 0 { // User chose "replace all exercise files". INVARIANT: after replace, // exerciseDir must be 1:1 with the exercise's start state - // (golden(prev) + scaffold(current)) — no stale user files. Enforced - // by replaceExerciseFilesAndCommit → replaceExerciseFiles. + // (golden(prev) merged with scaffold(current)) — no stale user files. + // Start state comes from server's GetExerciseStartState. if err := h.replaceExerciseFilesOnMergeConflict( ctx, gitOps, fs, - prevExerciseID, exercise.FilesToCreate, + exercise.ExerciseId, exerciseDir, previewBackupBranch, mergeMsg, trainingName, ); err != nil { return err @@ -381,7 +380,7 @@ func (h *Handlers) setExerciseWithGit( if err := h.resolveConflictsInteractive( ctx, gitOps, fs, initBranch, mergeMsg, moduleExercisePath, exerciseDir, trainingName, - prevExerciseID, exercise.FilesToCreate, + exercise.ExerciseId, ); err != nil { return err } @@ -616,8 +615,7 @@ func (h *Handlers) resolveConflictsInteractive( gitOps *git.Ops, fs *afero.BasePathFs, initBranch, mergeMsg, moduleExercisePath, exerciseDir, trainingName string, - prevExerciseID string, - scaffoldFiles []*genproto.File, + currentExerciseID string, ) error { conflictFiles, _ := gitOps.UnmergedFiles() fmt.Println(color.YellowString("\n Merge conflict detected.")) @@ -671,7 +669,7 @@ func (h *Handlers) resolveConflictsInteractive( case 'g': return h.replaceExerciseFilesOnMergeConflict( ctx, gitOps, fs, - prevExerciseID, scaffoldFiles, + currentExerciseID, exerciseDir, backupBranch, mergeMsg, trainingName, ) @@ -685,43 +683,42 @@ func (h *Handlers) resolveConflictsInteractive( } // replaceExerciseFilesOnMergeConflict completes an in-progress merge by replacing -// exerciseDir with the start state (golden(prev) + scaffold(current)). +// exerciseDir with the start state (golden(prev) merged with scaffold(current)). // User's pre-merge state is saved to backupBranch. // // INVARIANT: on success, exerciseDir is 1:1 with the start state — no stale user // files. Routes through replaceExerciseFilesAndCommit → replaceExerciseFiles; // see exercise_replace.go for why this invariant is load-bearing. // +// Start state composition lives server-side in GetExerciseStartState. // If backup creation fails and the user aborts, the merge is aborted and // errMergeAborted is returned so callers can treat it as a clean cancellation. func (h *Handlers) replaceExerciseFilesOnMergeConflict( ctx context.Context, gitOps *git.Ops, fs *afero.BasePathFs, - prevExerciseID string, - scaffoldFiles []*genproto.File, + currentExerciseID string, exerciseDir, backupBranch, mergeMsg, trainingName string, ) error { - var goldenFiles []*genproto.File - if prevExerciseID != "" { - gf, err := h.fetchGoldenFiles( - ctx, - h.config.TrainingConfig(fs).TrainingName, - prevExerciseID, - h.config.GlobalConfig().Token, - ) - if err != nil { - // Abort the in-progress merge to leave a clean state. - _ = gitOps.MergeAbort() - fmt.Println(formatGitError("Could not fetch example solution for previous exercise", err, trainingName)) - return err - } - goldenFiles = gf + resp, err := h.newGrpcClient().GetExerciseStartState(ctx, &genproto.GetExerciseStartStateRequest{ + TrainingName: h.config.TrainingConfig(fs).TrainingName, + Token: h.config.GlobalConfig().Token, + ExerciseId: currentExerciseID, + }) + if err != nil { + // Abort the in-progress merge to leave a clean state. + _ = gitOps.MergeAbort() + fmt.Println(formatGitError("Could not fetch exercise start state", err, trainingName)) + return err + } + if len(resp.Files) == 0 { + _ = gitOps.MergeAbort() + fmt.Println(color.YellowString(" Server returned no files for exercise start state — aborting to protect your workspace.")) + fmt.Println(color.YellowString(" Please update your CLI or contact support if the problem persists.")) + return fmt.Errorf("empty exercise start state") } - startState := mergeStartStateFiles(goldenFiles, scaffoldFiles) - - _, err := replaceExerciseFilesAndCommit(gitOps, fs, startState, exerciseDir, backupBranch, mergeMsg) + _, err = replaceExerciseFilesAndCommit(gitOps, fs, resp.Files, exerciseDir, backupBranch, mergeMsg) if err != nil { if errors.Is(err, errBackupAborted) { _ = gitOps.MergeAbort() diff --git a/trainings/reset.go b/trainings/reset.go index 5ff8965..acb7a33 100644 --- a/trainings/reset.go +++ b/trainings/reset.go @@ -168,42 +168,32 @@ func (h *Handlers) resetCleanFiles( } // fetchStartStateFiles returns the starting state of the given exercise: -// golden(prev) with scaffold(current) overlaid on top. Makes up to 3 -// gRPC calls (GetExercises for prev resolution, GetGoldenSolution for prev golden, -// GetExercise for current scaffold). Pass "" for exerciseID only in edge cases -// where the caller knows the current exercise has no meaningful prev. +// golden(prev) merged with scaffold(current). One gRPC call — the server +// owns the composition via GetExerciseStartState, which uses +// TrainingConfig.PreviousExercises (same-dir filter) to pick the right +// predecessor. +// +// The empty-files guard is load-bearing: callers pipe the result into +// replaceExerciseFiles which enforces a 1:1 "delete unused" invariant. +// Zero files would mean "wipe everything" — always an anomaly, never a +// legitimate input. func (h *Handlers) fetchStartStateFiles( ctx context.Context, fs *afero.BasePathFs, exerciseID string, ) ([]*genproto.File, error) { - trainingName := h.config.TrainingConfig(fs).TrainingName - token := h.config.GlobalConfig().Token - - prevExerciseID, _, err := h.resolvePreviousExercise(ctx, trainingName, token, exerciseID) - if err != nil { - return nil, fmt.Errorf("could not resolve previous exercise: %w", err) - } - - scaffoldResp, err := h.newGrpcClient().GetExercise(ctx, &genproto.GetExerciseRequest{ - TrainingName: trainingName, - Token: token, + resp, err := h.newGrpcClient().GetExerciseStartState(ctx, &genproto.GetExerciseStartStateRequest{ + TrainingName: h.config.TrainingConfig(fs).TrainingName, + Token: h.config.GlobalConfig().Token, ExerciseId: exerciseID, }) if err != nil { - return nil, fmt.Errorf("could not fetch exercise scaffold: %w", err) + return nil, fmt.Errorf("could not fetch exercise start state: %w", err) } - - var goldenFiles []*genproto.File - if prevExerciseID != "" { - gf, err := h.fetchGoldenFiles(ctx, trainingName, prevExerciseID, token) - if err != nil { - return nil, err - } - goldenFiles = gf + if len(resp.Files) == 0 { + return nil, fmt.Errorf("server returned no files for exercise start state — aborting to protect your workspace (please update your CLI or contact support)") } - - return mergeStartStateFiles(goldenFiles, scaffoldResp.FilesToCreate), nil + return resp.Files, nil } // resetMissingOnly restores files from the exercise's start state