syntax = "proto3"; package kovanex.v1; option go_package = "kovanex/proto/gen;kovanex"; import "google/protobuf/timestamp.proto"; // ═══════════════════════════════════════════════════════════════════════ // Auth Service — регистрация, логин, OIDC, ключи // ═══════════════════════════════════════════════════════════════════════ service AuthService { rpc Register (RegisterRequest) returns (AuthResponse); rpc Login (LoginRequest) returns (AuthResponse); rpc RefreshToken (RefreshTokenRequest) returns (AuthResponse); rpc Logout (LogoutRequest) returns (Empty); // SSH ключи для Git rpc AddSSHKey (AddSSHKeyRequest) returns (SSHKey); rpc ListSSHKeys (Empty) returns (SSHKeyList); rpc DeleteSSHKey (DeleteKeyRequest) returns (Empty); // Password reset (admin) rpc ResetPassword (ResetPasswordRequest) returns (ResetPasswordResponse); rpc ChangePassword (ChangePasswordRequest) returns (Empty); // mTLS ключи (Trust on First Use) rpc TrustMTLSKey (TrustMTLSKeyRequest) returns (MTLSKey); rpc ListMTLSKeys (Empty) returns (MTLSKeyList); rpc RevokeMTLSKey(DeleteKeyRequest) returns (Empty); // OIDC discovery (для Proxmox, Grafana и т.д.) rpc GetOIDCConfig(Empty) returns (OIDCConfig); } message RegisterRequest { string username = 1; string email = 2; string password = 3; } message LoginRequest { string username = 1; string password = 2; } message RefreshTokenRequest { string refresh_token = 1; } message LogoutRequest { string token = 1; } message ResetPasswordRequest { string user_id = 1; // admin resets for this user } message ResetPasswordResponse { string temporary_password = 1; // generated, must change on next login } message ChangePasswordRequest { string old_password = 1; string new_password = 2; } message AuthResponse { string access_token = 1; string refresh_token = 2; int64 expires_in = 3; // секунды User user = 4; } message User { string id = 1; string username = 2; string email = 3; repeated string roles = 4; google.protobuf.Timestamp created_at = 5; google.protobuf.Timestamp last_login = 6; int32 numeric_id = 7; // stable numeric ID for cross-system linking } message SSHKey { string id = 1; string name = 2; string fingerprint = 3; google.protobuf.Timestamp created_at = 4; } message SSHKeyList { repeated SSHKey keys = 1; } message AddSSHKeyRequest { string name = 1; string public_key = 2; } message MTLSKey { string id = 1; string name = 2; string fingerprint = 3; string type = 4; // "runner" | "user" | "ai" bool trusted = 5; google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp last_seen = 7; } message MTLSKeyList { repeated MTLSKey keys = 1; } message TrustMTLSKeyRequest { string name = 1; string fingerprint = 2; string type = 3; } message DeleteKeyRequest { string id = 1; } message OIDCConfig { string issuer = 1; string authorization_endpoint = 2; string token_endpoint = 3; string jwks_uri = 4; repeated string scopes_supported = 5; } // ═══════════════════════════════════════════════════════════════════════ // Runner Service — P2P gRPC стрим раннеров // ═══════════════════════════════════════════════════════════════════════ service RunnerService { // Двунаправленный стрим — раннер подключается и держит соединение rpc JoinPool(stream RunnerSignal) returns (stream ServerInstruction); // PSK registration: runner sends one-time token, gets long-lived PSK rpc RegisterRunner (RegisterRunnerRequest) returns (RegisterRunnerResponse); // Управление раннерами rpc ListRunners (Empty) returns (RunnerList); rpc GetRunner (GetRunnerRequest) returns (RunnerInfo); rpc RemoveRunner (GetRunnerRequest) returns (Empty); rpc TriggerEnvSync (EnvSyncRequest) returns (EnvSyncResponse); // Admin: runner key management rpc CreateRunnerToken (CreateRunnerTokenRequest) returns (CreateRunnerTokenResponse); rpc ListRunnerKeys (Empty) returns (RunnerKeyList); rpc RevokeRunnerKey (RevokeRunnerKeyRequest) returns (Empty); } message EnvSyncRequest { string runner_id = 1; // target runner (empty = any online) string secret_path = 2; // vault path, default: "env/kovanex/production" string target_file = 3; // target .env, default: "/opt/kovanex-server/.env" bool restart = 4; // restart after write } message EnvSyncResponse { bool dispatched = 1; string runner_id = 2; string error = 3; } message RunnerSignal { string runner_id = 1; string token = 2; // runner API key oneof payload { RunnerConnect connect = 3; RunnerTaskUpdate update = 4; bytes log_chunk = 5; // сырые логи StepResult step_done = 6; JobResult job_done = 7; HealthInfo health = 8; } } message RunnerConnect { string name = 1; string version = 2; repeated string labels = 3; // "linux", "docker", "gpu" string arch = 4; string os = 5; int32 cpu = 6; int64 mem_mb = 7; } message RunnerTaskUpdate { string job_id = 1; int32 step_idx = 2; string log = 3; } message StepResult { string job_id = 1; int32 step_idx = 2; int32 exit_code = 3; string output = 4; } message JobResult { string job_id = 1; int32 exit_code = 2; string error = 3; } message HealthInfo { float cpu_usage = 1; float mem_usage = 2; int32 jobs_count = 3; } message ServerInstruction { string instruction_id = 1; oneof action { BuildJob build_job = 2; StopJob stop_job = 3; Ping ping = 4; EnvSync env_sync = 5; } } // EnvSync — инструкция раннеру: забрать секреты из Vault и обновить .env message EnvSync { string vault_addr = 1; // e.g. "https://10.0.0.30:8080" string vault_token = 2; // ci-read token string secret_path = 3; // e.g. "env/kovanex/production" string target_file = 4; // e.g. "/opt/kovanex-server/.env" bool restart = 5; // restart service after write string service = 6; // docker compose service name, e.g. "kovanex" } message BuildJob { string job_id = 1; string repo_url = 2; string commit = 3; repeated StepInstruction steps = 4; map env = 5; string workspace = 6; } message StepInstruction { string name = 1; string command = 2; map env = 3; bool fire_and_forget = 4; // mark step success before executing (for self-deploy) } message StopJob { string job_id = 1; } message Ping { int64 timestamp = 1; } message RunnerList { repeated RunnerInfo runners = 1; } message RunnerInfo { string id = 1; string name = 2; string status = 3; // "online" | "busy" | "offline" string current_job = 4; repeated string labels = 5; google.protobuf.Timestamp connected_at = 6; google.protobuf.Timestamp last_ping = 7; HealthInfo health = 8; } message GetRunnerRequest { string id = 1; } // PSK Registration: one-time token → long-lived pre-shared key message RegisterRunnerRequest { string registration_token = 1; // one-time token from admin string name = 2; // runner display name repeated string labels = 3; // runner labels } message RegisterRunnerResponse { string runner_id = 1; // assigned runner UUID string psk = 2; // long-lived pre-shared key (store securely) } message CreateRunnerTokenRequest { string runner_name = 1; // intended runner name (informational) string ttl = 2; // e.g. "24h", "72h" (default: 24h) } message CreateRunnerTokenResponse { string token = 1; // one-time registration token string expires_at = 2; // human-readable expiry } message RunnerKeyInfo { string id = 1; string runner_id = 2; string runner_name = 3; string fingerprint = 4; // SHA256 of PSK (for identification) string status = 5; // "active" | "revoked" google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp last_auth_at = 7; } message RunnerKeyList { repeated RunnerKeyInfo keys = 1; } message RevokeRunnerKeyRequest { string runner_id = 1; } // ═══════════════════════════════════════════════════════════════════════ // Git Service — репозитории, коммиты, ветки, PR // ═══════════════════════════════════════════════════════════════════════ service GitService { // Репозитории rpc CreateRepo (CreateRepoRequest) returns (Repo); rpc GetRepo (RepoRequest) returns (Repo); rpc ListRepos (ListReposRequest) returns (RepoList); rpc DeleteRepo (RepoRequest) returns (Empty); // Ветки и коммиты rpc ListBranches (RepoRequest) returns (BranchList); rpc ListCommits (ListCommitsRequest) returns (CommitList); rpc GetCommit (GetCommitRequest) returns (Commit); rpc GetDiff (DiffRequest) returns (Diff); // Файлы rpc GetTree (TreeRequest) returns (TreeResponse); rpc GetFileContent (FileRequest) returns (FileResponse); // Pull Requests rpc CreatePR (CreatePRRequest) returns (PullRequest); rpc GetPR (PRRequest) returns (PullRequest); rpc ListPRs (ListPRsRequest) returns (PRList); rpc MergePR (MergePRRequest) returns (PullRequest); rpc ClosePR (PRRequest) returns (PullRequest); // PR Review Comments rpc AddPRComment (AddPRCommentRequest) returns (PRComment); rpc ListPRComments (ListPRCommentsRequest) returns (PRCommentList); rpc DeletePRComment(DeletePRCommentRequest) returns (Empty); // Stream событий репозитория (push, PR, tags) rpc WatchRepo (RepoRequest) returns (stream RepoEvent); } message CreateRepoRequest { string name = 1; string description = 2; bool is_private = 3; string default_branch = 4; // default: "main" bool init_readme = 5; string namespace = 6; // org slug (optional, default = owner username) } message RepoRequest { string repo_id = 1; } message Repo { string id = 1; string name = 2; string description = 3; string owner_id = 4; bool is_private = 5; string default_branch = 6; string clone_url = 7; // ssh://forge:2222/owner/repo.git string grpc_url = 8; // grpc://forge:9000/owner/repo.git int64 size_kb = 9; int32 stars = 10; int32 forks = 11; google.protobuf.Timestamp created_at = 12; google.protobuf.Timestamp updated_at = 13; string namespace = 14; // org slug prefix (e.g. "my-company") } message RepoList { repeated Repo repos = 1; } message ListReposRequest { string owner_id = 1; int32 limit = 2; int32 offset = 3; } message ListCommitsRequest { string repo_id = 1; string branch = 2; int32 limit = 3; } message GetCommitRequest { string repo_id = 1; string sha = 2; } message DiffRequest { string repo_id = 1; string from = 2; // commit SHA или branch string to = 3; } message Commit { string sha = 1; string message = 2; string author = 3; string email = 4; repeated string parent_shas = 5; google.protobuf.Timestamp timestamp = 6; } message CommitList { repeated Commit commits = 1; } message BranchList { repeated string branches = 1; } message Diff { string unified = 1; repeated string files = 2; } message TreeRequest { string repo_id = 1; string ref = 2; // branch, tag, or commit SHA (default: HEAD) string path = 3; // subdirectory path (default: root) } message TreeEntry { string name = 1; string type = 2; // "blob" | "tree" string sha = 3; int64 size = 4; // bytes, only for blobs string path = 5; // full path from root } message TreeResponse { repeated TreeEntry entries = 1; } message FileRequest { string repo_id = 1; string ref = 2; // branch, tag, or commit SHA string path = 3; // file path } message FileResponse { string path = 1; bytes content = 2; int64 size = 3; bool binary = 4; } message CreatePRRequest { string repo_id = 1; string title = 2; string body = 3; string head = 4; // source branch string base = 5; // target branch (обычно main) } message PullRequest { string id = 1; int32 number = 2; string title = 3; string body = 4; string status = 5; // "open" | "merged" | "closed" string head = 6; string base = 7; string author_id = 8; string merge_sha = 9; google.protobuf.Timestamp created_at = 10; google.protobuf.Timestamp merged_at = 11; } message PRList { repeated PullRequest prs = 1; } message PRRequest { string repo_id = 1; string pr_id = 2; } message ListPRsRequest { string repo_id = 1; string status = 2; // "open" | "merged" | "all" } message MergePRRequest { string repo_id = 1; string pr_id = 2; string method = 3; // "merge" | "squash" | "rebase" string commit_msg = 4; } // PR review comments (inline + general) message AddPRCommentRequest { string pr_id = 1; string body = 2; string file = 3; // empty = general comment int32 line = 4; // 0 = general comment string severity = 5; // "info" | "warning" | "error" — for AI reviews } message PRComment { string id = 1; string pr_id = 2; string author_id = 3; string body = 4; string file = 5; int32 line = 6; string severity = 7; bool is_ai = 8; // true if posted by AI agent google.protobuf.Timestamp created_at = 9; } message PRCommentList { repeated PRComment comments = 1; } message ListPRCommentsRequest { string pr_id = 1; } message DeletePRCommentRequest { string comment_id = 1; } message RepoEvent { string type = 1; // "push" | "pr_opened" | "pr_merged" | "tag" string repo_id = 2; string actor = 3; string branch = 4; string commit = 5; google.protobuf.Timestamp at = 6; } // ═══════════════════════════════════════════════════════════════════════ // CI/CD Service — пайплайны и запуска // ═══════════════════════════════════════════════════════════════════════ service CICDService { rpc TriggerPipeline (TriggerPipelineRequest) returns (Pipeline); rpc GetPipeline (PipelineRequest) returns (Pipeline); rpc ListPipelines (ListPipelinesRequest) returns (PipelineList); rpc CancelPipeline (PipelineRequest) returns (Pipeline); // Стрим логов job в реальном времени rpc StreamJobLogs (JobRequest) returns (stream LogChunk); } message TriggerPipelineRequest { string repo_id = 1; string branch = 2; string commit = 3; string trigger = 4; // "manual" | "push" | "pull_request" } message PipelineRequest { string id = 1; } message JobRequest { string pipeline_id = 1; string job_id = 2; } message Pipeline { string id = 1; string repo_id = 2; string repo_name = 3; string branch = 4; string commit_sha = 5; string commit_msg = 6; string status = 7; string trigger = 8; repeated PipelineJob jobs = 9; google.protobuf.Timestamp created_at = 10; google.protobuf.Timestamp started_at = 11; google.protobuf.Timestamp finished_at = 12; string project_id = 13; string environment_id = 14; } message PipelineJob { string id = 1; string name = 2; string status = 3; string runner_id = 4; google.protobuf.Timestamp started_at = 5; google.protobuf.Timestamp finished_at = 6; } message PipelineList { repeated Pipeline pipelines = 1; } message ListPipelinesRequest { string repo_id = 1; int32 limit = 2; int32 offset = 3; } message LogChunk { string job_id = 1; int32 step = 2; string content = 3; google.protobuf.Timestamp at = 4; } // ═══════════════════════════════════════════════════════════════════════ // Project Service — проекты, участники // ═══════════════════════════════════════════════════════════════════════ service ProjectService { rpc CreateProject (CreateProjectRequest) returns (Project); rpc GetProject (ProjectRequest) returns (Project); rpc ListProjects (Empty) returns (ProjectList); rpc GetOverview (ProjectRequest) returns (ProjectOverview); rpc LinkRepo (LinkRepoRequest) returns (Empty); rpc ArchiveProject (ProjectRequest) returns (Empty); // Участники rpc InviteMember (InviteMemberRequest) returns (ProjectMember); rpc ListMembers (ProjectRequest) returns (MemberList); rpc SetMemberRole (SetMemberRoleRequest) returns (Empty); rpc RemoveMember (RemoveMemberRequest) returns (Empty); // Методологии rpc ListMethodologies (Empty) returns (MethodologyList); // Релизы rpc CreateRelease (CreateReleaseRequest) returns (Release); rpc ListReleases (ProjectRequest) returns (ReleaseList); rpc GetRelease (ReleaseRequest) returns (Release); // GitFlow configuration rpc GetGitFlowConfig (ProjectRequest) returns (GitFlowConfig); rpc SetGitFlowConfig (SetGitFlowConfigRequest) returns (GitFlowConfig); // Environments rpc CreateEnvironment (CreateEnvironmentRequest) returns (Environment); rpc ListEnvironments (ProjectRequest) returns (EnvironmentList); rpc UpdateEnvironment (UpdateEnvironmentRequest) returns (Environment); rpc DeleteEnvironment (DeleteEnvironmentRequest) returns (Empty); // QA Gates rpc CreateQAGate (CreateQAGateRequest) returns (QAGate); rpc ListQAGates (ProjectRequest) returns (QAGateList); rpc UpdateQAGate (UpdateQAGateRequest) returns (QAGate); rpc DeleteQAGate (DeleteQAGateRequest) returns (Empty); // Release flow rpc PromoteRelease (PromoteReleaseRequest) returns (ReleaseDeployment); rpc RollbackRelease (RollbackReleaseRequest) returns (ReleaseDeployment); // Release QA verification rpc VerifyRelease (ReleaseRequest) returns (ReleaseVerification); rpc GenerateReleaseNotes (ReleaseRequest) returns (Release); } message CreateProjectRequest { string name = 1; string description = 2; repeated string repos = 3; bool is_public = 4; string methodology = 5; // "kanban" | "scrum" | "pmbok" | "mixed" string prefix = 6; // branch naming prefix: "KX", "APP", auto-generated from name if empty string team_id = 7; // org team (optional, for org-based projects) } message ProjectRequest { string project_id = 1; } message Project { string id = 1; string name = 2; string description = 3; string slug = 4; repeated string repos = 5; bool is_public = 6; string avatar_url = 7; string created_by = 8; google.protobuf.Timestamp created_at = 9; google.protobuf.Timestamp updated_at = 10; google.protobuf.Timestamp archived_at = 11; ProjectConfig config = 12; string prefix = 13; // branch naming prefix: "KX", "APP" string team_id = 14; // org team } message ProjectList { repeated Project projects = 1; } message ProjectOverview { Project project = 1; repeated ProjectMember members = 2; BoardState board_state = 3; int32 active_tasks = 4; int32 open_prs = 5; repeated ActivityItem recent_activity = 6; } message ActivityItem { string actor = 1; string action = 2; // "created task #42", "merged feat/ivan/42-..." string task_id = 3; int32 task_num = 4; string branch = 5; google.protobuf.Timestamp at = 6; } message LinkRepoRequest { string project_id = 1; string repo_id = 2; } message InviteMemberRequest { string project_id = 1; string user_id = 2; string role = 3; // "admin" | "lead" | "developer" | "devops" | "pm" | "analyst" | "agent" | "viewer" } message ProjectMember { string id = 1; string project_id = 2; string user_id = 3; string role = 4; string role_display = 5; repeated string permissions = 6; google.protobuf.Timestamp joined_at = 7; string invited_by = 8; string username = 9; } message MemberList { repeated ProjectMember members = 1; } message SetMemberRoleRequest { string project_id = 1; string user_id = 2; string role = 3; } message RemoveMemberRequest { string project_id = 1; string user_id = 2; } message Methodology { string style = 1; // "kanban" | "scrum" | "pmbok" | "mixed" string name = 2; string description = 3; bool has_sprints = 4; string sprint_name = 5; // "Sprint" | "Iteration" | ... } message MethodologyList { repeated Methodology items = 1; } // ── Releases ── message Release { string id = 1; string project_id = 2; string version = 3; // e.g. "1.1.0" string title = 4; string notes = 5; // markdown release notes string tag = 6; // git tag string branch = 7; // release branch string status = 8; // draft | published string author_id = 9; repeated string task_ids = 10; // linked closed tasks google.protobuf.Timestamp created_at = 11; google.protobuf.Timestamp published_at = 12; repeated ReleaseDeployment deployments = 13; repeated QAGateResult qa_results = 14; } message CreateReleaseRequest { string project_id = 1; string version = 2; string title = 3; string notes = 4; string tag = 5; string branch = 6; repeated string task_ids = 7; } message ReleaseRequest { string id = 1; } message ReleaseList { repeated Release releases = 1; } message ReleaseVerification { string release_id = 1; string pipeline_id = 2; string status = 3; // "running" | "passed" | "failed" repeated TaskQAResult task_results = 4; } message TaskQAResult { string task_id = 1; int32 task_number = 2; string task_title = 3; string qa_type = 4; string status = 5; // "passed" | "failed" | "skipped" | "no_script" string output = 6; } // ── GitFlow / Environments / QA requests ──────────────────────────── message SetGitFlowConfigRequest { string project_id = 1; GitFlowConfig config = 2; } message CreateEnvironmentRequest { string project_id = 1; string name = 2; string slug = 3; int32 order = 4; string linked_branch = 5; repeated string runner_labels = 6; string vault_secret_path = 7; bool auto_deploy = 8; string deploy_pipeline_ref = 9; } message EnvironmentList { repeated Environment environments = 1; } message UpdateEnvironmentRequest { string id = 1; string name = 2; string linked_branch = 3; repeated string runner_labels = 4; string vault_secret_path = 5; bool auto_deploy = 6; string deploy_pipeline_ref = 7; } message DeleteEnvironmentRequest { string id = 1; } message CreateQAGateRequest { string project_id = 1; QAStage stage = 2; QACheckType check_type = 3; string name = 4; bool required = 5; bool blocking = 6; string pipeline_job_ref = 7; float threshold = 8; string environment_id = 9; } message QAGateList { repeated QAGate gates = 1; } message UpdateQAGateRequest { string id = 1; string name = 2; bool required = 3; bool blocking = 4; string pipeline_job_ref = 5; float threshold = 6; } message DeleteQAGateRequest { string id = 1; } message PromoteReleaseRequest { string release_id = 1; string environment_id = 2; } message RollbackReleaseRequest { string release_id = 1; string environment_id = 2; } // ═══════════════════════════════════════════════════════════════════════ // Task Service — задачи, Kanban, спринты // ═══════════════════════════════════════════════════════════════════════ service TaskService { // CRUD rpc CreateTask (CreateTaskRequest) returns (Task); rpc GetTask (TaskRequest) returns (Task); rpc ListTasks (ListTasksRequest) returns (TaskList); rpc UpdateTask (UpdateTaskRequest) returns (Task); rpc DeleteTask (TaskRequest) returns (Empty); // Действия rpc AssignTask (AssignTaskRequest) returns (Task); rpc MoveTask (MoveTaskRequest) returns (Task); // Kanban rpc GetBoard (GetBoardRequest) returns (BoardState); // Спринты rpc CreateSprint (CreateSprintRequest) returns (Sprint); rpc ListSprints (ProjectRequest) returns (SprintList); rpc GetSprintSummary (SprintRequest) returns (SprintSummary); rpc MoveTasksToSprint (MoveTasksToSprintRequest) returns (Empty); // AI контекст rpc GetTaskAIContext (TaskRequest) returns (TaskAIContext); } message CreateTaskRequest { string project_id = 1; string title = 2; string description = 3; string type = 4; // "feature" | "bug" | "infra" | "docs" | "analysis" | "research" int32 priority = 5; // 0=low 1=medium 2=high 3=critical int32 story_points = 6; repeated string labels = 7; repeated string repos = 8; repeated string files = 9; string parent_id = 10; string ai_prompt = 11; string base_branch = 12; repeated string blocked_by = 13; repeated AcceptanceCriterion acceptance_criteria = 14; google.protobuf.Timestamp due_date = 15; string qa_script_path = 16; // path to QA script in repo: "qa/42-jwt.sh" string qa_type = 17; // "smoke" | "regression" | "both" string docs_path = 18; // path to docs in repo: "docs/42-jwt.md" repeated string required_capabilities = 19; // agent routing: ["code", "review"] } message TaskRequest { string task_id = 1; } message Task { string id = 1; string project_id = 2; int32 number = 3; // #42 string type = 4; string status = 5; // "backlog" | "todo" | "in_progress" | "review" | "done" | "blocked" | "cancelled" string title = 6; string description = 7; string ai_prompt = 8; repeated AcceptanceCriterion acceptance_criteria = 9; repeated string repos = 10; repeated string files = 11; string branch = 12; string base_branch = 13; int32 pr_number = 14; repeated TaskCommitRef linked_commits = 28; string assignee_id = 15; google.protobuf.Timestamp assigned_at = 16; string sprint_id = 17; int32 priority = 18; int32 story_points = 19; repeated string labels = 20; string parent_id = 21; repeated string blocked_by = 22; string created_by = 23; google.protobuf.Timestamp created_at = 24; google.protobuf.Timestamp updated_at = 25; google.protobuf.Timestamp due_date = 26; google.protobuf.Timestamp closed_at = 27; string qa_script_path = 29; // path to QA script: "qa/42-jwt.sh" string qa_type = 30; // "smoke" | "regression" | "both" string docs_path = 31; // path to docs: "docs/42-jwt.md" // Agent routing repeated string required_capabilities = 32; // ["code", "review"] — what agent needs string assignee_type = 33; // "human" | "agent" } message AcceptanceCriterion { string id = 1; string description = 2; bool auto_check = 3; string test_name = 4; bool met = 5; google.protobuf.Timestamp met_at = 6; string met_by = 7; // "ci" | "claude" | "lead" } message TaskCommitRef { string sha = 1; string message = 2; string author = 3; google.protobuf.Timestamp pushed_at = 4; } message TaskList { repeated Task tasks = 1; } message ListTasksRequest { string project_id = 1; string sprint_id = 2; string status = 3; string assignee_id = 4; string type = 5; int32 limit = 6; int32 offset = 7; } message UpdateTaskRequest { string task_id = 1; string title = 2; string description = 3; string ai_prompt = 4; int32 priority = 5; int32 story_points = 6; repeated string labels = 7; string base_branch = 8; google.protobuf.Timestamp due_date = 9; string qa_script_path = 10; string qa_type = 11; string docs_path = 12; } message AssignTaskRequest { string task_id = 1; string assignee_id = 2; } message MoveTaskRequest { string task_id = 1; string status = 2; } // ── Kanban Board ────────────────────────────────────────────────────── message GetBoardRequest { string project_id = 1; string sprint_id = 2; } message BoardState { Board board = 1; Sprint sprint = 2; repeated ColumnWithTasks columns = 3; BoardStats stats = 4; } message Board { string id = 1; string project_id = 2; string name = 3; repeated Column columns = 4; google.protobuf.Timestamp created_at = 5; } message Column { string id = 1; string name = 2; string status = 3; // задачи с каким статусом показывать int32 wip_limit = 4; // 0 = без лимита int32 order = 5; string color = 6; // hex } message ColumnWithTasks { Column column = 1; repeated Task tasks = 2; int32 count = 3; int32 points = 4; } message BoardStats { int32 total_tasks = 1; int32 completed_today = 2; int32 blocked_tasks = 3; repeated string wip_violations = 4; int32 velocity_this_sprint = 5; repeated AssigneeStat by_assignee = 6; } message AssigneeStat { string assignee_id = 1; int32 in_progress = 2; int32 done = 3; int32 blocked = 4; } // ── Sprints ────────────────────────────────────────────────────────── message CreateSprintRequest { string project_id = 1; string name = 2; string goal = 3; google.protobuf.Timestamp start_date = 4; google.protobuf.Timestamp end_date = 5; } message Sprint { string id = 1; string project_id = 2; string name = 3; string goal = 4; string status = 5; // "planning" | "active" | "completed" google.protobuf.Timestamp start_date = 6; google.protobuf.Timestamp end_date = 7; int32 planned_points = 8; int32 completed_points = 9; float velocity = 10; google.protobuf.Timestamp created_at = 11; } message SprintList { repeated Sprint sprints = 1; } message SprintRequest { string sprint_id = 1; } message SprintSummary { Sprint sprint = 1; int32 total_tasks = 2; int32 done_tasks = 3; int32 in_progress_tasks = 4; int32 blocked_tasks = 5; int32 planned_points = 6; int32 completed_points = 7; float velocity = 8; } message MoveTasksToSprintRequest { string sprint_id = 1; repeated string task_ids = 2; } message TaskAIContext { string task_id = 1; int32 number = 2; string branch = 3; repeated string repos = 4; string ai_context = 5; // markdown для Claude bool has_prompt = 6; } // ═══════════════════════════════════════════════════════════════════════ // AI Service — chat with Claude, code review, context // ═══════════════════════════════════════════════════════════════════════ service AIService { rpc Chat (AIChatRequest) returns (AIChatResponse); rpc ChatStream (AIChatRequest) returns (stream AIChatChunk); } message AIChatRequest { string message = 1; string project_id = 2; // optional: project context string task_id = 3; // optional: task context string repo_id = 4; // optional: repo context repeated AIChatMessage history = 5; // conversation history } message AIChatMessage { string role = 1; // "user" | "assistant" string content = 2; } message AIChatResponse { string message = 1; string model = 2; int32 tokens = 3; } message AIChatChunk { string content = 1; bool done = 2; } // ═══════════════════════════════════════════════════════════════════════ // Admin Service — управление пользователями (server-wide) // ═══════════════════════════════════════════════════════════════════════ service AdminService { rpc ListUsers (Empty) returns (UserList); rpc GetUser (UserRequest) returns (User); rpc UpdateUserRoles (UpdateUserRolesRequest) returns (User); rpc DeleteUser (UserRequest) returns (Empty); rpc CreateBackup (BackupRequest) returns (stream BackupChunk); rpc RestoreBackup (RestoreRequest) returns (RestoreResponse); rpc GetSystemStatus (Empty) returns (SystemStatus); rpc Upgrade (UpgradeRequest) returns (UpgradeResponse); rpc ListDocs (Empty) returns (DocList); rpc GetDoc (DocRequest) returns (DocContent); rpc DownloadBinary (DownloadBinaryRequest) returns (stream BackupChunk); // reuse BackupChunk for streaming } message DocRequest { string name = 1; } message DocContent { string name = 1; string content = 2; } message DocList { repeated DocEntry docs = 1; } message DocEntry { string name = 1; int64 size = 2; } message UpgradeRequest { bool pull_only = 1; // only pull image, don't restart } message UpgradeResponse { bool success = 1; string message = 2; repeated string steps = 3; // log of steps performed } message BackupRequest { bool include_images = 1; // include container images (can be large) } message DownloadBinaryRequest { string name = 1; // "ctl" | "ui" | "runner" } message BackupChunk { bytes data = 1; string filename = 2; // set in first chunk int64 total = 3; // total size, set in last chunk } message RestoreRequest { bytes data = 1; // tar.gz archive bool restore_db = 2; // restore SurrealDB bool restore_repos = 3; // restore git repos bool restore_keys = 4; // restore TLS/SSH keys bool restore_images = 5; // restore container images } message RestoreResponse { bool success = 1; string message = 2; repeated string details = 3; // per-component results } message SystemStatus { string version = 1; string uptime = 2; int32 user_count = 3; int32 repo_count = 4; int32 pipeline_count = 5; bool vault_sealed = 6; int32 runner_count = 7; string db_status = 8; string current_image = 9; // current running image tag } message UserList { repeated User users = 1; } message UserRequest { string user_id = 1; } message UpdateUserRolesRequest { string user_id = 1; repeated string roles = 2; } // ═══════════════════════════════════════════════════════════════════════ // Registry Service — OCI container images // ═══════════════════════════════════════════════════════════════════════ service RegistryService { rpc PushImage (stream ImageChunk) returns (ImageInfo); rpc PullImage (PullImageRequest) returns (stream ImageChunk); rpc ListImages (ListImagesRequest) returns (ImageList); rpc DeleteImage (DeleteImageRequest) returns (Empty); rpc GetRetention (Empty) returns (RetentionPolicy); rpc SetRetention (RetentionPolicy) returns (RetentionPolicy); rpc GetImageStats (ImageStatsRequest) returns (ImageStats); } message ImageChunk { bytes data = 1; // Fields below are set ONLY in the first chunk (header) string name = 2; // image name, e.g. "kovanex-server" string tag = 3; // e.g. "v1.0", "latest", commit SHA string repo_id = 4; string pipeline_id = 5; } message ImageInfo { string name = 1; string tag = 2; string digest = 3; // SHA256 int64 size = 4; string repo_id = 5; string pipeline_id = 6; string pushed_by = 7; google.protobuf.Timestamp pushed_at = 8; repeated string pruned = 9; // tags removed by retention } message PullImageRequest { string repo_id = 1; string name = 2; string tag = 3; } message ListImagesRequest { string repo_id = 1; string name = 2; int32 limit = 3; } message ImageList { repeated ImageInfo images = 1; } message DeleteImageRequest { string digest = 1; } message RetentionPolicy { int32 max_depth = 1; // max tags per image name (0=unlimited) int64 max_bytes = 2; // max total bytes per repo (0=unlimited) } message ImageStatsRequest { string repo_id = 1; } message ImageStats { int32 total_images = 1; int64 total_bytes = 2; RetentionPolicy retention = 3; } // ═══════════════════════════════════════════════════════════════════════ // GitFlow, Environments, QA — Project Configuration // ═══════════════════════════════════════════════════════════════════════ enum BranchStrategy { GITFLOW = 0; GITHUB_FLOW = 1; TRUNK_BASED = 2; } enum MergeMethod { MERGE = 0; SQUASH = 1; REBASE = 2; } enum QAStage { ON_PUSH = 0; ON_PR = 1; PRE_MERGE = 2; PRE_RELEASE = 3; PRE_DEPLOY = 4; } enum QACheckType { UNIT_TESTS = 0; INTEGRATION_TESTS = 1; LINT = 2; SECURITY_SCAN = 3; AI_REVIEW = 4; MANUAL_APPROVAL = 5; COVERAGE = 6; CUSTOM = 7; } message GitFlowConfig { string id = 1; string project_id = 2; BranchStrategy strategy = 3; string production_branch = 4; // "main" string integration_branch = 5; // "develop" (gitflow only) repeated string protected_branches = 6; repeated MergeRule merge_rules = 7; string feature_prefix = 8; // "feat/" string bugfix_prefix = 9; // "fix/" string release_prefix = 10; // "release/" string hotfix_prefix = 11; // "hotfix/" bool auto_release_branch = 12; // auto-create release/x.y.z } message MergeRule { string target_branch = 1; // branch pattern, e.g. "main", "release/*" MergeMethod method = 2; bool require_pr = 3; int32 min_approvals = 4; bool require_ci_pass = 5; } message Environment { string id = 1; string project_id = 2; string name = 3; // "Production", "Staging", "Development" string slug = 4; // "production", "staging", "dev" int32 order = 5; // 0=dev, 1=staging, 2=prod string linked_branch = 6; // "main" for prod, "develop" for dev repeated string runner_labels = 7; // runner label filter string vault_secret_path = 8; // "kv/env/{project}/{env}" bool auto_deploy = 9; // auto-deploy on linked branch push string current_version = 10; // currently deployed version string deploy_status = 11; // "deployed" | "deploying" | "failed" | "idle" string deploy_pipeline_ref = 12; // pipeline job to run for deploy google.protobuf.Timestamp last_deployed_at = 13; } message QAGate { string id = 1; string project_id = 2; QAStage stage = 3; QACheckType check_type = 4; string name = 5; bool required = 6; bool blocking = 7; // blocks promotion if failed string pipeline_job_ref = 8; // which pipeline job implements this check float threshold = 9; // e.g. 80.0 for coverage string environment_id = 10; // gate applies to specific env (empty=all) } message QAGateResult { string id = 1; string gate_id = 2; string pipeline_id = 3; string pr_id = 4; string status = 5; // "passed" | "failed" | "skipped" | "pending" string details = 6; google.protobuf.Timestamp evaluated_at = 7; } message ReleaseDeployment { string id = 1; string release_id = 2; string environment_id = 3; string status = 4; // "pending" | "deploying" | "deployed" | "failed" | "rolled_back" string pipeline_id = 5; string deployed_by = 6; google.protobuf.Timestamp deployed_at = 7; } message ProjectConfig { string methodology = 1; // "kanban" | "scrum" | "pmbok" | "mixed" GitFlowConfig gitflow = 2; repeated Environment environments = 3; repeated QAGate qa_gates = 4; } // ═══════════════════════════════════════════════════════════════════════ // Audit Service — лог действий: кто, что, когда // ═══════════════════════════════════════════════════════════════════════ service AuditService { rpc ListAuditLog (ListAuditLogRequest) returns (AuditLogList); } message AuditEntry { string id = 1; string actor_id = 2; // user ID string actor_name = 3; // username (denormalized) string action = 4; // "repo.create", "task.move", "pipeline.trigger", etc. string resource_type = 5; // "repo", "task", "pipeline", "user", "pr", "release" string resource_id = 6; string resource_name = 7; // human-readable name (denormalized) string project_id = 8; // optional, for project-scoped filtering string details = 9; // JSON extra data (old/new values, etc.) google.protobuf.Timestamp at = 10; string org_id = 11; // optional, for org-scoped filtering string team_id = 12; // optional, for team-scoped filtering } message AuditLogList { repeated AuditEntry entries = 1; int32 total = 2; } message ListAuditLogRequest { string actor_id = 1; string resource_type = 2; string resource_id = 3; string project_id = 4; string action = 5; int32 limit = 6; int32 offset = 7; string org_id = 8; string team_id = 9; } // ═══════════════════════════════════════════════════════════════════════ // Webhook Service — outgoing HTTP notifications (Telegram, Slack, etc.) // ═══════════════════════════════════════════════════════════════════════ service WebhookService { rpc CreateWebhook (CreateWebhookRequest) returns (Webhook); rpc ListWebhooks (ProjectRequest) returns (WebhookList); rpc DeleteWebhook (WebhookRequest) returns (Empty); rpc TestWebhook (WebhookRequest) returns (WebhookDelivery); } message Webhook { string id = 1; string project_id = 2; string name = 3; string url = 4; // POST target (Telegram bot API, Slack webhook, custom) string type = 5; // "slack" | "telegram" | "custom" repeated string events = 6; // "git.push", "pipeline.done", "task.move", "*" string secret = 7; // HMAC signing secret (for custom) bool active = 8; google.protobuf.Timestamp created_at = 9; google.protobuf.Timestamp last_delivery_at = 10; int32 failure_count = 11; } message CreateWebhookRequest { string project_id = 1; string name = 2; string url = 3; string type = 4; // "slack" | "telegram" | "custom" repeated string events = 5; string secret = 6; } message WebhookRequest { string id = 1; } message WebhookList { repeated Webhook webhooks = 1; } message WebhookDelivery { bool success = 1; int32 status_code = 2; string response = 3; int64 duration_ms = 4; } // ═══════════════════════════════════════════════════════════════════════ // Feedback Service — bot-submitted issues, PM moderation // ═══════════════════════════════════════════════════════════════════════ service FeedbackService { rpc SubmitFeedback (SubmitFeedbackRequest) returns (FeedbackEntry); rpc ListFeedback (ListFeedbackRequest) returns (FeedbackList); rpc ResolveFeedback (ResolveFeedbackRequest) returns (FeedbackEntry); rpc GetFeedbackStats (ProjectRequest) returns (FeedbackStats); rpc SetProjectBot (SetProjectBotRequest) returns (ProjectBot); rpc GetProjectBot (ProjectRequest) returns (ProjectBot); } message SubmitFeedbackRequest { string project_id = 1; string source = 2; // "telegram" | "slack" | "cli" string author_name = 3; // external user name (telegram username, etc.) string author_id = 4; // external user ID string message = 5; // raw message text string type = 6; // "bug" | "feature" | "question" (auto-detected if empty) } message FeedbackEntry { string id = 1; string project_id = 2; string source = 3; string author_name = 4; string author_id = 5; string message = 6; string type = 7; string status = 8; // "pending" | "accepted" | "rejected" string task_id = 9; // linked task (created in triage) string reject_reason = 10; google.protobuf.Timestamp created_at = 11; google.protobuf.Timestamp resolved_at = 12; } message FeedbackList { repeated FeedbackEntry entries = 1; } message ListFeedbackRequest { string project_id = 1; string status = 2; // filter by status int32 limit = 3; int32 offset = 4; } message SetProjectBotRequest { string project_id = 1; string telegram_token = 2; string telegram_chat_id = 3; string slack_channel = 4; bool auto_create_task = 5; // auto-create triage task from feedback bool active = 6; } message ResolveFeedbackRequest { string id = 1; // feedback entry ID string action = 2; // "accept" | "reject" string reject_reason = 3; // reason (only for reject) } message FeedbackStats { int32 total_pending = 1; int32 total_today = 2; int32 accepted_today = 3; int32 rejected_today = 4; int32 bugs = 5; int32 features = 6; int32 questions = 7; } message ProjectBot { string id = 1; string project_id = 2; string telegram_chat_id = 3; string slack_channel = 4; bool auto_create_task = 5; bool active = 6; bool telegram_configured = 7; bool slack_configured = 8; } // ═══════════════════════════════════════════════════════════════════════ // Module Service — server feature modules management // ═══════════════════════════════════════════════════════════════════════ service ModuleService { rpc ListModules (Empty) returns (ModuleList); rpc EnableModule (ModuleRequest) returns (Module); rpc DisableModule (ModuleRequest) returns (Module); rpc GetModuleConfig (ModuleRequest) returns (ModuleConfig); rpc SetModuleConfig (SetModuleConfigRequest) returns (ModuleConfig); } message Module { string name = 1; // "webhooks" | "telegram" | "ai_agent" | "email" string description = 2; bool enabled = 3; bool configured = 4; // has valid config (e.g. API key set) string status = 5; // "active" | "disabled" | "error" } message ModuleList { repeated Module modules = 1; } message ModuleRequest { string name = 1; } message ModuleConfig { string name = 1; map config = 2; // key-value settings bool enabled = 3; // read-only when enabled } message SetModuleConfigRequest { string name = 1; map config = 2; } // ═══════════════════════════════════════════════════════════════════════ // Org Service — organizations, teams, members // ═══════════════════════════════════════════════════════════════════════ service OrgService { // Organizations rpc CreateOrg (CreateOrgRequest) returns (Organization); rpc GetOrg (OrgRequest) returns (Organization); rpc ListOrgs (Empty) returns (OrgList); rpc UpdateOrg (UpdateOrgRequest) returns (Organization); rpc DeleteOrg (OrgRequest) returns (Empty); // Teams (within org) rpc CreateTeam (CreateTeamRequest) returns (Team); rpc GetTeam (TeamRequest) returns (Team); rpc ListTeams (OrgRequest) returns (TeamList); rpc UpdateTeam (UpdateTeamRequest) returns (Team); rpc DeleteTeam (TeamRequest) returns (Empty); // Members (within team) rpc AddTeamMember (AddTeamMemberRequest) returns (TeamMember); rpc RemoveTeamMember (RemoveTeamMemberRequest) returns (Empty); rpc ListTeamMembers (TeamRequest) returns (TeamMemberList); rpc UpdateTeamMemberRole (UpdateTeamMemberRoleRequest) returns (TeamMember); // My orgs/teams (for current user) rpc ListMyOrgs (Empty) returns (OrgList); // Quotas rpc SetTeamQuota (SetTeamQuotaRequest) returns (Team); rpc GetTeamQuota (TeamRequest) returns (TeamQuota); // Invitations rpc CreateInvite (CreateInviteRequest) returns (Invite); rpc ListInvites (TeamRequest) returns (InviteList); rpc AcceptInvite (AcceptInviteRequest) returns (TeamMember); rpc RevokeInvite (InviteRequest) returns (Empty); } message Invite { string id = 1; string team_id = 2; string code = 3; // short invite code (e.g. "KX-abc123") string role = 4; // role assigned on accept string created_by = 5; string status = 6; // "pending" | "accepted" | "revoked" string accepted_by = 7; google.protobuf.Timestamp created_at = 8; google.protobuf.Timestamp expires_at = 9; } message CreateInviteRequest { string team_id = 1; string role = 2; // default: "member" string ttl = 3; // "24h", "7d" — default 7d } message InviteRequest { string id = 1; } message AcceptInviteRequest { string code = 1; // invite code } message InviteList { repeated Invite invites = 1; } message Organization { string id = 1; string name = 2; string slug = 3; // URL-safe identifier (auto from name) string description = 4; string owner_id = 5; // user who created the org int32 team_count = 6; int32 member_count = 7; google.protobuf.Timestamp created_at = 8; } message OrgRequest { string id = 1; } message CreateOrgRequest { string name = 1; string description = 2; } message UpdateOrgRequest { string id = 1; string name = 2; string description = 3; } message OrgList { repeated Organization orgs = 1; } message Team { string id = 1; string org_id = 2; string name = 3; string slug = 4; string description = 5; int32 member_count = 6; int32 project_count = 7; google.protobuf.Timestamp created_at = 8; TeamQuota quota = 9; } message TeamQuota { int32 max_repos = 1; // 0 = unlimited int32 max_projects = 2; int32 max_members = 3; int32 max_pipelines_per_day = 4; int64 max_storage_mb = 5; // Current usage (read-only, filled by server) int32 used_repos = 10; int32 used_projects = 11; int32 used_members = 12; int64 used_storage_mb = 13; } message SetTeamQuotaRequest { string team_id = 1; TeamQuota quota = 2; } message TeamRequest { string id = 1; } message CreateTeamRequest { string org_id = 1; string name = 2; string description = 3; } message UpdateTeamRequest { string id = 1; string name = 2; string description = 3; } message TeamList { repeated Team teams = 1; } message TeamMember { string id = 1; string team_id = 2; string user_id = 3; string username = 4; string role = 5; // "org_admin" | "team_admin" | "member" | "viewer" google.protobuf.Timestamp joined_at = 6; } message AddTeamMemberRequest { string team_id = 1; string user_id = 2; string role = 3; } message RemoveTeamMemberRequest { string team_id = 1; string user_id = 2; } message UpdateTeamMemberRoleRequest { string team_id = 1; string user_id = 2; string role = 3; } message TeamMemberList { repeated TeamMember members = 1; } // ═══════════════════════════════════════════════════════════════════════ // Vault Service — secrets management, seal/unseal, tokens, audit // ═══════════════════════════════════════════════════════════════════════ service VaultService { rpc GetSealStatus (Empty) returns (VaultSealStatus); rpc Unseal (VaultUnsealRequest) returns (VaultSealStatus); rpc Seal (Empty) returns (VaultSealStatus); rpc ReadSecret (VaultSecretRequest) returns (VaultSecretResponse); rpc WriteSecret (VaultWriteRequest) returns (Empty); rpc DeleteSecret (VaultSecretRequest) returns (Empty); rpc ListSecrets (VaultListRequest) returns (VaultListResponse); rpc CreateToken (VaultCreateTokenRequest) returns (VaultTokenResponse); rpc RevokeToken (VaultRevokeTokenRequest) returns (Empty); rpc LookupToken (Empty) returns (VaultTokenResponse); rpc ListAuditLog (VaultAuditRequest) returns (VaultAuditResponse); } message VaultSealStatus { bool sealed = 1; bool initialized = 2; string version = 3; } message VaultUnsealRequest { string key = 1; } message VaultSecretRequest { string path = 1; } message VaultSecretResponse { string path = 1; map data = 2; int32 version = 3; } message VaultWriteRequest { string path = 1; map data = 2; } message VaultListRequest { string prefix = 1; } message VaultListResponse { repeated string keys = 1; } message VaultCreateTokenRequest { string display_name = 1; string project_id = 2; repeated string policies = 3; string ttl = 4; } message VaultTokenResponse { string id = 1; string display_name = 2; repeated string policies = 3; string project_id = 4; google.protobuf.Timestamp created_at = 5; google.protobuf.Timestamp expires_at = 6; int32 use_count = 7; int32 max_uses = 8; } message VaultRevokeTokenRequest { string token_id = 1; } message VaultAuditRequest { string path = 1; int32 limit = 2; } message VaultAuditEntry { string id = 1; string token_id = 2; string operation = 3; string path = 4; bool success = 5; string error = 6; google.protobuf.Timestamp at = 7; } message VaultAuditResponse { repeated VaultAuditEntry entries = 1; } // ═══════════════════════════════════════════════════════════════════════ // Agent Service — AI agents as first-class citizens // ═══════════════════════════════════════════════════════════════════════ service AgentService { // Agent CRUD rpc CreateAgent (CreateAgentRequest) returns (Agent); rpc GetAgent (AgentRequest) returns (Agent); rpc ListAgents (ListAgentsRequest) returns (AgentList); rpc UpdateAgent (UpdateAgentRequest) returns (Agent); rpc DeleteAgent (AgentRequest) returns (Empty); // Agent auth (PSK — same mechanism as runners) rpc CreateAgentToken (CreateAgentTokenRequest) returns (CreateAgentTokenResponse); rpc RegisterAgent (RegisterAgentRequest) returns (RegisterAgentResponse); rpc ListAgentKeys (Empty) returns (AgentKeyList); rpc RevokeAgentKey (AgentRequest) returns (Empty); } message Agent { string id = 1; string name = 2; // display name (e.g. "review-bot", "deploy-agent") string type = 3; // "claude", "copilot", "custom" string status = 4; // "online" | "offline" | "busy" string owner_id = 5; // user who created/manages this agent string team_id = 6; // team this agent belongs to (scope) repeated string capabilities = 7; // ["code", "review", "deploy", "chat", "test"] AgentQuota quota = 8; google.protobuf.Timestamp created_at = 9; google.protobuf.Timestamp last_seen_at = 10; string description = 11; string model = 12; // e.g. "claude-sonnet-4-20250514" } message AgentQuota { int32 max_requests_per_hour = 1; // 0 = unlimited int64 max_tokens_per_day = 2; // 0 = unlimited int32 max_concurrent_tasks = 3; // 0 = unlimited } message AgentRequest { string id = 1; } message CreateAgentRequest { string name = 1; string type = 2; // default: "custom" string team_id = 3; // optional: scope to team repeated string capabilities = 4; string description = 5; string model = 6; AgentQuota quota = 7; } message UpdateAgentRequest { string id = 1; string name = 2; string type = 3; string team_id = 4; repeated string capabilities = 5; string description = 6; string model = 7; AgentQuota quota = 8; } message ListAgentsRequest { string team_id = 1; // optional: filter by team string status = 2; // optional: filter by status } message AgentList { repeated Agent agents = 1; } // Agent PSK auth — mirrors runner PSK flow message CreateAgentTokenRequest { string agent_name = 1; string ttl = 2; // "24h" default } message CreateAgentTokenResponse { string token = 1; // one-time registration token (atok_...) string expires_at = 2; } message RegisterAgentRequest { string registration_token = 1; string name = 2; repeated string capabilities = 3; } message RegisterAgentResponse { string agent_id = 1; string psk = 2; // long-lived key (apsk_...) } message AgentKeyInfo { string id = 1; string agent_id = 2; string agent_name = 3; string fingerprint = 4; string status = 5; // "active" | "revoked" google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp last_auth_at = 7; } message AgentKeyList { repeated AgentKeyInfo keys = 1; } // ═══════════════════════════════════════════════════════════════════════ // Message Service — inter-agent and human-agent communication // ═══════════════════════════════════════════════════════════════════════ service MessageService { // Send a message to a channel rpc SendMessage (SendMessageRequest) returns (Message); // Subscribe to channel(s) — bidi stream for real-time messaging rpc Subscribe (stream ClientEvent) returns (stream Message); // Read history rpc GetHistory (GetHistoryRequest) returns (MessageList); // Channel management rpc ListChannels (ListChannelsRequest) returns (ChannelList); rpc GetChannel (ChannelRequest) returns (Channel); rpc CreateChannel (CreateChannelRequest) returns (Channel); } message Message { string id = 1; string channel = 2; // "project:{id}", "task:{id}", "pipeline:{id}", "agent:{id}", "team:{id}" string sender_id = 3; // user_id or agent_id string sender_name = 4; string sender_type = 5; // "human" | "agent" string content = 6; string content_type = 7; // "text" | "code" | "diff" | "json" | "markdown" string reply_to = 8; // message ID for threading map metadata = 9; // task_id, pipeline_id, commit_sha, etc google.protobuf.Timestamp created_at = 10; } message SendMessageRequest { string channel = 1; string content = 2; string content_type = 3; // default: "text" string reply_to = 4; // optional: thread reply map metadata = 5; } // Client events for Subscribe stream message ClientEvent { oneof event { SubscribeRequest subscribe = 1; // join channel(s) UnsubscribeRequest unsubscribe = 2; // leave channel SendMessageRequest message = 3; // send via stream Heartbeat heartbeat = 4; } } message SubscribeRequest { repeated string channels = 1; // channels to subscribe to } message UnsubscribeRequest { string channel = 1; } message Heartbeat { int64 timestamp = 1; } message GetHistoryRequest { string channel = 1; int32 limit = 2; // default: 50 string before = 3; // message ID — pagination cursor } message MessageList { repeated Message messages = 1; bool has_more = 2; } message Channel { string id = 1; string name = 2; // display name string type = 3; // "project", "task", "pipeline", "agent", "team", "direct" string ref_id = 4; // referenced entity ID (project_id, task_id, etc) int32 member_count = 5; int32 message_count = 6; google.protobuf.Timestamp created_at = 7; google.protobuf.Timestamp last_message_at = 8; } message ChannelRequest { string channel = 1; // channel identifier (e.g. "project:uuid") } message CreateChannelRequest { string name = 1; string type = 2; // "project", "task", "team", "direct" string ref_id = 3; // link to entity } message ListChannelsRequest { string type = 1; // optional filter by type string ref_id = 2; // optional filter by referenced entity } message ChannelList { repeated Channel channels = 1; } // ═══════════════════════════════════════════════════════════════════════ // Common // ═══════════════════════════════════════════════════════════════════════ message Empty {}