detached b94fede

docs(ci): tighten the annotation, summary and action resolution contracts

Add GetStepSummariesResponse.truncated and the 20 summaries cap, document annotation replacement, per-field annotation limits, ListAnnotations attempts, resolved_sha and token scope, and the UploadSummary request size.
commit
b94fede8b9d3b7c980ac641364d295dc864a9430
author
brice · · 2026-10-13 09:17 UTC
parent
1541ed7
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
7 files · +139 -64
M gen/ermite/ermiteconnect/run.connect.go +12 -4
@@ -78,7 +78,9 @@ type RunServiceClient interface {
7878 GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
7979 // ListAnnotations lists the annotations of a run attempt, optionally of one
8080 // job, ordered by job (as in GetRun), step number, then the order in which
81- // the step produced them. Needs the same access as GetRun. Returns at most
81+ // the step produced them. For each job it gives the annotations of the
82+ // attempt in which that job executed (RunJob.attempt), which is lower than
83+ // the attempt of the run for a job kept by a re-run of failed jobs only. Needs the same access as GetRun. Returns at most
8284 // 1,000 annotations (truncated tells). A run without annotations gives an
8385 // empty list. Only servers with the feature "ci.annotations" implement it
8486 // (unimplemented otherwise). Fails with not_found if the run, the attempt or
@@ -86,7 +88,9 @@ type RunServiceClient interface {
8688 ListAnnotations(context.Context, *connect.Request[ermite.ListAnnotationsRequest]) (*connect.Response[ermite.ListAnnotationsResponse], error)
8789 // GetStepSummaries returns the job summaries (the Markdown written to
8890 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step
89- // number. Needs the same access as GetJob. Only servers with the feature
91+ // number. Like GitHub, it returns at most 20 summaries per job: the first 20
92+ // steps that have one, in step order; truncated says there are more. Needs
93+ // the same access as GetJob. Only servers with the feature
9094 // "ci.summaries" implement it (unimplemented otherwise). A job without
9195 // summaries gives an empty list. Fails with not_found if the job or the
9296 // attempt does not exist.
@@ -307,7 +311,9 @@ type RunServiceHandler interface {
307311 GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
308312 // ListAnnotations lists the annotations of a run attempt, optionally of one
309313 // job, ordered by job (as in GetRun), step number, then the order in which
310- // the step produced them. Needs the same access as GetRun. Returns at most
314+ // the step produced them. For each job it gives the annotations of the
315+ // attempt in which that job executed (RunJob.attempt), which is lower than
316+ // the attempt of the run for a job kept by a re-run of failed jobs only. Needs the same access as GetRun. Returns at most
311317 // 1,000 annotations (truncated tells). A run without annotations gives an
312318 // empty list. Only servers with the feature "ci.annotations" implement it
313319 // (unimplemented otherwise). Fails with not_found if the run, the attempt or
@@ -315,7 +321,9 @@ type RunServiceHandler interface {
315321 ListAnnotations(context.Context, *connect.Request[ermite.ListAnnotationsRequest]) (*connect.Response[ermite.ListAnnotationsResponse], error)
316322 // GetStepSummaries returns the job summaries (the Markdown written to
317323 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step
318- // number. Needs the same access as GetJob. Only servers with the feature
324+ // number. Like GitHub, it returns at most 20 summaries per job: the first 20
325+ // steps that have one, in step order; truncated says there are more. Needs
326+ // the same access as GetJob. Only servers with the feature
319327 // "ci.summaries" implement it (unimplemented otherwise). A job without
320328 // summaries gives an empty list. Fails with not_found if the job or the
321329 // attempt does not exist.
@@ -78,7 +78,9 @@ type RunServiceClient interface {
78 GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)78 GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
79 // ListAnnotations lists the annotations of a run attempt, optionally of one79 // ListAnnotations lists the annotations of a run attempt, optionally of one
80 // job, ordered by job (as in GetRun), step number, then the order in which80 // job, ordered by job (as in GetRun), step number, then the order in which
81- // the step produced them. Needs the same access as GetRun. Returns at most81+ // the step produced them. For each job it gives the annotations of the
82+ // attempt in which that job executed (RunJob.attempt), which is lower than
83+ // the attempt of the run for a job kept by a re-run of failed jobs only. Needs the same access as GetRun. Returns at most
82 // 1,000 annotations (truncated tells). A run without annotations gives an84 // 1,000 annotations (truncated tells). A run without annotations gives an
83 // empty list. Only servers with the feature "ci.annotations" implement it85 // empty list. Only servers with the feature "ci.annotations" implement it
84 // (unimplemented otherwise). Fails with not_found if the run, the attempt or86 // (unimplemented otherwise). Fails with not_found if the run, the attempt or
@@ -86,7 +88,9 @@ type RunServiceClient interface {
86 ListAnnotations(context.Context, *connect.Request[ermite.ListAnnotationsRequest]) (*connect.Response[ermite.ListAnnotationsResponse], error)88 ListAnnotations(context.Context, *connect.Request[ermite.ListAnnotationsRequest]) (*connect.Response[ermite.ListAnnotationsResponse], error)
87 // GetStepSummaries returns the job summaries (the Markdown written to89 // GetStepSummaries returns the job summaries (the Markdown written to
88 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step90 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step
89- // number. Needs the same access as GetJob. Only servers with the feature91+ // number. Like GitHub, it returns at most 20 summaries per job: the first 20
92+ // steps that have one, in step order; truncated says there are more. Needs
93+ // the same access as GetJob. Only servers with the feature
90 // "ci.summaries" implement it (unimplemented otherwise). A job without94 // "ci.summaries" implement it (unimplemented otherwise). A job without
91 // summaries gives an empty list. Fails with not_found if the job or the95 // summaries gives an empty list. Fails with not_found if the job or the
92 // attempt does not exist.96 // attempt does not exist.
@@ -307,7 +311,9 @@ type RunServiceHandler interface {
307 GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)311 GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
308 // ListAnnotations lists the annotations of a run attempt, optionally of one312 // ListAnnotations lists the annotations of a run attempt, optionally of one
309 // job, ordered by job (as in GetRun), step number, then the order in which313 // job, ordered by job (as in GetRun), step number, then the order in which
310- // the step produced them. Needs the same access as GetRun. Returns at most314+ // the step produced them. For each job it gives the annotations of the
315+ // attempt in which that job executed (RunJob.attempt), which is lower than
316+ // the attempt of the run for a job kept by a re-run of failed jobs only. Needs the same access as GetRun. Returns at most
311 // 1,000 annotations (truncated tells). A run without annotations gives an317 // 1,000 annotations (truncated tells). A run without annotations gives an
312 // empty list. Only servers with the feature "ci.annotations" implement it318 // empty list. Only servers with the feature "ci.annotations" implement it
313 // (unimplemented otherwise). Fails with not_found if the run, the attempt or319 // (unimplemented otherwise). Fails with not_found if the run, the attempt or
@@ -315,7 +321,9 @@ type RunServiceHandler interface {
315 ListAnnotations(context.Context, *connect.Request[ermite.ListAnnotationsRequest]) (*connect.Response[ermite.ListAnnotationsResponse], error)321 ListAnnotations(context.Context, *connect.Request[ermite.ListAnnotationsRequest]) (*connect.Response[ermite.ListAnnotationsResponse], error)
316 // GetStepSummaries returns the job summaries (the Markdown written to322 // GetStepSummaries returns the job summaries (the Markdown written to
317 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step323 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step
318- // number. Needs the same access as GetJob. Only servers with the feature324+ // number. Like GitHub, it returns at most 20 summaries per job: the first 20
325+ // steps that have one, in step order; truncated says there are more. Needs
326+ // the same access as GetJob. Only servers with the feature
319 // "ci.summaries" implement it (unimplemented otherwise). A job without327 // "ci.summaries" implement it (unimplemented otherwise). A job without
320 // summaries gives an empty list. Fails with not_found if the job or the328 // summaries gives an empty list. Fails with not_found if the job or the
321 // attempt does not exist.329 // attempt does not exist.
M gen/ermite/ermiteconnect/runner.connect.go +32 -14
@@ -115,7 +115,12 @@ type RunnerServiceClient interface {
115115 // step_number present in annotations, the annotations of that step stored
116116 // so far are replaced by the ones of the request, so a retry never
117117 // duplicates them. A step number absent from the request keeps its
118- // annotations.
118+ // annotations. The step of an annotation need not be in the steps of the
119+ // same call, and may already be COMPLETED: its annotations can still be
120+ // replaced (the rule that a COMPLETED step cannot change applies to
121+ // StepUpdate, not to annotations), until the job ends. A runner sends one
122+ // UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
123+ // so replacements apply in the order of the calls.
119124 //
120125 // Fails with invalid_argument if a rule is broken (including the annotation
121126 // limits), with failed_precondition if a step goes back (a COMPLETED step
@@ -225,7 +230,8 @@ type RunnerServiceClient interface {
225230 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
226231 // they never started and the job did not end by cancellation).
227232 //
228- // Annotations are applied as in UpdateJob, together with the conclusion.
233+ // Annotations are applied as in UpdateJob (replacement per step number, also
234+ // for COMPLETED steps and steps not in steps), together with the conclusion.
229235 // Summaries are not part of this call: a runner sends them with
230236 // UploadSummary before it.
231237 //
@@ -249,9 +255,10 @@ type RunnerServiceClient interface {
249255 // as is. Only a failure of the call itself (credential, ended job, bad
250256 // request, server error) is an RPC error; the runner then fails the job.
251257 //
252- // Resolution is stable for a run: the same ref resolves to the same sha for
253- // every job and every call of one attempt of the run. A re-run attempt
254- // resolves again.
258+ // Resolution is stable for a run attempt: the same ref resolves to the same
259+ // sha (ResolvedAction.resolved_sha) for every job and every call of that
260+ // attempt. A re-run attempt resolves again. Tokens are not shared: each
261+ // job gets its own (ResolvedAction.token).
255262 //
256263 // Fails with unauthenticated or permission_denied as the other job RPCs,
257264 // with failed_precondition and a JobEnded detail if the job has ended, and
@@ -266,8 +273,10 @@ type RunnerServiceClient interface {
266273 //
267274 // It is idempotent: the summary of a step is replaced by each call, so a
268275 // retry changes nothing, and an empty markdown removes the summary of the
269- // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8;
270- // clients set a message size limit above it.
276+ // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
277+ // server reads requests of up to 1 MiB plus the envelope (job id, step
278+ // number, framing), so a summary of exactly 1 MiB is accepted; clients set
279+ // their own message size limit above that.
271280 //
272281 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
273282 // and with failed_precondition and a JobEnded detail if the job has ended.
@@ -456,7 +465,12 @@ type RunnerServiceHandler interface {
456465 // step_number present in annotations, the annotations of that step stored
457466 // so far are replaced by the ones of the request, so a retry never
458467 // duplicates them. A step number absent from the request keeps its
459- // annotations.
468+ // annotations. The step of an annotation need not be in the steps of the
469+ // same call, and may already be COMPLETED: its annotations can still be
470+ // replaced (the rule that a COMPLETED step cannot change applies to
471+ // StepUpdate, not to annotations), until the job ends. A runner sends one
472+ // UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
473+ // so replacements apply in the order of the calls.
460474 //
461475 // Fails with invalid_argument if a rule is broken (including the annotation
462476 // limits), with failed_precondition if a step goes back (a COMPLETED step
@@ -566,7 +580,8 @@ type RunnerServiceHandler interface {
566580 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
567581 // they never started and the job did not end by cancellation).
568582 //
569- // Annotations are applied as in UpdateJob, together with the conclusion.
583+ // Annotations are applied as in UpdateJob (replacement per step number, also
584+ // for COMPLETED steps and steps not in steps), together with the conclusion.
570585 // Summaries are not part of this call: a runner sends them with
571586 // UploadSummary before it.
572587 //
@@ -590,9 +605,10 @@ type RunnerServiceHandler interface {
590605 // as is. Only a failure of the call itself (credential, ended job, bad
591606 // request, server error) is an RPC error; the runner then fails the job.
592607 //
593- // Resolution is stable for a run: the same ref resolves to the same sha for
594- // every job and every call of one attempt of the run. A re-run attempt
595- // resolves again.
608+ // Resolution is stable for a run attempt: the same ref resolves to the same
609+ // sha (ResolvedAction.resolved_sha) for every job and every call of that
610+ // attempt. A re-run attempt resolves again. Tokens are not shared: each
611+ // job gets its own (ResolvedAction.token).
596612 //
597613 // Fails with unauthenticated or permission_denied as the other job RPCs,
598614 // with failed_precondition and a JobEnded detail if the job has ended, and
@@ -607,8 +623,10 @@ type RunnerServiceHandler interface {
607623 //
608624 // It is idempotent: the summary of a step is replaced by each call, so a
609625 // retry changes nothing, and an empty markdown removes the summary of the
610- // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8;
611- // clients set a message size limit above it.
626+ // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
627+ // server reads requests of up to 1 MiB plus the envelope (job id, step
628+ // number, framing), so a summary of exactly 1 MiB is accepted; clients set
629+ // their own message size limit above that.
612630 //
613631 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
614632 // and with failed_precondition and a JobEnded detail if the job has ended.
@@ -115,7 +115,12 @@ type RunnerServiceClient interface {
115 // step_number present in annotations, the annotations of that step stored115 // step_number present in annotations, the annotations of that step stored
116 // so far are replaced by the ones of the request, so a retry never116 // so far are replaced by the ones of the request, so a retry never
117 // duplicates them. A step number absent from the request keeps its117 // duplicates them. A step number absent from the request keeps its
118- // annotations.118+ // annotations. The step of an annotation need not be in the steps of the
119+ // same call, and may already be COMPLETED: its annotations can still be
120+ // replaced (the rule that a COMPLETED step cannot change applies to
121+ // StepUpdate, not to annotations), until the job ends. A runner sends one
122+ // UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
123+ // so replacements apply in the order of the calls.
119 //124 //
120 // Fails with invalid_argument if a rule is broken (including the annotation125 // Fails with invalid_argument if a rule is broken (including the annotation
121 // limits), with failed_precondition if a step goes back (a COMPLETED step126 // limits), with failed_precondition if a step goes back (a COMPLETED step
@@ -225,7 +230,8 @@ type RunnerServiceClient interface {
225 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when230 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
226 // they never started and the job did not end by cancellation).231 // they never started and the job did not end by cancellation).
227 //232 //
228- // Annotations are applied as in UpdateJob, together with the conclusion.233+ // Annotations are applied as in UpdateJob (replacement per step number, also
234+ // for COMPLETED steps and steps not in steps), together with the conclusion.
229 // Summaries are not part of this call: a runner sends them with235 // Summaries are not part of this call: a runner sends them with
230 // UploadSummary before it.236 // UploadSummary before it.
231 //237 //
@@ -249,9 +255,10 @@ type RunnerServiceClient interface {
249 // as is. Only a failure of the call itself (credential, ended job, bad255 // as is. Only a failure of the call itself (credential, ended job, bad
250 // request, server error) is an RPC error; the runner then fails the job.256 // request, server error) is an RPC error; the runner then fails the job.
251 //257 //
252- // Resolution is stable for a run: the same ref resolves to the same sha for258+ // Resolution is stable for a run attempt: the same ref resolves to the same
253- // every job and every call of one attempt of the run. A re-run attempt259+ // sha (ResolvedAction.resolved_sha) for every job and every call of that
254- // resolves again.260+ // attempt. A re-run attempt resolves again. Tokens are not shared: each
261+ // job gets its own (ResolvedAction.token).
255 //262 //
256 // Fails with unauthenticated or permission_denied as the other job RPCs,263 // Fails with unauthenticated or permission_denied as the other job RPCs,
257 // with failed_precondition and a JobEnded detail if the job has ended, and264 // with failed_precondition and a JobEnded detail if the job has ended, and
@@ -266,8 +273,10 @@ type RunnerServiceClient interface {
266 //273 //
267 // It is idempotent: the summary of a step is replaced by each call, so a274 // It is idempotent: the summary of a step is replaced by each call, so a
268 // retry changes nothing, and an empty markdown removes the summary of the275 // retry changes nothing, and an empty markdown removes the summary of the
269- // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8;276+ // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
270- // clients set a message size limit above it.277+ // server reads requests of up to 1 MiB plus the envelope (job id, step
278+ // number, framing), so a summary of exactly 1 MiB is accepted; clients set
279+ // their own message size limit above that.
271 //280 //
272 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)281 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
273 // and with failed_precondition and a JobEnded detail if the job has ended.282 // and with failed_precondition and a JobEnded detail if the job has ended.
@@ -456,7 +465,12 @@ type RunnerServiceHandler interface {
456 // step_number present in annotations, the annotations of that step stored465 // step_number present in annotations, the annotations of that step stored
457 // so far are replaced by the ones of the request, so a retry never466 // so far are replaced by the ones of the request, so a retry never
458 // duplicates them. A step number absent from the request keeps its467 // duplicates them. A step number absent from the request keeps its
459- // annotations.468+ // annotations. The step of an annotation need not be in the steps of the
469+ // same call, and may already be COMPLETED: its annotations can still be
470+ // replaced (the rule that a COMPLETED step cannot change applies to
471+ // StepUpdate, not to annotations), until the job ends. A runner sends one
472+ // UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
473+ // so replacements apply in the order of the calls.
460 //474 //
461 // Fails with invalid_argument if a rule is broken (including the annotation475 // Fails with invalid_argument if a rule is broken (including the annotation
462 // limits), with failed_precondition if a step goes back (a COMPLETED step476 // limits), with failed_precondition if a step goes back (a COMPLETED step
@@ -566,7 +580,8 @@ type RunnerServiceHandler interface {
566 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when580 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
567 // they never started and the job did not end by cancellation).581 // they never started and the job did not end by cancellation).
568 //582 //
569- // Annotations are applied as in UpdateJob, together with the conclusion.583+ // Annotations are applied as in UpdateJob (replacement per step number, also
584+ // for COMPLETED steps and steps not in steps), together with the conclusion.
570 // Summaries are not part of this call: a runner sends them with585 // Summaries are not part of this call: a runner sends them with
571 // UploadSummary before it.586 // UploadSummary before it.
572 //587 //
@@ -590,9 +605,10 @@ type RunnerServiceHandler interface {
590 // as is. Only a failure of the call itself (credential, ended job, bad605 // as is. Only a failure of the call itself (credential, ended job, bad
591 // request, server error) is an RPC error; the runner then fails the job.606 // request, server error) is an RPC error; the runner then fails the job.
592 //607 //
593- // Resolution is stable for a run: the same ref resolves to the same sha for608+ // Resolution is stable for a run attempt: the same ref resolves to the same
594- // every job and every call of one attempt of the run. A re-run attempt609+ // sha (ResolvedAction.resolved_sha) for every job and every call of that
595- // resolves again.610+ // attempt. A re-run attempt resolves again. Tokens are not shared: each
611+ // job gets its own (ResolvedAction.token).
596 //612 //
597 // Fails with unauthenticated or permission_denied as the other job RPCs,613 // Fails with unauthenticated or permission_denied as the other job RPCs,
598 // with failed_precondition and a JobEnded detail if the job has ended, and614 // with failed_precondition and a JobEnded detail if the job has ended, and
@@ -607,8 +623,10 @@ type RunnerServiceHandler interface {
607 //623 //
608 // It is idempotent: the summary of a step is replaced by each call, so a624 // It is idempotent: the summary of a step is replaced by each call, so a
609 // retry changes nothing, and an empty markdown removes the summary of the625 // retry changes nothing, and an empty markdown removes the summary of the
610- // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8;626+ // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
611- // clients set a message size limit above it.627+ // server reads requests of up to 1 MiB plus the envelope (job id, step
628+ // number, framing), so a summary of exactly 1 MiB is accepted; clients set
629+ // their own message size limit above that.
612 //630 //
613 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)631 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
614 // and with failed_precondition and a JobEnded detail if the job has ended.632 // and with failed_precondition and a JobEnded detail if the job has ended.
M gen/ermite/run.pb.go +18 -5 generated file: collapsed
M gen/ermite/runner.pb.go +23 -14 generated file: collapsed
M gen_test.go +2 -2
@@ -389,7 +389,7 @@ func TestCI2FieldNumbers(t *testing.T) {
389389 {&ermite.ListAnnotationsResponse{}, map[string]int{"annotations": 1, "truncated": 2}},
390390 {&ermite.JobAnnotation{}, map[string]int{"job_id": 1, "annotation": 2}},
391391 {&ermite.GetStepSummariesRequest{}, map[string]int{"repo": 1, "job_id": 2, "attempt": 3}},
392- {&ermite.GetStepSummariesResponse{}, map[string]int{"summaries": 1}},
392+ {&ermite.GetStepSummariesResponse{}, map[string]int{"summaries": 1, "truncated": 2}},
393393 {&ermite.StepSummary{}, map[string]int{"step_number": 1, "markdown": 2}},
394394 }
395395 for _, tt := range tests {
@@ -433,7 +433,7 @@ func TestCI2RoundTrip(t *testing.T) {
433433 }},
434434 &ermite.UploadSummaryRequest{JobId: "j1", StepNumber: 2, Markdown: "# hi"},
435435 &ermite.ListAnnotationsResponse{Annotations: []*ermite.JobAnnotation{{JobId: "j1", Annotation: &ermite.Annotation{Message: "m"}}}, Truncated: true},
436- &ermite.GetStepSummariesResponse{Summaries: []*ermite.StepSummary{{StepNumber: 2, Markdown: "# hi"}}},
436+ &ermite.GetStepSummariesResponse{Summaries: []*ermite.StepSummary{{StepNumber: 2, Markdown: "# hi"}}, Truncated: true},
437437 &ermite.RunJob{Id: "j1", Steps: []*ermite.Step{{Number: 2, Outcome: ermite.CiConclusion_CI_CONCLUSION_FAILURE}}},
438438 }
439439 for _, in := range msgs {
@@ -389,7 +389,7 @@ func TestCI2FieldNumbers(t *testing.T) {
389 {&ermite.ListAnnotationsResponse{}, map[string]int{"annotations": 1, "truncated": 2}},389 {&ermite.ListAnnotationsResponse{}, map[string]int{"annotations": 1, "truncated": 2}},
390 {&ermite.JobAnnotation{}, map[string]int{"job_id": 1, "annotation": 2}},390 {&ermite.JobAnnotation{}, map[string]int{"job_id": 1, "annotation": 2}},
391 {&ermite.GetStepSummariesRequest{}, map[string]int{"repo": 1, "job_id": 2, "attempt": 3}},391 {&ermite.GetStepSummariesRequest{}, map[string]int{"repo": 1, "job_id": 2, "attempt": 3}},
392- {&ermite.GetStepSummariesResponse{}, map[string]int{"summaries": 1}},392+ {&ermite.GetStepSummariesResponse{}, map[string]int{"summaries": 1, "truncated": 2}},
393 {&ermite.StepSummary{}, map[string]int{"step_number": 1, "markdown": 2}},393 {&ermite.StepSummary{}, map[string]int{"step_number": 1, "markdown": 2}},
394 }394 }
395 for _, tt := range tests {395 for _, tt := range tests {
@@ -433,7 +433,7 @@ func TestCI2RoundTrip(t *testing.T) {
433 }},433 }},
434 &ermite.UploadSummaryRequest{JobId: "j1", StepNumber: 2, Markdown: "# hi"},434 &ermite.UploadSummaryRequest{JobId: "j1", StepNumber: 2, Markdown: "# hi"},
435 &ermite.ListAnnotationsResponse{Annotations: []*ermite.JobAnnotation{{JobId: "j1", Annotation: &ermite.Annotation{Message: "m"}}}, Truncated: true},435 &ermite.ListAnnotationsResponse{Annotations: []*ermite.JobAnnotation{{JobId: "j1", Annotation: &ermite.Annotation{Message: "m"}}}, Truncated: true},
436- &ermite.GetStepSummariesResponse{Summaries: []*ermite.StepSummary{{StepNumber: 2, Markdown: "# hi"}}},436+ &ermite.GetStepSummariesResponse{Summaries: []*ermite.StepSummary{{StepNumber: 2, Markdown: "# hi"}}, Truncated: true},
437 &ermite.RunJob{Id: "j1", Steps: []*ermite.Step{{Number: 2, Outcome: ermite.CiConclusion_CI_CONCLUSION_FAILURE}}},437 &ermite.RunJob{Id: "j1", Steps: []*ermite.Step{{Number: 2, Outcome: ermite.CiConclusion_CI_CONCLUSION_FAILURE}}},
438 }438 }
439 for _, in := range msgs {439 for _, in := range msgs {
M proto/ermite/run.proto +13 -4
@@ -283,7 +283,9 @@ service RunService {
283283 rpc GetJob(GetJobRequest) returns (GetJobResponse);
284284 // ListAnnotations lists the annotations of a run attempt, optionally of one
285285 // job, ordered by job (as in GetRun), step number, then the order in which
286- // the step produced them. Needs the same access as GetRun. Returns at most
286+ // the step produced them. For each job it gives the annotations of the
287+ // attempt in which that job executed (RunJob.attempt), which is lower than
288+ // the attempt of the run for a job kept by a re-run of failed jobs only. Needs the same access as GetRun. Returns at most
287289 // 1,000 annotations (truncated tells). A run without annotations gives an
288290 // empty list. Only servers with the feature "ci.annotations" implement it
289291 // (unimplemented otherwise). Fails with not_found if the run, the attempt or
@@ -291,7 +293,9 @@ service RunService {
291293 rpc ListAnnotations(ListAnnotationsRequest) returns (ListAnnotationsResponse);
292294 // GetStepSummaries returns the job summaries (the Markdown written to
293295 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step
294- // number. Needs the same access as GetJob. Only servers with the feature
296+ // number. Like GitHub, it returns at most 20 summaries per job: the first 20
297+ // steps that have one, in step order; truncated says there are more. Needs
298+ // the same access as GetJob. Only servers with the feature
295299 // "ci.summaries" implement it (unimplemented otherwise). A job without
296300 // summaries gives an empty list. Fails with not_found if the job or the
297301 // attempt does not exist.
@@ -422,7 +426,9 @@ message ListAnnotationsRequest {
422426 string repo = 1;
423427 // RunId is the Run.id.
424428 int64 run_id = 2;
425- // Attempt is the attempt to read. 0 means the latest.
429+ // Attempt is the attempt to read. 0 means the latest, and then each job
430+ // contributes the annotations of its own RunJob.attempt (not necessarily the
431+ // latest attempt of the run, see ListAnnotations).
426432 int32 attempt = 3;
427433 // JobId restricts the result to one job (RunJob.id). Empty means every job
428434 // of the run.
@@ -459,8 +465,11 @@ message GetStepSummariesRequest {
459465 // GetStepSummariesResponse holds the summaries of a job.
460466 message GetStepSummariesResponse {
461467 // Summaries are the summaries of the steps that have one, ordered by step
462- // number.
468+ // number: at most 20, the first 20 steps having one.
463469 repeated StepSummary summaries = 1;
470+ // Truncated is true when the job has more than 20 steps with a summary and
471+ // summaries holds only the first 20.
472+ bool truncated = 2;
464473 }
465474
466475 // StepSummary is the job summary of one step.
@@ -283,7 +283,9 @@ service RunService {
283 rpc GetJob(GetJobRequest) returns (GetJobResponse);283 rpc GetJob(GetJobRequest) returns (GetJobResponse);
284 // ListAnnotations lists the annotations of a run attempt, optionally of one284 // ListAnnotations lists the annotations of a run attempt, optionally of one
285 // job, ordered by job (as in GetRun), step number, then the order in which285 // job, ordered by job (as in GetRun), step number, then the order in which
286- // the step produced them. Needs the same access as GetRun. Returns at most286+ // the step produced them. For each job it gives the annotations of the
287+ // attempt in which that job executed (RunJob.attempt), which is lower than
288+ // the attempt of the run for a job kept by a re-run of failed jobs only. Needs the same access as GetRun. Returns at most
287 // 1,000 annotations (truncated tells). A run without annotations gives an289 // 1,000 annotations (truncated tells). A run without annotations gives an
288 // empty list. Only servers with the feature "ci.annotations" implement it290 // empty list. Only servers with the feature "ci.annotations" implement it
289 // (unimplemented otherwise). Fails with not_found if the run, the attempt or291 // (unimplemented otherwise). Fails with not_found if the run, the attempt or
@@ -291,7 +293,9 @@ service RunService {
291 rpc ListAnnotations(ListAnnotationsRequest) returns (ListAnnotationsResponse);293 rpc ListAnnotations(ListAnnotationsRequest) returns (ListAnnotationsResponse);
292 // GetStepSummaries returns the job summaries (the Markdown written to294 // GetStepSummaries returns the job summaries (the Markdown written to
293 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step295 // GITHUB_STEP_SUMMARY) of the steps of a job that have one, ordered by step
294- // number. Needs the same access as GetJob. Only servers with the feature296+ // number. Like GitHub, it returns at most 20 summaries per job: the first 20
297+ // steps that have one, in step order; truncated says there are more. Needs
298+ // the same access as GetJob. Only servers with the feature
295 // "ci.summaries" implement it (unimplemented otherwise). A job without299 // "ci.summaries" implement it (unimplemented otherwise). A job without
296 // summaries gives an empty list. Fails with not_found if the job or the300 // summaries gives an empty list. Fails with not_found if the job or the
297 // attempt does not exist.301 // attempt does not exist.
@@ -422,7 +426,9 @@ message ListAnnotationsRequest {
422 string repo = 1;426 string repo = 1;
423 // RunId is the Run.id.427 // RunId is the Run.id.
424 int64 run_id = 2;428 int64 run_id = 2;
425- // Attempt is the attempt to read. 0 means the latest.429+ // Attempt is the attempt to read. 0 means the latest, and then each job
430+ // contributes the annotations of its own RunJob.attempt (not necessarily the
431+ // latest attempt of the run, see ListAnnotations).
426 int32 attempt = 3;432 int32 attempt = 3;
427 // JobId restricts the result to one job (RunJob.id). Empty means every job433 // JobId restricts the result to one job (RunJob.id). Empty means every job
428 // of the run.434 // of the run.
@@ -459,8 +465,11 @@ message GetStepSummariesRequest {
459 // GetStepSummariesResponse holds the summaries of a job.465 // GetStepSummariesResponse holds the summaries of a job.
460 message GetStepSummariesResponse {466 message GetStepSummariesResponse {
461 // Summaries are the summaries of the steps that have one, ordered by step467 // Summaries are the summaries of the steps that have one, ordered by step
462- // number.468+ // number: at most 20, the first 20 steps having one.
463 repeated StepSummary summaries = 1;469 repeated StepSummary summaries = 1;
470+ // Truncated is true when the job has more than 20 steps with a summary and
471+ // summaries holds only the first 20.
472+ bool truncated = 2;
464 }473 }
465 474
466 // StepSummary is the job summary of one step.475 // StepSummary is the job summary of one step.
M proto/ermite/runner.proto +39 -21
@@ -229,7 +229,12 @@ service RunnerService {
229229 // step_number present in annotations, the annotations of that step stored
230230 // so far are replaced by the ones of the request, so a retry never
231231 // duplicates them. A step number absent from the request keeps its
232- // annotations.
232+ // annotations. The step of an annotation need not be in the steps of the
233+ // same call, and may already be COMPLETED: its annotations can still be
234+ // replaced (the rule that a COMPLETED step cannot change applies to
235+ // StepUpdate, not to annotations), until the job ends. A runner sends one
236+ // UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
237+ // so replacements apply in the order of the calls.
233238 //
234239 // Fails with invalid_argument if a rule is broken (including the annotation
235240 // limits), with failed_precondition if a step goes back (a COMPLETED step
@@ -339,7 +344,8 @@ service RunnerService {
339344 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
340345 // they never started and the job did not end by cancellation).
341346 //
342- // Annotations are applied as in UpdateJob, together with the conclusion.
347+ // Annotations are applied as in UpdateJob (replacement per step number, also
348+ // for COMPLETED steps and steps not in steps), together with the conclusion.
343349 // Summaries are not part of this call: a runner sends them with
344350 // UploadSummary before it.
345351 //
@@ -363,9 +369,10 @@ service RunnerService {
363369 // as is. Only a failure of the call itself (credential, ended job, bad
364370 // request, server error) is an RPC error; the runner then fails the job.
365371 //
366- // Resolution is stable for a run: the same ref resolves to the same sha for
367- // every job and every call of one attempt of the run. A re-run attempt
368- // resolves again.
372+ // Resolution is stable for a run attempt: the same ref resolves to the same
373+ // sha (ResolvedAction.resolved_sha) for every job and every call of that
374+ // attempt. A re-run attempt resolves again. Tokens are not shared: each
375+ // job gets its own (ResolvedAction.token).
369376 //
370377 // Fails with unauthenticated or permission_denied as the other job RPCs,
371378 // with failed_precondition and a JobEnded detail if the job has ended, and
@@ -380,8 +387,10 @@ service RunnerService {
380387 //
381388 // It is idempotent: the summary of a step is replaced by each call, so a
382389 // retry changes nothing, and an empty markdown removes the summary of the
383- // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8;
384- // clients set a message size limit above it.
390+ // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
391+ // server reads requests of up to 1 MiB plus the envelope (job id, step
392+ // number, framing), so a summary of exactly 1 MiB is accepted; clients set
393+ // their own message size limit above that.
385394 //
386395 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
387396 // and with failed_precondition and a JobEnded detail if the job has ended.
@@ -548,8 +557,9 @@ message UpdateJobRequest {
548557 // Annotations are the annotations of the steps that the call names: for
549558 // each step_number present, all the annotations of that step known so far,
550559 // not only the new ones (see the rules on UpdateJob). At most 10 per level
551- // and per step; more is invalid_argument. The server ignores them without
552- // the feature "ci.annotations".
560+ // and per step, and each one within the limits of Annotation; otherwise the
561+ // whole call is invalid_argument. The server ignores them without the
562+ // feature "ci.annotations".
553563 repeated Annotation annotations = 3;
554564 }
555565
@@ -741,19 +751,22 @@ message CancelRequest {
741751 // produces them from the workflow commands ::notice, ::warning and ::error and
742752 // from problem matchers, after masking.
743753 //
744-// The runner sends at most 10 annotations of each level per step (the ones
745-// beyond only reach the log) and the server rejects more.
754+// Limits, checked by the server: at most 10 annotations of each level per step
755+// (the runner sends no more: the ones beyond only reach the log, as with
756+// GitHub). A call that breaks a limit below is rejected whole with
757+// invalid_argument. Lengths count characters (Unicode code points), not bytes.
746758 message Annotation {
747759 // Level is the severity. Required.
748760 AnnotationLevel level = 1;
749- // Title is the optional title. Empty when not given.
761+ // Title is the optional title, at most 255 characters. Empty when not given.
750762 string title = 2;
751- // Message is the text, at most 4,096 characters (the runner cuts longer
752- // ones). Required, not empty.
763+ // Message is the text. Required, not empty, at most 4,096 characters; a
764+ // longer one is invalid_argument. The runner never sends a longer one: like
765+ // GitHub, it only logs such an annotation.
753766 string message = 3;
754767 // File is the path the annotation is about, relative to the repository when
755- // the runner can tell, else as given. Empty when the annotation is not about
756- // a file.
768+ // the runner can tell, else as given. At most 1,024 characters. Empty when
769+ // the annotation is not about a file.
757770 string file = 4;
758771 // StartLine is the first line, counted from 1. 0 when not given.
759772 int32 start_line = 5;
@@ -799,13 +812,17 @@ message ResolvedAction {
799812 // NameWithOwner is the repository of the action, "owner/repo", with the
800813 // case of the repository as the source gives it. Empty when error is set.
801814 string name_with_owner = 2;
802- // Path is the sub-path of the action inside the repository, without leading
803- // or trailing slash: "path/to/action" for "owner/repo/path/to/action@ref".
804- // Empty for an action at the root of the repository.
815+ // Path is the sub-path of the action inside the repository, a clean
816+ // relative path: "path/to/action" for "owner/repo/path/to/action@ref", with
817+ // "/" separators, no leading or trailing slash, no empty, "." or ".."
818+ // element, never absolute. Empty for an action at the root of the
819+ // repository.
805820 string path = 3;
806821 // ResolvedSha is the full id of the commit the ref points to, 40 lowercase
807822 // hexadecimal characters for SHA-1 (the commit that an annotated tag points
808- // to, not the tag object). Empty when error is set.
823+ // to, not the tag object). Stable for a run attempt: every job and every
824+ // call of the attempt gets the same value for the same ref. Empty when error
825+ // is set.
809826 string resolved_sha = 4;
810827 // TarballUrl is where to download the archive of the repository at
811828 // resolved_sha, a gzip-compressed tar with one top-level directory. The
@@ -816,7 +833,8 @@ message ResolvedAction {
816833 // For ACTION_SOURCE_ERMITE it is a read token for that one repository,
817834 // valid until the job ends, so a job can use an action of another private
818835 // repository of the server that its own repository token cannot read. It is
819- // a secret: the runner masks it, and neither the runner nor the server logs
836+ // issued per job and never shared across jobs (two jobs resolving the same
837+ // action get different tokens). It is a secret: the runner masks it, and neither the runner nor the server logs
820838 // it.
821839 string token = 6;
822840 // Source is where the action was found. UNSPECIFIED when error is set.
@@ -229,7 +229,12 @@ service RunnerService {
229 // step_number present in annotations, the annotations of that step stored229 // step_number present in annotations, the annotations of that step stored
230 // so far are replaced by the ones of the request, so a retry never230 // so far are replaced by the ones of the request, so a retry never
231 // duplicates them. A step number absent from the request keeps its231 // duplicates them. A step number absent from the request keeps its
232- // annotations.232+ // annotations. The step of an annotation need not be in the steps of the
233+ // same call, and may already be COMPLETED: its annotations can still be
234+ // replaced (the rule that a COMPLETED step cannot change applies to
235+ // StepUpdate, not to annotations), until the job ends. A runner sends one
236+ // UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
237+ // so replacements apply in the order of the calls.
233 //238 //
234 // Fails with invalid_argument if a rule is broken (including the annotation239 // Fails with invalid_argument if a rule is broken (including the annotation
235 // limits), with failed_precondition if a step goes back (a COMPLETED step240 // limits), with failed_precondition if a step goes back (a COMPLETED step
@@ -339,7 +344,8 @@ service RunnerService {
339 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when344 // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
340 // they never started and the job did not end by cancellation).345 // they never started and the job did not end by cancellation).
341 //346 //
342- // Annotations are applied as in UpdateJob, together with the conclusion.347+ // Annotations are applied as in UpdateJob (replacement per step number, also
348+ // for COMPLETED steps and steps not in steps), together with the conclusion.
343 // Summaries are not part of this call: a runner sends them with349 // Summaries are not part of this call: a runner sends them with
344 // UploadSummary before it.350 // UploadSummary before it.
345 //351 //
@@ -363,9 +369,10 @@ service RunnerService {
363 // as is. Only a failure of the call itself (credential, ended job, bad369 // as is. Only a failure of the call itself (credential, ended job, bad
364 // request, server error) is an RPC error; the runner then fails the job.370 // request, server error) is an RPC error; the runner then fails the job.
365 //371 //
366- // Resolution is stable for a run: the same ref resolves to the same sha for372+ // Resolution is stable for a run attempt: the same ref resolves to the same
367- // every job and every call of one attempt of the run. A re-run attempt373+ // sha (ResolvedAction.resolved_sha) for every job and every call of that
368- // resolves again.374+ // attempt. A re-run attempt resolves again. Tokens are not shared: each
375+ // job gets its own (ResolvedAction.token).
369 //376 //
370 // Fails with unauthenticated or permission_denied as the other job RPCs,377 // Fails with unauthenticated or permission_denied as the other job RPCs,
371 // with failed_precondition and a JobEnded detail if the job has ended, and378 // with failed_precondition and a JobEnded detail if the job has ended, and
@@ -380,8 +387,10 @@ service RunnerService {
380 //387 //
381 // It is idempotent: the summary of a step is replaced by each call, so a388 // It is idempotent: the summary of a step is replaced by each call, so a
382 // retry changes nothing, and an empty markdown removes the summary of the389 // retry changes nothing, and an empty markdown removes the summary of the
383- // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8;390+ // step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
384- // clients set a message size limit above it.391+ // server reads requests of up to 1 MiB plus the envelope (job id, step
392+ // number, framing), so a summary of exactly 1 MiB is accepted; clients set
393+ // their own message size limit above that.
385 //394 //
386 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)395 // Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
387 // and with failed_precondition and a JobEnded detail if the job has ended.396 // and with failed_precondition and a JobEnded detail if the job has ended.
@@ -548,8 +557,9 @@ message UpdateJobRequest {
548 // Annotations are the annotations of the steps that the call names: for557 // Annotations are the annotations of the steps that the call names: for
549 // each step_number present, all the annotations of that step known so far,558 // each step_number present, all the annotations of that step known so far,
550 // not only the new ones (see the rules on UpdateJob). At most 10 per level559 // not only the new ones (see the rules on UpdateJob). At most 10 per level
551- // and per step; more is invalid_argument. The server ignores them without560+ // and per step, and each one within the limits of Annotation; otherwise the
552- // the feature "ci.annotations".561+ // whole call is invalid_argument. The server ignores them without the
562+ // feature "ci.annotations".
553 repeated Annotation annotations = 3;563 repeated Annotation annotations = 3;
554 }564 }
555 565
@@ -741,19 +751,22 @@ message CancelRequest {
741 // produces them from the workflow commands ::notice, ::warning and ::error and751 // produces them from the workflow commands ::notice, ::warning and ::error and
742 // from problem matchers, after masking.752 // from problem matchers, after masking.
743 //753 //
744-// The runner sends at most 10 annotations of each level per step (the ones754+// Limits, checked by the server: at most 10 annotations of each level per step
745-// beyond only reach the log) and the server rejects more.755+// (the runner sends no more: the ones beyond only reach the log, as with
756+// GitHub). A call that breaks a limit below is rejected whole with
757+// invalid_argument. Lengths count characters (Unicode code points), not bytes.
746 message Annotation {758 message Annotation {
747 // Level is the severity. Required.759 // Level is the severity. Required.
748 AnnotationLevel level = 1;760 AnnotationLevel level = 1;
749- // Title is the optional title. Empty when not given.761+ // Title is the optional title, at most 255 characters. Empty when not given.
750 string title = 2;762 string title = 2;
751- // Message is the text, at most 4,096 characters (the runner cuts longer763+ // Message is the text. Required, not empty, at most 4,096 characters; a
752- // ones). Required, not empty.764+ // longer one is invalid_argument. The runner never sends a longer one: like
765+ // GitHub, it only logs such an annotation.
753 string message = 3;766 string message = 3;
754 // File is the path the annotation is about, relative to the repository when767 // File is the path the annotation is about, relative to the repository when
755- // the runner can tell, else as given. Empty when the annotation is not about768+ // the runner can tell, else as given. At most 1,024 characters. Empty when
756- // a file.769+ // the annotation is not about a file.
757 string file = 4;770 string file = 4;
758 // StartLine is the first line, counted from 1. 0 when not given.771 // StartLine is the first line, counted from 1. 0 when not given.
759 int32 start_line = 5;772 int32 start_line = 5;
@@ -799,13 +812,17 @@ message ResolvedAction {
799 // NameWithOwner is the repository of the action, "owner/repo", with the812 // NameWithOwner is the repository of the action, "owner/repo", with the
800 // case of the repository as the source gives it. Empty when error is set.813 // case of the repository as the source gives it. Empty when error is set.
801 string name_with_owner = 2;814 string name_with_owner = 2;
802- // Path is the sub-path of the action inside the repository, without leading815+ // Path is the sub-path of the action inside the repository, a clean
803- // or trailing slash: "path/to/action" for "owner/repo/path/to/action@ref".816+ // relative path: "path/to/action" for "owner/repo/path/to/action@ref", with
804- // Empty for an action at the root of the repository.817+ // "/" separators, no leading or trailing slash, no empty, "." or ".."
818+ // element, never absolute. Empty for an action at the root of the
819+ // repository.
805 string path = 3;820 string path = 3;
806 // ResolvedSha is the full id of the commit the ref points to, 40 lowercase821 // ResolvedSha is the full id of the commit the ref points to, 40 lowercase
807 // hexadecimal characters for SHA-1 (the commit that an annotated tag points822 // hexadecimal characters for SHA-1 (the commit that an annotated tag points
808- // to, not the tag object). Empty when error is set.823+ // to, not the tag object). Stable for a run attempt: every job and every
824+ // call of the attempt gets the same value for the same ref. Empty when error
825+ // is set.
809 string resolved_sha = 4;826 string resolved_sha = 4;
810 // TarballUrl is where to download the archive of the repository at827 // TarballUrl is where to download the archive of the repository at
811 // resolved_sha, a gzip-compressed tar with one top-level directory. The828 // resolved_sha, a gzip-compressed tar with one top-level directory. The
@@ -816,7 +833,8 @@ message ResolvedAction {
816 // For ACTION_SOURCE_ERMITE it is a read token for that one repository,833 // For ACTION_SOURCE_ERMITE it is a read token for that one repository,
817 // valid until the job ends, so a job can use an action of another private834 // valid until the job ends, so a job can use an action of another private
818 // repository of the server that its own repository token cannot read. It is835 // repository of the server that its own repository token cannot read. It is
819- // a secret: the runner masks it, and neither the runner nor the server logs836+ // issued per job and never shared across jobs (two jobs resolving the same
837+ // action get different tokens). It is a secret: the runner masks it, and neither the runner nor the server logs
820 // it.838 // it.
821 string token = 6;839 string token = 6;
822 // Source is where the action was found. UNSPECIFIED when error is set.840 // Source is where the action was found. UNSPECIFIED when error is set.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 18

↑↓ moveenter openesc close