docs(api): document issue access, errors, the owner topic and the issues setting
- commit
- 939090845b49b704c717b205b10b702abcd9a833
- author
- brice · · 2026-10-19 08:50 UTC
- parent
- bfc173e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
9 files · +108 -39
M README.md +1 -0
| @@ -33,6 +33,7 @@ Status: early development. | |||
| 33 | 33 | | `GitService` | refs, trees, blobs, commits and diffs | | |
| 34 | 34 | | `TokenService` | API tokens | | |
| 35 | 35 | | `MergeRequestService` | merge requests, comments, merge | | |
| 36 | + | | `IssueService` | issues, comments, owner moderation of public submissions | | |
| 36 | 37 | | `SessionService` | web sign-in links (owner only) | | |
| 37 | 38 | | `EventService` | live event stream, resumable | | |
| 38 | 39 | | `ServerService` | version, features, signing key, OIDC key rotation | | |
| @@ -33,6 +33,7 @@ Status: early development. | |||||
| 33 | | `GitService` | refs, trees, blobs, commits and diffs | | 33 | | `GitService` | refs, trees, blobs, commits and diffs | | ||
| 34 | | `TokenService` | API tokens | | 34 | | `TokenService` | API tokens | | ||
| 35 | | `MergeRequestService` | merge requests, comments, merge | | 35 | | `MergeRequestService` | merge requests, comments, merge | | ||
| 36 | + | | `IssueService` | issues, comments, owner moderation of public submissions | | |||
| 36 | | `SessionService` | web sign-in links (owner only) | | 37 | | `SessionService` | web sign-in links (owner only) | | ||
| 37 | | `EventService` | live event stream, resumable | | 38 | | `EventService` | live event stream, resumable | | ||
| 38 | | `ServerService` | version, features, signing key, OIDC key rotation | | 39 | | `ServerService` | version, features, signing key, OIDC key rotation | | ||
M docs/services.md +2 -1
| @@ -6,8 +6,9 @@ | |||
| 6 | 6 | | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` | | |
| 7 | 7 | | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` | | |
| 8 | 8 | | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` | | |
| 9 | + | | `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Feature `issues`. Issues share numbers with merge requests | | |
| 9 | 10 | | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) | | |
| 10 | - | | `EventService` | `Watch` (streamed, resumable) | | |
| 11 | + | | `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content) | | |
| 11 | 12 | | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) | | |
| 12 | 13 | | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) | | |
| 13 | 14 | | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` | | |
| @@ -6,8 +6,9 @@ | |||||
| 6 | | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` | | 6 | | `GitService` | `ListRefs`, `GetTree`, `ListPaths`, `GetBlob` (streamed), `ListCommits`, `GetCommit` (structured diff), `ResolveRevision` | | ||
| 7 | | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` | | 7 | | `TokenService` | `CreateToken` (secret shown once), `ListTokens`, `RevokeToken` | | ||
| 8 | | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` | | 8 | | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `EditComment`, `DeleteComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` | | ||
| 9 | + | | `IssueService` | `CreateIssue`, `ListIssues`, `GetIssue` (comments), `AddIssueComment`, `CloseIssue`, `ReopenIssue`; owner only: `ListPendingIssues`, `ApproveIssue`, `RejectIssue`. Feature `issues`. Issues share numbers with merge requests | | |||
| 9 | | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) | | 10 | | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) | | ||
| 10 | - | | `EventService` | `Watch` (streamed, resumable) | | 11 | + | | `EventService` | `Watch` (streamed, resumable). Topics `home`, `repo:<name>` and `owner` (owner rights only). Events 17–21: `issue_opened`, `issue_closed`, `issue_reopened`, `issue_commented`, and `issue_submitted` (only on `owner`, no content) | |
| 11 | | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) | | 12 | | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) | | ||
| 12 | | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) | | 13 | | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) | | ||
| 13 | | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` | | 14 | | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` | | ||
M gen/ermite/ermiteconnect/event.connect.go +10 -8
| @@ -49,10 +49,11 @@ type EventServiceClient interface { | |||
| 49 | 49 | // for merge requests, merge_request_opened, merge_request_merged, | |
| 50 | 50 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | |
| 51 | 51 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | |
| 52 | - | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | |
| 53 | - | // owner watchers only). Each one is delivered on the | |
| 54 | - | // "repo:<name>" topic of its repository and on "home", subject to the | |
| 55 | - | // visibility of the repository. | |
| 52 | + | // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each | |
| 53 | + | // one is delivered on the "repo:<name>" topic of its repository and on | |
| 54 | + | // "home", subject to the visibility of the repository, except | |
| 55 | + | // issue_submitted: it is delivered only on the "owner" topic, never on | |
| 56 | + | // "home" or "repo:<name>", and carries no content (see IssueSubmitted). | |
| 56 | 57 | // | |
| 57 | 58 | // Servers with the feature "ci.runs" also publish run_updated and | |
| 58 | 59 | // job_updated, only on the "repo:<name>" topic of their repository (never on | |
| @@ -124,10 +125,11 @@ type EventServiceHandler interface { | |||
| 124 | 125 | // for merge requests, merge_request_opened, merge_request_merged, | |
| 125 | 126 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | |
| 126 | 127 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | |
| 127 | - | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | |
| 128 | - | // owner watchers only). Each one is delivered on the | |
| 129 | - | // "repo:<name>" topic of its repository and on "home", subject to the | |
| 130 | - | // visibility of the repository. | |
| 128 | + | // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each | |
| 129 | + | // one is delivered on the "repo:<name>" topic of its repository and on | |
| 130 | + | // "home", subject to the visibility of the repository, except | |
| 131 | + | // issue_submitted: it is delivered only on the "owner" topic, never on | |
| 132 | + | // "home" or "repo:<name>", and carries no content (see IssueSubmitted). | |
| 131 | 133 | // | |
| 132 | 134 | // Servers with the feature "ci.runs" also publish run_updated and | |
| 133 | 135 | // job_updated, only on the "repo:<name>" topic of their repository (never on | |
| @@ -49,10 +49,11 @@ type EventServiceClient interface { | |||||
| 49 | // for merge requests, merge_request_opened, merge_request_merged, | 49 | // for merge requests, merge_request_opened, merge_request_merged, | ||
| 50 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | 50 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | ||
| 51 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | 51 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | ||
| 52 | - | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | 52 | + | // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each |
| 53 | - | // owner watchers only). Each one is delivered on the | 53 | + | // one is delivered on the "repo:<name>" topic of its repository and on |
| 54 | - | // "repo:<name>" topic of its repository and on "home", subject to the | 54 | + | // "home", subject to the visibility of the repository, except |
| 55 | - | // visibility of the repository. | 55 | + | // issue_submitted: it is delivered only on the "owner" topic, never on |
| 56 | + | // "home" or "repo:<name>", and carries no content (see IssueSubmitted). | |||
| 56 | // | 57 | // | ||
| 57 | // Servers with the feature "ci.runs" also publish run_updated and | 58 | // Servers with the feature "ci.runs" also publish run_updated and | ||
| 58 | // job_updated, only on the "repo:<name>" topic of their repository (never on | 59 | // job_updated, only on the "repo:<name>" topic of their repository (never on | ||
| @@ -124,10 +125,11 @@ type EventServiceHandler interface { | |||||
| 124 | // for merge requests, merge_request_opened, merge_request_merged, | 125 | // for merge requests, merge_request_opened, merge_request_merged, | ||
| 125 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | 126 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | ||
| 126 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | 127 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | ||
| 127 | - | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | 128 | + | // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each |
| 128 | - | // owner watchers only). Each one is delivered on the | 129 | + | // one is delivered on the "repo:<name>" topic of its repository and on |
| 129 | - | // "repo:<name>" topic of its repository and on "home", subject to the | 130 | + | // "home", subject to the visibility of the repository, except |
| 130 | - | // visibility of the repository. | 131 | + | // issue_submitted: it is delivered only on the "owner" topic, never on |
| 132 | + | // "home" or "repo:<name>", and carries no content (see IssueSubmitted). | |||
| 131 | // | 133 | // | ||
| 132 | // Servers with the feature "ci.runs" also publish run_updated and | 134 | // Servers with the feature "ci.runs" also publish run_updated and | ||
| 133 | // job_updated, only on the "repo:<name>" topic of their repository (never on | 135 | // job_updated, only on the "repo:<name>" topic of their repository (never on | ||
M gen/ermite/event.pb.go +15 -7 generated file: collapsed
M gen/ermite/issue.pb.go +8 -4
| @@ -95,9 +95,12 @@ type Issue struct { | |||
| 95 | 95 | Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` | |
| 96 | 96 | // State is where the issue is in its life. | |
| 97 | 97 | State IssueState `protobuf:"varint,5,opt,name=state,proto3,enum=ermite.IssueState" json:"state,omitempty"` | |
| 98 | - | // Author names who created the issue: the label of the token that was used, | |
| 99 | - | // "root" for the root token, or "owner" for a web session. It is | |
| 100 | - | // informational: labels are not unique and the token may be gone. | |
| 98 | + | // Author names who created the issue: the label of the token or session | |
| 99 | + | // that was used ("root" for the root token, "owner" for a web session), or, | |
| 100 | + | // for an issue approved from the public form, the unverified display name | |
| 101 | + | // the visitor typed (or "anonymous"). It is informational and never proves | |
| 102 | + | // identity: labels are not unique, the token may be gone, and a visitor can | |
| 103 | + | // type "owner". | |
| 101 | 104 | Author string `protobuf:"bytes,6,opt,name=author,proto3" json:"author,omitempty"` | |
| 102 | 105 | // CreatedAt is when the issue was created. | |
| 103 | 106 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | |
| @@ -951,7 +954,8 @@ type PendingIssue struct { | |||
| 951 | 954 | // Body is the content. It may be empty. | |
| 952 | 955 | Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` | |
| 953 | 956 | // Author is the optional display name given by the anonymous submitter. It | |
| 954 | - | // may be empty and is not verified. | |
| 957 | + | // may be empty or "anonymous", and is not verified: it never proves | |
| 958 | + | // identity (a visitor can type "owner"). It becomes Issue.author on approval. | |
| 955 | 959 | Author string `protobuf:"bytes,5,opt,name=author,proto3" json:"author,omitempty"` | |
| 956 | 960 | // CreatedAt is when the issue was submitted. | |
| 957 | 961 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | |
| @@ -95,9 +95,12 @@ type Issue struct { | |||||
| 95 | Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` | 95 | Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` | ||
| 96 | // State is where the issue is in its life. | 96 | // State is where the issue is in its life. | ||
| 97 | State IssueState `protobuf:"varint,5,opt,name=state,proto3,enum=ermite.IssueState" json:"state,omitempty"` | 97 | State IssueState `protobuf:"varint,5,opt,name=state,proto3,enum=ermite.IssueState" json:"state,omitempty"` | ||
| 98 | - | // Author names who created the issue: the label of the token that was used, | 98 | + | // Author names who created the issue: the label of the token or session |
| 99 | - | // "root" for the root token, or "owner" for a web session. It is | 99 | + | // that was used ("root" for the root token, "owner" for a web session), or, |
| 100 | - | // informational: labels are not unique and the token may be gone. | 100 | + | // for an issue approved from the public form, the unverified display name |
| 101 | + | // the visitor typed (or "anonymous"). It is informational and never proves | |||
| 102 | + | // identity: labels are not unique, the token may be gone, and a visitor can | |||
| 103 | + | // type "owner". | |||
| 101 | Author string `protobuf:"bytes,6,opt,name=author,proto3" json:"author,omitempty"` | 104 | Author string `protobuf:"bytes,6,opt,name=author,proto3" json:"author,omitempty"` | ||
| 102 | // CreatedAt is when the issue was created. | 105 | // CreatedAt is when the issue was created. | ||
| 103 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | 106 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | ||
| @@ -951,7 +954,8 @@ type PendingIssue struct { | |||||
| 951 | // Body is the content. It may be empty. | 954 | // Body is the content. It may be empty. | ||
| 952 | Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` | 955 | Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` | ||
| 953 | // Author is the optional display name given by the anonymous submitter. It | 956 | // Author is the optional display name given by the anonymous submitter. It | ||
| 954 | - | // may be empty and is not verified. | 957 | + | // may be empty or "anonymous", and is not verified: it never proves |
| 958 | + | // identity (a visitor can type "owner"). It becomes Issue.author on approval. | |||
| 955 | Author string `protobuf:"bytes,5,opt,name=author,proto3" json:"author,omitempty"` | 959 | Author string `protobuf:"bytes,5,opt,name=author,proto3" json:"author,omitempty"` | ||
| 956 | // CreatedAt is when the issue was submitted. | 960 | // CreatedAt is when the issue was submitted. | ||
| 957 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | 961 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | ||
M gen/ermite/repo.pb.go +7 -2 generated file: collapsed
M proto/ermite/event.proto +20 -11
| @@ -20,10 +20,11 @@ service EventService { | |||
| 20 | 20 | // for merge requests, merge_request_opened, merge_request_merged, | |
| 21 | 21 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | |
| 22 | 22 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | |
| 23 | - | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | |
| 24 | - | // owner watchers only). Each one is delivered on the | |
| 25 | - | // "repo:<name>" topic of its repository and on "home", subject to the | |
| 26 | - | // visibility of the repository. | |
| 23 | + | // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each | |
| 24 | + | // one is delivered on the "repo:<name>" topic of its repository and on | |
| 25 | + | // "home", subject to the visibility of the repository, except | |
| 26 | + | // issue_submitted: it is delivered only on the "owner" topic, never on | |
| 27 | + | // "home" or "repo:<name>", and carries no content (see IssueSubmitted). | |
| 27 | 28 | // | |
| 28 | 29 | // Servers with the feature "ci.runs" also publish run_updated and | |
| 29 | 30 | // job_updated, only on the "repo:<name>" topic of their repository (never on | |
| @@ -58,9 +59,17 @@ service EventService { | |||
| 58 | 59 | // WatchRequest selects the events to receive. | |
| 59 | 60 | message WatchRequest { | |
| 60 | 61 | // Topics are the subjects to follow: "home" for every repository visible to | |
| 61 | - | // the caller, or "repo:<name>" for one repository. Empty means "home". | |
| 62 | - | // Unknown topics are invalid_argument. A "repo:<name>" topic for a repository | |
| 63 | - | // that does not exist or is not visible is not_found. | |
| 62 | + | // the caller, "repo:<name>" for one repository, or "owner". Empty means | |
| 63 | + | // "home". Unknown topics are invalid_argument. A "repo:<name>" topic for a | |
| 64 | + | // repository that does not exist or is not visible is not_found. | |
| 65 | + | // | |
| 66 | + | // The "owner" topic needs owner rights: root, an owner session, or a token | |
| 67 | + | // that holds every scope with no repository or branch limit. Anyone else | |
| 68 | + | // gets the same invalid_argument as for an unknown topic, so the topic is | |
| 69 | + | // not revealed. It carries the merge request and issue events of every | |
| 70 | + | // repository (merge_request_opened, merge_request_merged, | |
| 71 | + | // merge_request_closed, comment_added, and the issue events), repo_updated | |
| 72 | + | // and repo_deleted, plus issue_submitted, which is delivered nowhere else. | |
| 64 | 73 | repeated string topics = 1; | |
| 65 | 74 | // ResumeAfter is the Event.id of the last event the client has seen. Empty | |
| 66 | 75 | // to receive only new events. | |
| @@ -120,8 +129,8 @@ message Event { | |||
| 120 | 129 | IssueReopened issue_reopened = 19; | |
| 121 | 130 | // IssueCommented: a comment was added to an issue. | |
| 122 | 131 | IssueCommented issue_commented = 20; | |
| 123 | - | // IssueSubmitted: an issue is awaiting moderation. Delivered to owner | |
| 124 | - | // watchers only. | |
| 132 | + | // IssueSubmitted: an issue is awaiting moderation. Delivered only on the | |
| 133 | + | // "owner" topic, never on "home" or "repo:<name>", and carries no content. | |
| 125 | 134 | IssueSubmitted issue_submitted = 21; | |
| 126 | 135 | } | |
| 127 | 136 | } | |
| @@ -251,8 +260,8 @@ message IssueCommented { | |||
| 251 | 260 | IssueComment comment = 3; | |
| 252 | 261 | } | |
| 253 | 262 | ||
| 254 | - | // IssueSubmitted reports an issue awaiting moderation. It is delivered to | |
| 255 | - | // owner watchers only. It carries no title or body: content never travels in | |
| 263 | + | // IssueSubmitted reports an issue awaiting moderation. It is delivered only | |
| 264 | + | // on the "owner" topic, never on "home" or "repo:<name>". It carries no title or body: content never travels in | |
| 256 | 265 | // events, read it with IssueService.ListPendingIssues. | |
| 257 | 266 | message IssueSubmitted { | |
| 258 | 267 | // Repo is the name of the repository. | |
| @@ -20,10 +20,11 @@ service EventService { | |||||
| 20 | // for merge requests, merge_request_opened, merge_request_merged, | 20 | // for merge requests, merge_request_opened, merge_request_merged, | ||
| 21 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | 21 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | ||
| 22 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | 22 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | ||
| 23 | - | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | 23 | + | // issue_closed, issue_reopened and issue_commented, and issue_submitted. Each |
| 24 | - | // owner watchers only). Each one is delivered on the | 24 | + | // one is delivered on the "repo:<name>" topic of its repository and on |
| 25 | - | // "repo:<name>" topic of its repository and on "home", subject to the | 25 | + | // "home", subject to the visibility of the repository, except |
| 26 | - | // visibility of the repository. | 26 | + | // issue_submitted: it is delivered only on the "owner" topic, never on |
| 27 | + | // "home" or "repo:<name>", and carries no content (see IssueSubmitted). | |||
| 27 | // | 28 | // | ||
| 28 | // Servers with the feature "ci.runs" also publish run_updated and | 29 | // Servers with the feature "ci.runs" also publish run_updated and | ||
| 29 | // job_updated, only on the "repo:<name>" topic of their repository (never on | 30 | // job_updated, only on the "repo:<name>" topic of their repository (never on | ||
| @@ -58,9 +59,17 @@ service EventService { | |||||
| 58 | // WatchRequest selects the events to receive. | 59 | // WatchRequest selects the events to receive. | ||
| 59 | message WatchRequest { | 60 | message WatchRequest { | ||
| 60 | // Topics are the subjects to follow: "home" for every repository visible to | 61 | // Topics are the subjects to follow: "home" for every repository visible to | ||
| 61 | - | // the caller, or "repo:<name>" for one repository. Empty means "home". | 62 | + | // the caller, "repo:<name>" for one repository, or "owner". Empty means |
| 62 | - | // Unknown topics are invalid_argument. A "repo:<name>" topic for a repository | 63 | + | // "home". Unknown topics are invalid_argument. A "repo:<name>" topic for a |
| 63 | - | // that does not exist or is not visible is not_found. | 64 | + | // repository that does not exist or is not visible is not_found. |
| 65 | + | // | |||
| 66 | + | // The "owner" topic needs owner rights: root, an owner session, or a token | |||
| 67 | + | // that holds every scope with no repository or branch limit. Anyone else | |||
| 68 | + | // gets the same invalid_argument as for an unknown topic, so the topic is | |||
| 69 | + | // not revealed. It carries the merge request and issue events of every | |||
| 70 | + | // repository (merge_request_opened, merge_request_merged, | |||
| 71 | + | // merge_request_closed, comment_added, and the issue events), repo_updated | |||
| 72 | + | // and repo_deleted, plus issue_submitted, which is delivered nowhere else. | |||
| 64 | repeated string topics = 1; | 73 | repeated string topics = 1; | ||
| 65 | // ResumeAfter is the Event.id of the last event the client has seen. Empty | 74 | // ResumeAfter is the Event.id of the last event the client has seen. Empty | ||
| 66 | // to receive only new events. | 75 | // to receive only new events. | ||
| @@ -120,8 +129,8 @@ message Event { | |||||
| 120 | IssueReopened issue_reopened = 19; | 129 | IssueReopened issue_reopened = 19; | ||
| 121 | // IssueCommented: a comment was added to an issue. | 130 | // IssueCommented: a comment was added to an issue. | ||
| 122 | IssueCommented issue_commented = 20; | 131 | IssueCommented issue_commented = 20; | ||
| 123 | - | // IssueSubmitted: an issue is awaiting moderation. Delivered to owner | 132 | + | // IssueSubmitted: an issue is awaiting moderation. Delivered only on the |
| 124 | - | // watchers only. | 133 | + | // "owner" topic, never on "home" or "repo:<name>", and carries no content. |
| 125 | IssueSubmitted issue_submitted = 21; | 134 | IssueSubmitted issue_submitted = 21; | ||
| 126 | } | 135 | } | ||
| 127 | } | 136 | } | ||
| @@ -251,8 +260,8 @@ message IssueCommented { | |||||
| 251 | IssueComment comment = 3; | 260 | IssueComment comment = 3; | ||
| 252 | } | 261 | } | ||
| 253 | 262 | ||||
| 254 | - | // IssueSubmitted reports an issue awaiting moderation. It is delivered to | 263 | + | // IssueSubmitted reports an issue awaiting moderation. It is delivered only |
| 255 | - | // owner watchers only. It carries no title or body: content never travels in | 264 | + | // on the "owner" topic, never on "home" or "repo:<name>". It carries no title or body: content never travels in |
| 256 | // events, read it with IssueService.ListPendingIssues. | 265 | // events, read it with IssueService.ListPendingIssues. | ||
| 257 | message IssueSubmitted { | 266 | message IssueSubmitted { | ||
| 258 | // Repo is the name of the repository. | 267 | // Repo is the name of the repository. | ||
M proto/ermite/issue.proto +38 -4
| @@ -31,9 +31,12 @@ message Issue { | |||
| 31 | 31 | string body = 4; | |
| 32 | 32 | // State is where the issue is in its life. | |
| 33 | 33 | IssueState state = 5; | |
| 34 | - | // Author names who created the issue: the label of the token that was used, | |
| 35 | - | // "root" for the root token, or "owner" for a web session. It is | |
| 36 | - | // informational: labels are not unique and the token may be gone. | |
| 34 | + | // Author names who created the issue: the label of the token or session | |
| 35 | + | // that was used ("root" for the root token, "owner" for a web session), or, | |
| 36 | + | // for an issue approved from the public form, the unverified display name | |
| 37 | + | // the visitor typed (or "anonymous"). It is informational and never proves | |
| 38 | + | // identity: labels are not unique, the token may be gone, and a visitor can | |
| 39 | + | // type "owner". | |
| 37 | 40 | string author = 6; | |
| 38 | 41 | // CreatedAt is when the issue was created. | |
| 39 | 42 | google.protobuf.Timestamp created_at = 7; | |
| @@ -56,6 +59,36 @@ message IssueComment { | |||
| 56 | 59 | } | |
| 57 | 60 | ||
| 58 | 61 | // IssueService manages issues and their comments. | |
| 62 | + | // | |
| 63 | + | // Servers that implement this service list "issues" in | |
| 64 | + | // GetVersionResponse.features. Issues and merge requests of a repository share | |
| 65 | + | // one number sequence. When the issues mode of the repository is off, every | |
| 66 | + | // call answers not_found, as for a repository that does not exist. | |
| 67 | + | // | |
| 68 | + | // Reading (ListIssues, GetIssue) needs the same access as reading the | |
| 69 | + | // repository: nothing for a public one, the repo:read scope for a private one. | |
| 70 | + | // Writing (CreateIssue, AddIssueComment, CloseIssue, ReopenIssue) needs the | |
| 71 | + | // owner or a token with the issue:write scope on the repository; branch | |
| 72 | + | // prefixes do not restrict it. A caller without credentials gets | |
| 73 | + | // unauthenticated, one with an insufficient scope gets permission_denied, and | |
| 74 | + | // one that cannot read the repository gets not_found. | |
| 75 | + | // | |
| 76 | + | // Text fields are normalized to NFC and stripped of control and Unicode | |
| 77 | + | // bidirectional characters (line breaks and tabs stay in bodies and comments). | |
| 78 | + | // The title is one line of 1 to 120 code points; body and comment text are at | |
| 79 | + | // most 10,000 code points and a comment must not be blank. A text that breaks | |
| 80 | + | // a rule is rejected with invalid_argument. A comment on an issue that already | |
| 81 | + | // holds 1,000 comments gives resource_exhausted. Closing an issue that is not | |
| 82 | + | // open, or reopening one that is not closed, gives failed_precondition. | |
| 83 | + | // | |
| 84 | + | // The ordinary RPCs only return issues in state OPEN or CLOSED, and | |
| 85 | + | // ListIssues with state ISSUE_STATE_PENDING is invalid_argument. Submissions | |
| 86 | + | // from the public form wait as PendingIssue records, reachable only through | |
| 87 | + | // the three moderation RPCs (ListPendingIssues, ApproveIssue, RejectIssue). | |
| 88 | + | // These need owner rights (root, an owner session, or a token holding every | |
| 89 | + | // scope with no repository or branch limit); anyone else gets not_found, and | |
| 90 | + | // so does a malformed pending issue id. A malformed repository name is | |
| 91 | + | // invalid_argument whoever asks. | |
| 59 | 92 | service IssueService { | |
| 60 | 93 | // CreateIssue opens an issue. Needs the issue:write scope. | |
| 61 | 94 | rpc CreateIssue(CreateIssueRequest) returns (CreateIssueResponse); | |
| @@ -191,7 +224,8 @@ message PendingIssue { | |||
| 191 | 224 | // Body is the content. It may be empty. | |
| 192 | 225 | string body = 4; | |
| 193 | 226 | // Author is the optional display name given by the anonymous submitter. It | |
| 194 | - | // may be empty and is not verified. | |
| 227 | + | // may be empty or "anonymous", and is not verified: it never proves | |
| 228 | + | // identity (a visitor can type "owner"). It becomes Issue.author on approval. | |
| 195 | 229 | string author = 5; | |
| 196 | 230 | // CreatedAt is when the issue was submitted. | |
| 197 | 231 | google.protobuf.Timestamp created_at = 6; | |
| @@ -31,9 +31,12 @@ message Issue { | |||||
| 31 | string body = 4; | 31 | string body = 4; | ||
| 32 | // State is where the issue is in its life. | 32 | // State is where the issue is in its life. | ||
| 33 | IssueState state = 5; | 33 | IssueState state = 5; | ||
| 34 | - | // Author names who created the issue: the label of the token that was used, | 34 | + | // Author names who created the issue: the label of the token or session |
| 35 | - | // "root" for the root token, or "owner" for a web session. It is | 35 | + | // that was used ("root" for the root token, "owner" for a web session), or, |
| 36 | - | // informational: labels are not unique and the token may be gone. | 36 | + | // for an issue approved from the public form, the unverified display name |
| 37 | + | // the visitor typed (or "anonymous"). It is informational and never proves | |||
| 38 | + | // identity: labels are not unique, the token may be gone, and a visitor can | |||
| 39 | + | // type "owner". | |||
| 37 | string author = 6; | 40 | string author = 6; | ||
| 38 | // CreatedAt is when the issue was created. | 41 | // CreatedAt is when the issue was created. | ||
| 39 | google.protobuf.Timestamp created_at = 7; | 42 | google.protobuf.Timestamp created_at = 7; | ||
| @@ -56,6 +59,36 @@ message IssueComment { | |||||
| 56 | } | 59 | } | ||
| 57 | 60 | ||||
| 58 | // IssueService manages issues and their comments. | 61 | // IssueService manages issues and their comments. | ||
| 62 | + | // | |||
| 63 | + | // Servers that implement this service list "issues" in | |||
| 64 | + | // GetVersionResponse.features. Issues and merge requests of a repository share | |||
| 65 | + | // one number sequence. When the issues mode of the repository is off, every | |||
| 66 | + | // call answers not_found, as for a repository that does not exist. | |||
| 67 | + | // | |||
| 68 | + | // Reading (ListIssues, GetIssue) needs the same access as reading the | |||
| 69 | + | // repository: nothing for a public one, the repo:read scope for a private one. | |||
| 70 | + | // Writing (CreateIssue, AddIssueComment, CloseIssue, ReopenIssue) needs the | |||
| 71 | + | // owner or a token with the issue:write scope on the repository; branch | |||
| 72 | + | // prefixes do not restrict it. A caller without credentials gets | |||
| 73 | + | // unauthenticated, one with an insufficient scope gets permission_denied, and | |||
| 74 | + | // one that cannot read the repository gets not_found. | |||
| 75 | + | // | |||
| 76 | + | // Text fields are normalized to NFC and stripped of control and Unicode | |||
| 77 | + | // bidirectional characters (line breaks and tabs stay in bodies and comments). | |||
| 78 | + | // The title is one line of 1 to 120 code points; body and comment text are at | |||
| 79 | + | // most 10,000 code points and a comment must not be blank. A text that breaks | |||
| 80 | + | // a rule is rejected with invalid_argument. A comment on an issue that already | |||
| 81 | + | // holds 1,000 comments gives resource_exhausted. Closing an issue that is not | |||
| 82 | + | // open, or reopening one that is not closed, gives failed_precondition. | |||
| 83 | + | // | |||
| 84 | + | // The ordinary RPCs only return issues in state OPEN or CLOSED, and | |||
| 85 | + | // ListIssues with state ISSUE_STATE_PENDING is invalid_argument. Submissions | |||
| 86 | + | // from the public form wait as PendingIssue records, reachable only through | |||
| 87 | + | // the three moderation RPCs (ListPendingIssues, ApproveIssue, RejectIssue). | |||
| 88 | + | // These need owner rights (root, an owner session, or a token holding every | |||
| 89 | + | // scope with no repository or branch limit); anyone else gets not_found, and | |||
| 90 | + | // so does a malformed pending issue id. A malformed repository name is | |||
| 91 | + | // invalid_argument whoever asks. | |||
| 59 | service IssueService { | 92 | service IssueService { | ||
| 60 | // CreateIssue opens an issue. Needs the issue:write scope. | 93 | // CreateIssue opens an issue. Needs the issue:write scope. | ||
| 61 | rpc CreateIssue(CreateIssueRequest) returns (CreateIssueResponse); | 94 | rpc CreateIssue(CreateIssueRequest) returns (CreateIssueResponse); | ||
| @@ -191,7 +224,8 @@ message PendingIssue { | |||||
| 191 | // Body is the content. It may be empty. | 224 | // Body is the content. It may be empty. | ||
| 192 | string body = 4; | 225 | string body = 4; | ||
| 193 | // Author is the optional display name given by the anonymous submitter. It | 226 | // Author is the optional display name given by the anonymous submitter. It | ||
| 194 | - | // may be empty and is not verified. | 227 | + | // may be empty or "anonymous", and is not verified: it never proves |
| 228 | + | // identity (a visitor can type "owner"). It becomes Issue.author on approval. | |||
| 195 | string author = 5; | 229 | string author = 5; | ||
| 196 | // CreatedAt is when the issue was submitted. | 230 | // CreatedAt is when the issue was submitted. | ||
| 197 | google.protobuf.Timestamp created_at = 6; | 231 | google.protobuf.Timestamp created_at = 6; | ||
M proto/ermite/repo.proto +7 -2
| @@ -168,8 +168,9 @@ message UpdateRepoRequest { | |||
| 168 | 168 | // UpdateMask lists the fields to change: "description", "visibility", | |
| 169 | 169 | // "default_branch", "topics", "settings.allowed_merge_modes" and/or | |
| 170 | 170 | // "settings.failing_checks_block". "settings" stands for both merge settings | |
| 171 | - | // paths. The optional issues field changes the issue mode separately. | |
| 172 | - | // It must not be empty. Other paths are invalid_argument. | |
| 171 | + | // paths. It must not be empty unless issues is set. Other paths are | |
| 172 | + | // invalid_argument, including "settings.issues" and "issues": the issues | |
| 173 | + | // field is applied outside the mask. | |
| 173 | 174 | google.protobuf.FieldMask update_mask = 5; | |
| 174 | 175 | // Settings holds the new merge settings. Only the fields named in | |
| 175 | 176 | // update_mask are used. allowed_merge_modes must then not be empty nor | |
| @@ -181,6 +182,10 @@ message UpdateRepoRequest { | |||
| 181 | 182 | // if update_mask contains "topics"; an empty list then clears them. | |
| 182 | 183 | repeated string topics = 7; | |
| 183 | 184 | // Issues is the new issue participation mode. When absent it is unchanged. | |
| 185 | + | // It is applied whatever update_mask contains, and may be the only change | |
| 186 | + | // (an empty update_mask is then valid). When present it must not be | |
| 187 | + | // ISSUES_MODE_UNSPECIFIED: that is invalid_argument. Changing it needs the | |
| 188 | + | // repo:admin scope and a token with no branch limit, like any other field. | |
| 184 | 189 | optional IssuesMode issues = 8; | |
| 185 | 190 | } | |
| 186 | 191 | ||
| @@ -168,8 +168,9 @@ message UpdateRepoRequest { | |||||
| 168 | // UpdateMask lists the fields to change: "description", "visibility", | 168 | // UpdateMask lists the fields to change: "description", "visibility", | ||
| 169 | // "default_branch", "topics", "settings.allowed_merge_modes" and/or | 169 | // "default_branch", "topics", "settings.allowed_merge_modes" and/or | ||
| 170 | // "settings.failing_checks_block". "settings" stands for both merge settings | 170 | // "settings.failing_checks_block". "settings" stands for both merge settings | ||
| 171 | - | // paths. The optional issues field changes the issue mode separately. | 171 | + | // paths. It must not be empty unless issues is set. Other paths are |
| 172 | - | // It must not be empty. Other paths are invalid_argument. | 172 | + | // invalid_argument, including "settings.issues" and "issues": the issues |
| 173 | + | // field is applied outside the mask. | |||
| 173 | google.protobuf.FieldMask update_mask = 5; | 174 | google.protobuf.FieldMask update_mask = 5; | ||
| 174 | // Settings holds the new merge settings. Only the fields named in | 175 | // Settings holds the new merge settings. Only the fields named in | ||
| 175 | // update_mask are used. allowed_merge_modes must then not be empty nor | 176 | // update_mask are used. allowed_merge_modes must then not be empty nor | ||
| @@ -181,6 +182,10 @@ message UpdateRepoRequest { | |||||
| 181 | // if update_mask contains "topics"; an empty list then clears them. | 182 | // if update_mask contains "topics"; an empty list then clears them. | ||
| 182 | repeated string topics = 7; | 183 | repeated string topics = 7; | ||
| 183 | // Issues is the new issue participation mode. When absent it is unchanged. | 184 | // Issues is the new issue participation mode. When absent it is unchanged. | ||
| 185 | + | // It is applied whatever update_mask contains, and may be the only change | |||
| 186 | + | // (an empty update_mask is then valid). When present it must not be | |||
| 187 | + | // ISSUES_MODE_UNSPECIFIED: that is invalid_argument. Changing it needs the | |||
| 188 | + | // repo:admin scope and a token with no branch limit, like any other field. | |||
| 184 | optional IssuesMode issues = 8; | 189 | optional IssuesMode issues = 8; | ||
| 185 | } | 190 | } | ||
| 186 | 191 | ||||