From 694cf624695ae223e23e0a5e8f1d2dd5cdc1cf30 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Fri, 20 Mar 2026 14:26:56 +0530 Subject: [PATCH 1/8] TEP-0166: Task Notices and Warnings Add structured notices to TaskRun and PipelineRun status, enabling steps that exit successfully to emit warnings, informational messages, and non-fatal errors. This addresses a gap where CI/CD platforms built on Tekton (Konflux, PAC) cannot surface task warnings as VCS annotations because Tekton's execution model is binary (pass/fail). Co-Authored-By: Andrew Thorp Co-Authored-By: Claude Opus 4.6 (1M context) --- teps/0166-task-notices-and-warnings.md | 637 +++++++++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 teps/0166-task-notices-and-warnings.md diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md new file mode 100644 index 000000000..85c48f2c1 --- /dev/null +++ b/teps/0166-task-notices-and-warnings.md @@ -0,0 +1,637 @@ +--- +status: proposed +title: Task Notices and Warnings +creation-date: '2026-03-20' +last-updated: '2026-03-20' +authors: +- '@waveywaves' +- '@athorp96' +--- + +# TEP-0166: Task Notices and Warnings + + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Use Cases](#use-cases) + - [Linter Warnings on Successful Builds](#linter-warnings-on-successful-builds) + - [Security Scanner Informational Findings](#security-scanner-informational-findings) + - [Deprecation Notices](#deprecation-notices) + - [Test Coverage Regression](#test-coverage-regression) + - [Requirements](#requirements) +- [Proposal](#proposal) + - [Overview](#overview) + - [Notice Type Definition](#notice-type-definition) + - [Emitting Notices from Steps](#emitting-notices-from-steps) + - [Notice Aggregation in PipelineRun Status](#notice-aggregation-in-pipelinerun-status) + - [Notes and Caveats](#notes-and-caveats) +- [Design Details](#design-details) + - [Entrypoint Collection](#entrypoint-collection) + - [Reconciler Processing](#reconciler-processing) + - [Size Limits and Termination Message Budget](#size-limits-and-termination-message-budget) + - [Notice File Format](#notice-file-format) +- [Design Evaluation](#design-evaluation) + - [Reusability](#reusability) + - [Simplicity](#simplicity) + - [Flexibility](#flexibility) + - [Conformance](#conformance) + - [User Experience](#user-experience) + - [Performance](#performance) + - [Risks and Mitigations](#risks-and-mitigations) + - [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + - [Results with Reserved Prefix](#results-with-reserved-prefix) + - [Kubernetes Events](#kubernetes-events) + - [Annotations on TaskRun](#annotations-on-taskrun) + - [Condition Message Encoding](#condition-message-encoding) +- [Implementation Plan](#implementation-plan) + - [Test Plan](#test-plan) + - [Infrastructure Needed](#infrastructure-needed) + - [Upgrade and Migration Strategy](#upgrade-and-migration-strategy) + - [Implementation Pull Requests](#implementation-pull-requests) +- [References](#references) + + +## Summary + +This TEP proposes adding structured **notices** to TaskRun and PipelineRun +status, enabling steps that exit successfully (exit code 0) to emit warnings, +informational messages, and non-fatal errors that are surfaced in the run's +status. + +Today, Tekton's execution model is binary: a step either succeeds or fails. +There is no structured mechanism for a successful step to communicate +"I succeeded, but here are things you should know." This gap prevents CI/CD +systems built on Tekton (such as Konflux, Pipelines as Code) from surfacing +warnings as GitHub Check Run annotations, PR comments, or dashboard alerts. + +This TEP introduces: + +- A `Notice` type with `level`, `message`, `step`, and optional source + location fields (`file`, `startLine`, `endLine`) +- A `notices` field on `TaskRunStatusFields` for task-level notices +- A `notices` field on `StepState` for per-step notices +- A `/tekton/notices/` volume mount where steps write notice files +- Entrypoint collection of notices alongside results +- PipelineRun aggregation of notices from child TaskRuns + +**Example TaskRun status with notices:** + +```yaml +status: + conditions: + - type: Succeeded + status: "True" + reason: Succeeded + steps: + - name: lint + terminated: + exitCode: 0 + notices: + - level: warning + message: "variable 'x' is unused" + file: "src/main.go" + startLine: 42 + - level: warning + message: "function 'oldAPI' is deprecated, use 'newAPI'" + file: "src/handler.go" + startLine: 15 + notices: + - level: warning + message: "variable 'x' is unused" + step: lint + file: "src/main.go" + startLine: 42 + - level: warning + message: "function 'oldAPI' is deprecated, use 'newAPI'" + step: lint + file: "src/handler.go" + startLine: 15 +``` + +## Motivation + +GitHub Actions provides `::warning::`, `::notice::`, and `::error::` +[workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message) +that surface as inline annotations on PR diffs and in the Checks summary. +GitLab CI supports +[code quality reports](https://docs.gitlab.com/ee/ci/testing/code_quality.html) +that annotate merge requests with findings. These mechanisms are essential +for CI/CD systems to communicate nuanced feedback beyond pass/fail. + +Tekton has no equivalent. A linter that finds low-severity issues, a security +scanner that finds informational CVEs, a build that uses a deprecated API, +or a test suite that detects coverage regression — all exit 0 but have +warnings worth surfacing. Today, these warnings are buried in container logs +and invisible to VCS integrations. + +Downstream platforms built on Tekton are directly impacted: + +- **Konflux** ([KONFLUX-8688](https://issues.redhat.com/browse/KONFLUX-8688)): + Tasks that exit successfully but emit warnings need those warnings reflected + in VCS comments and checks. +- **Pipelines as Code**: Cannot map task warnings to GitHub Check Run + annotations because TaskRun status has no structured warning data. +- **Tekton Dashboard**: Cannot display a "warnings" section for successful + TaskRuns because no such data exists in the API. + +### Goals + +- Steps can emit structured notices (warnings, info, non-fatal errors) that + persist in TaskRun status after successful completion. +- Notices are available at both the step level (`stepState.notices`) and + the task level (`taskRunStatus.notices`) for flexible consumption. +- The mechanism follows existing Tekton patterns (similar to Results via + `/tekton/results/`). +- Downstream systems (PAC, Konflux, Dashboard, `tkn` CLI) can consume + notices from the TaskRun status API without parsing logs. +- PipelineRun status aggregates notices from child TaskRuns. + +### Non-Goals + +- **Log parsing**: This TEP does not propose parsing step container logs + for `::warning::` syntax. Steps must explicitly write to `/tekton/notices/`. +- **Failing on warnings**: Notices do not affect the Succeeded condition. + A TaskRun with notices and exit code 0 is still Succeeded. Policy engines + (Kyverno, OPA) or downstream systems can implement fail-on-warning logic + externally. +- **Inline code annotations in Tekton Dashboard**: While the data model + supports file/line information, rendering inline annotations is a + Dashboard concern outside this TEP's scope. +- **Notice-based routing in Pipelines**: Using notices to conditionally + skip or trigger downstream tasks is deferred to future work. + +### Use Cases + +#### Linter Warnings on Successful Builds + +As a **Task author**, I want my linter step to report specific warnings +(unused variables, style violations) that appear as annotations on the +pull request, without failing the build. + +Today, the linter exits 0 and warnings are only visible in container logs. +With notices, the linter writes structured warnings to `/tekton/notices/` +and they appear in `taskrun.status.notices`. PAC reads these and creates +GitHub Check Run annotations with file and line information. + +#### Security Scanner Informational Findings + +As a **platform engineer**, I want security scan steps that find low or +informational CVEs to surface those findings in the TaskRun status, so +our security dashboard can aggregate them without treating the build as +failed. + +#### Deprecation Notices + +As a **Task author**, I want to emit a warning when my task detects that +the user is relying on a deprecated API, configuration, or dependency, +so the warning appears in their PR feedback. + +#### Test Coverage Regression + +As a **CI administrator**, I want test steps to report when coverage drops +below a threshold as a warning (not a failure), so teams are informed +but not blocked. + +### Requirements + +- Notices must be structured (not free-form log text) to enable programmatic + consumption. +- The mechanism must not affect the TaskRun's Succeeded condition — a + TaskRun with notices is still Succeeded if all steps exit 0. +- Notices must persist in TaskRun status (not ephemeral like Events). +- The mechanism must work within the existing termination message size + constraints or use the sidecar-log approach (TEP-0127) for larger payloads. +- Notices must be attributable to a specific step. +- The mechanism should support optional source location (file, line) for + VCS annotation use cases. + +## Proposal + +### Overview + +This proposal introduces notices as a first-class concept in the Tekton +API, following the same architectural pattern as Results: + +1. **`/tekton/notices/` volume**: Steps write notice files to this directory. +2. **Entrypoint collection**: The entrypoint binary reads notices after step + completion and includes them in the termination message. +3. **Reconciler processing**: The TaskRun reconciler reads notices from pod + status and populates `TaskRunStatus.Notices` and `StepState.Notices`. +4. **PipelineRun aggregation**: The PipelineRun reconciler aggregates notices + from child TaskRuns into `PipelineRunStatus.Notices`. + +### Notice Type Definition + +```go +// Notice represents a structured message emitted by a step that does not +// affect the step's success/failure status. Notices are informational +// messages, warnings, or non-fatal errors that downstream systems can +// consume for display, annotation, or policy evaluation. +type Notice struct { + // Level indicates the severity of the notice. + // Valid values: "info", "warning", "error". + // "error" means a non-fatal error — the step still succeeded. + Level string `json:"level"` + + // Message is the human-readable notice text. + Message string `json:"message"` + + // Step is the name of the step that emitted this notice. + // Populated automatically by the reconciler; not set by the step author. + // +optional + Step string `json:"step,omitempty"` + + // File is the source file path related to this notice. + // Used by VCS integrations to create inline annotations. + // +optional + File string `json:"file,omitempty"` + + // StartLine is the starting line number in the source file. + // +optional + StartLine int `json:"startLine,omitempty"` + + // EndLine is the ending line number in the source file. + // +optional + EndLine int `json:"endLine,omitempty"` +} +``` + +The `notices` field is added to both `TaskRunStatusFields` and `StepState`: + +```go +type TaskRunStatusFields struct { + // ... existing fields ... + + // Notices are structured messages emitted by steps that do not affect + // the task's success/failure status. + // +optional + // +listType=atomic + Notices []Notice `json:"notices,omitempty"` +} + +type StepState struct { + // ... existing fields ... + + // Notices are structured messages emitted by this step. + // +optional + // +listType=atomic + Notices []Notice `json:"notices,omitempty"` +} +``` + +### Emitting Notices from Steps + +Steps emit notices by writing JSON files to `/tekton/notices/`: + +```bash +# Single notice +cat > /tekton/notices/001.json < /tekton/notices/lint.json < + +## References + +- [KONFLUX-8688](https://issues.redhat.com/browse/KONFLUX-8688): Tasks + which exit successfully but emit warnings considered "successful" in VCS + comments/checks +- [GitHub Actions Workflow Commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message): + `::warning::`, `::notice::`, `::error::` annotations +- [GitLab Code Quality Reports](https://docs.gitlab.com/ee/ci/testing/code_quality.html): + Inline merge request annotations from CI +- [TEP-0127: Larger Results via Sidecar Logs](https://github.com/tektoncd/community/blob/main/teps/0127-larger-results-via-sidecar-logs.md): + Mechanism for bypassing 4KB termination message limit +- [TEP-0147: Tekton Artifacts Phase 1](https://github.com/tektoncd/community/blob/main/teps/0147-tekton-artifacts-phase1.md): + Recent addition to TaskRun status (similar pattern of extending status) From ec67a2a02cc6a5b4add4c006bf8bab62bc99215f Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Fri, 20 Mar 2026 14:56:20 +0530 Subject: [PATCH 2/8] Address review feedback on TEP-0166 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply 10 fixes from brutal code review: 1. Use per-step directories (/tekton/steps//notices/) instead of shared /tekton/notices/ volume. Follows Artifacts (TEP-0147) pattern. No new volume mount needed. 2. Honest termination message budget: Results always win, notices are best-effort in remaining space. Drop fictional "1KB budget". 3. Add level validation: entrypoint validates, reconciler double-checks. Invalid levels are dropped with warning log. Case-sensitive. 4. Add 100-notice cap on PipelineRun aggregation to prevent CRD bloat. 5. Single JSON format: arrays only. No single-object ambiguity. 6. Use *int for StartLine/EndLine so line 0 is representable. 7. Use PerFeatureFlag pattern (Stability: AlphaAPIFields) matching enable-artifacts, not a standalone boolean. 8. Define failed step behavior: notices collected in deferred path, lost on OOMKill/eviction (same as Results). 9. Add deduplication by (pipelineTask, step, level, message) tuple. 10. Drop "error" level — a non-fatal error is a warning by definition. Co-Authored-By: Claude Opus 4.6 (1M context) --- teps/0166-task-notices-and-warnings.md | 317 +++++++++++++++++-------- 1 file changed, 221 insertions(+), 96 deletions(-) diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md index 85c48f2c1..61c627f6f 100644 --- a/teps/0166-task-notices-and-warnings.md +++ b/teps/0166-task-notices-and-warnings.md @@ -73,9 +73,10 @@ This TEP introduces: location fields (`file`, `startLine`, `endLine`) - A `notices` field on `TaskRunStatusFields` for task-level notices - A `notices` field on `StepState` for per-step notices -- A `/tekton/notices/` volume mount where steps write notice files +- Per-step notice directory at `/tekton/steps//notices/` using + the existing step metadata directory infrastructure (no new volume mount) - Entrypoint collection of notices alongside results -- PipelineRun aggregation of notices from child TaskRuns +- PipelineRun aggregation of notices from child TaskRuns (capped at 100) **Example TaskRun status with notices:** @@ -152,7 +153,8 @@ Downstream platforms built on Tekton are directly impacted: ### Non-Goals - **Log parsing**: This TEP does not propose parsing step container logs - for `::warning::` syntax. Steps must explicitly write to `/tekton/notices/`. + for `::warning::` syntax. Steps must explicitly write to their notices + directory. - **Failing on warnings**: Notices do not affect the Succeeded condition. A TaskRun with notices and exit code 0 is still Succeeded. Policy engines (Kyverno, OPA) or downstream systems can implement fail-on-warning logic @@ -172,9 +174,10 @@ As a **Task author**, I want my linter step to report specific warnings pull request, without failing the build. Today, the linter exits 0 and warnings are only visible in container logs. -With notices, the linter writes structured warnings to `/tekton/notices/` -and they appear in `taskrun.status.notices`. PAC reads these and creates -GitHub Check Run annotations with file and line information. +With notices, the linter writes structured warnings to its per-step +notices directory and they appear in `taskrun.status.notices`. PAC reads +these and creates GitHub Check Run annotations with file and line +information. #### Security Scanner Informational Findings @@ -213,30 +216,51 @@ but not blocked. ### Overview This proposal introduces notices as a first-class concept in the Tekton -API, following the same architectural pattern as Results: +API, following the same architectural pattern as Artifacts (TEP-0147): -1. **`/tekton/notices/` volume**: Steps write notice files to this directory. +1. **Per-step notices directory**: Each step writes notice files to + `/tekton/steps//notices/`, using the existing step metadata + directory infrastructure. No new volume mount is needed — the entrypoint + already creates per-step directories for results and artifacts. 2. **Entrypoint collection**: The entrypoint binary reads notices after step completion and includes them in the termination message. 3. **Reconciler processing**: The TaskRun reconciler reads notices from pod status and populates `TaskRunStatus.Notices` and `StepState.Notices`. 4. **PipelineRun aggregation**: The PipelineRun reconciler aggregates notices - from child TaskRuns into `PipelineRunStatus.Notices`. + from child TaskRuns into `PipelineRunStatus.Notices`, capped at 100 + notices total to prevent CRD size limit issues. ### Notice Type Definition ```go +// NoticeLevel indicates the severity of a notice. +type NoticeLevel string + +const ( + // NoticeLevelInfo represents an informational message, no action needed. + NoticeLevelInfo NoticeLevel = "info" + // NoticeLevelWarning represents something to address, but not blocking. + NoticeLevelWarning NoticeLevel = "warning" +) + +// AllNoticeLevels is the set of valid notice levels for validation. +var AllNoticeLevels = []NoticeLevel{NoticeLevelInfo, NoticeLevelWarning} + // Notice represents a structured message emitted by a step that does not // affect the step's success/failure status. Notices are informational -// messages, warnings, or non-fatal errors that downstream systems can -// consume for display, annotation, or policy evaluation. +// messages or warnings that downstream systems can consume for display, +// annotation, or policy evaluation. type Notice struct { // Level indicates the severity of the notice. - // Valid values: "info", "warning", "error". - // "error" means a non-fatal error — the step still succeeded. - Level string `json:"level"` + // Valid values: "info", "warning". + // Validated by the entrypoint at collection time. Notices with + // unrecognized levels are dropped with a warning log. + // +kubebuilder:validation:Enum=info;warning + Level NoticeLevel `json:"level"` // Message is the human-readable notice text. + // Maximum length: 1024 characters. Truncated by the entrypoint if longer. + // +kubebuilder:validation:MaxLength=1024 Message string `json:"message"` // Step is the name of the step that emitted this notice. @@ -246,19 +270,31 @@ type Notice struct { // File is the source file path related to this notice. // Used by VCS integrations to create inline annotations. + // Maximum length: 256 characters. // +optional + // +kubebuilder:validation:MaxLength=256 File string `json:"file,omitempty"` - // StartLine is the starting line number in the source file. + // StartLine is the starting line number in the source file (1-based). + // Pointer type so that absence (nil) is distinguishable from line 0. // +optional - StartLine int `json:"startLine,omitempty"` + StartLine *int `json:"startLine,omitempty"` - // EndLine is the ending line number in the source file. + // EndLine is the ending line number in the source file (1-based). // +optional - EndLine int `json:"endLine,omitempty"` + EndLine *int `json:"endLine,omitempty"` } ``` +**Level validation:** The entrypoint validates the `level` field when +collecting notices. Notices with unrecognized levels are dropped and a +warning is logged. The reconciler performs a second validation pass and +drops any notices that bypassed entrypoint validation (e.g., from +sidecar-log path). Only `"info"` and `"warning"` are valid. The original +design considered `"error"` for non-fatal errors, but a non-fatal error +is a warning by definition — keeping only two levels avoids semantic +confusion and simplifies downstream mapping. + The `notices` field is added to both `TaskRunStatusFields` and `StepState`: ```go @@ -284,40 +320,65 @@ type StepState struct { ### Emitting Notices from Steps -Steps emit notices by writing JSON files to `/tekton/notices/`: +Steps emit notices by writing JSON files to their per-step notices +directory. The path is `/tekton/steps//notices/`, which the +entrypoint creates automatically (same infrastructure as +`/tekton/steps//artifacts/`). No new volume mount is needed. -```bash -# Single notice -cat > /tekton/notices/001.json < /tekton/notices/lint.json < $(step.stepMetadataDir)/notices/lint.json < $(step.stepMetadataDir)/notices/deprecations.json < $(step.stepMetadataDir)/notices/style.json </notices/`, there is no risk of filename +collisions between steps. This follows the same pattern that Artifacts +(TEP-0147) uses for per-step provenance data. ### Notice Aggregation in PipelineRun Status PipelineRun status includes an aggregated `notices` field that collects -notices from all child TaskRuns: +notices from all child TaskRuns, capped at **100 notices total** to +prevent CRD size limit issues. When the cap is reached, a final notice +is appended: `{"level": "warning", "message": "N additional notices from +M tasks truncated"}`. + +The 100-notice cap is chosen conservatively: at ~300 bytes per notice +(including attribution fields), 100 notices is ~30KB — well within the +1.5MB CRD limit while leaving room for the rest of PipelineRunStatus. ```go +// MaxPipelineRunNotices is the maximum number of notices aggregated +// into PipelineRunStatus from child TaskRuns. +const MaxPipelineRunNotices = 100 + type PipelineRunStatusFields struct { // ... existing fields ... // Notices are aggregated from child TaskRuns. // Each notice includes the step and task attribution. + // Maximum 100 notices; additional notices are truncated. // +optional // +listType=atomic Notices []PipelineRunNotice `json:"notices,omitempty"` @@ -334,39 +395,70 @@ type PipelineRunNotice struct { } ``` +**Deduplication:** The PipelineRun reconciler deduplicates notices by +`(pipelineTask, step, level, message)` tuple before appending to status. +This prevents duplicate notices from accumulating across reconciliation +loops. Notices with identical tuples but different file/line information +are considered distinct. + ### Notes and Caveats - Notices are **not** included in the TaskRun's `Succeeded` condition. A TaskRun with notices is still `Succeeded: True` if all steps exited 0. -- The `level: "error"` value is intentionally distinct from step failure. - It represents a non-fatal error that the step author chose not to fail on - (e.g., a test that found a flaky test but didn't block the suite). +- Only two levels are supported: `info` and `warning`. The original design + considered `error` for non-fatal errors, but an error that does not fail + a step is a warning by definition. Two levels keep the semantics clean + and simplify downstream mapping (GitHub: `notice`/`warning`). - Notice ordering is not guaranteed across steps but is preserved within - a single step's notice file. + a single step's notice files. + +**Behavior on failed steps:** + +- If a step exits with a non-zero exit code and `onError` is not set to + `continue`, the entrypoint calls `os.Exit()` and the deferred + `WriteMessage` fires. In this path, notices ARE collected (the deferred + function reads the notices directory before writing the termination + message), so failed steps can still emit notices. +- If the step is killed by a signal (OOMKilled, evicted), the entrypoint + does not get a chance to collect notices. Notices from killed steps are + lost. This is consistent with how Results behave on killed steps. +- When `onError: continue` is set, the entrypoint continues normally + after step failure and notices are collected as usual. ## Design Details ### Entrypoint Collection The entrypoint binary (`cmd/entrypoint`) already collects results from -`/tekton/results/` after step completion. Notice collection follows the -same pattern: - -1. After the step process exits, the entrypoint reads all `.json` files - from `/tekton/notices/`. -2. Each file is parsed as either a single `Notice` JSON object or a JSON - array of `Notice` objects. -3. Invalid JSON files are skipped with a warning log (non-fatal). -4. Collected notices are included in the container's termination message - alongside results and step metadata. +`/tekton/results/` and artifacts from `/tekton/steps//artifacts/` +after step completion. Notice collection follows the same Artifacts +pattern: + +1. The entrypoint creates the notices directory during initialization: + ```go + os.MkdirAll(filepath.Join(e.StepMetadataDir, "notices"), os.ModePerm) + ``` +2. After the step process exits (in the deferred `WriteMessage` path), + the entrypoint reads all `.json` files from the notices directory. +3. Each file is parsed as a JSON array of `Notice` objects. Files that + are not valid JSON arrays are skipped with a warning log (non-fatal). +4. Each notice is validated: + - `level` must be `"info"` or `"warning"` (invalid levels are dropped) + - `message` is truncated to 1024 characters + - `file` is truncated to 256 characters +5. Notices are capped at 20 per step. If more exist, they are truncated + and a final notice is appended: + `{"level": "warning", "message": "N additional notices truncated"}`. +6. Collected notices are serialized as a `RunResult` with + `NoticeResultType` and included in the termination message. -The termination message format is extended to include a `notices` key: +The termination message format is extended to include a notices entry: ```json [ {"key": "StartedAt", "value": "2026-03-20T10:00:00Z", "type": 3}, {"key": "Results", "value": "[...]", "type": 1}, - {"key": "Notices", "value": "[{\"level\":\"warning\",\"message\":\"...\"}]", "type": 1} + {"key": "Notices", "value": "[{\"level\":\"warning\",\"message\":\"...\"}]", "type": 7} ] ``` @@ -385,36 +477,45 @@ This follows the same code path as result extraction in ### Size Limits and Termination Message Budget -Kubernetes limits container termination messages to **4KB** (configurable -via `terminationMessagePolicy`). Tekton already uses this space for: - -- Step metadata (start time, exit code) -- Results - -Notices share this budget. To manage this: - -1. **Default limit**: A maximum of **20 notices per step** is enforced by - the entrypoint. Additional notices are truncated with a final notice: - `{"level": "warning", "message": "N additional notices truncated"}`. -2. **Total size cap**: Notices are serialized and checked against a - configurable budget (default: 1KB of the 4KB termination message). - If notices exceed the budget, they are truncated. -3. **Sidecar-log fallback**: When `results-from: sidecar-logs` is - configured (TEP-0127), notices use the same sidecar-log mechanism, - bypassing the 4KB limit. This is the recommended configuration for - use cases with many notices (e.g., linters producing hundreds of - warnings). +Kubernetes limits container termination messages to **4KB** +(`MaxContainerTerminationMessageLength = 4096` in +`pkg/termination/write.go`). Tekton already uses this space for step +metadata (StartedAt, ExitCode, Reason), Results, and Artifacts. There +is **no existing infrastructure** for reserving portions of this budget. + +Notices are **best-effort** within this constraint. The priority model: + +1. **Results always win.** The entrypoint collects results first (they + are required for pipeline data flow). After results are serialized, + the remaining termination message space is available for notices. +2. **Notices fill remaining space.** The entrypoint serializes collected + notices and checks whether adding them would exceed the 4KB limit. + If so, notices are progressively dropped (last-in-first-dropped) + until the message fits, with a final truncation notice appended. +3. **Per-step cap: 20 notices.** Even before budget checking, the + entrypoint caps at 20 notices per step to bound collection time. +4. **Message length cap: 1024 characters.** Individual notice messages + are truncated to 1024 characters to prevent a single verbose notice + from consuming the entire budget. + +**Practical example:** If results consume 3KB, there is ~1KB for notices +(roughly 3-5 notices). If results consume 3.9KB, there is ~100 bytes +(likely 0 notices — they are silently dropped). If results are small or +empty, up to 20 notices can fit. + +**Sidecar-log fallback:** When `results-from: sidecar-logs` is +configured (TEP-0127), notices use the same sidecar-log mechanism, +bypassing the 4KB limit entirely. This is the **recommended +configuration** for use cases with many notices (e.g., linters producing +hundreds of warnings). With sidecar-logs, the 20-notice per-step cap +still applies but the termination message budget constraint does not. ### Notice File Format -Steps write notices as JSON to `/tekton/notices/`. The format supports: +Steps write notices as **JSON arrays** to their per-step notices directory. +Only the array format is supported (no single-object format) to keep the +parser simple and avoid ambiguity: -**Single notice per file:** -```json -{"level": "warning", "message": "unused import", "file": "main.go", "startLine": 3} -``` - -**Array of notices per file:** ```json [ {"level": "warning", "message": "unused import", "file": "main.go", "startLine": 3}, @@ -422,28 +523,38 @@ Steps write notices as JSON to `/tekton/notices/`. The format supports: ] ``` +A single notice is written as an array with one element: + +```json +[{"level": "warning", "message": "dependency 'foo' is deprecated", "file": "go.mod", "startLine": 15}] +``` + **Valid `level` values:** | Level | Meaning | GitHub Annotation Mapping | |-------|---------|--------------------------| | `info` | Informational, no action needed | `notice` | | `warning` | Something to address, not blocking | `warning` | -| `error` | Non-fatal error, step still succeeded | `failure` | + +Notices with unrecognized `level` values are dropped by the entrypoint +with a warning log. The validation is case-sensitive (`"Warning"` is +invalid, `"warning"` is valid). ## Design Evaluation ### Reusability -Notices follow the same architectural pattern as Results -(`/tekton/results/` → entrypoint → termination message → reconciler → -status). This reuses the existing volume mount, entrypoint collection, -and status propagation infrastructure. Task authors familiar with Results -will find Notices intuitive. +Notices follow the same architectural pattern as Artifacts (TEP-0147): +per-step directories under `/tekton/steps//` → entrypoint +collection → termination message → reconciler → status. This reuses +the existing step metadata directory, entrypoint collection, and status +propagation infrastructure. No new volume mounts are introduced. Task +authors familiar with Results and Artifacts will find Notices intuitive. ### Simplicity -The core user experience is simple: write a JSON file to -`/tekton/notices/`, and the notice appears in the TaskRun status. No +The core user experience is simple: write a JSON array to the step's +notices directory, and the notices appear in the TaskRun status. No new binaries, sidecars, or configuration is needed for basic usage. The API addition is minimal — one new type (`Notice`) and one new field @@ -471,8 +582,8 @@ on two existing types (`TaskRunStatusFields.Notices` and ### User Experience -- **Task authors**: Write JSON files to `/tekton/notices/` — minimal - learning curve. +- **Task authors**: Write JSON arrays to the step's notices directory — + minimal learning curve. - **Platform engineers**: Read `taskrun.status.notices` to build integrations (dashboards, VCS annotations, alerting). - **`tkn` CLI users**: `tkn taskrun describe` could include a "Notices" @@ -483,9 +594,9 @@ on two existing types (`TaskRunStatusFields.Notices` and ### Performance - **Minimal overhead**: Notice collection adds one directory read per step - (same as results). If no `/tekton/notices/` files exist, no work is done. -- **Termination message size**: Notices share the 4KB budget with results. - The 20-notice default cap and 1KB budget prevent unbounded growth. + (same as results). If no notice files exist, no work is done. +- **Termination message size**: Notices are best-effort after results. + The 20-notice per-step cap and 1024-char message limit bound growth. - **Reconciler**: Notice parsing adds negligible CPU — it is a JSON unmarshal of a small payload, done once per step per reconciliation. @@ -493,10 +604,12 @@ on two existing types (`TaskRunStatusFields.Notices` and | Risk | Severity | Mitigation | |------|----------|------------| -| **Termination message overflow** | HIGH | Default 1KB budget for notices, 20-notice cap, truncation with warning. Sidecar-log fallback for large payloads. | -| **Abuse (excessively large notices)** | MEDIUM | Message field length capped at 1024 characters. File path capped at 256 characters. | +| **Termination message overflow** | HIGH | Results-first priority model. Notices are best-effort: progressively dropped when space is insufficient. Sidecar-log fallback for large payloads. | +| **PipelineRun CRD size** | MEDIUM | 100-notice cap on PipelineRun aggregation. Deduplication by `(pipelineTask, step, level, message)` prevents accumulation across reconciliation loops. | +| **Abuse (excessively large notices)** | MEDIUM | Message capped at 1024 characters. File path capped at 256 characters. 20-notice per-step cap. | | **Backward compatibility** | LOW | New optional field, defaulting to empty. No behavior change for existing TaskRuns. | | **Schema bloat** | LOW | One new type, two new fields. Comparable to the recent `Artifacts` addition. | +| **Invalid JSON from step authors** | LOW | Invalid files skipped with warning log. Does not affect step success/failure. | ### Drawbacks @@ -572,17 +685,25 @@ This requires no API change but: ## Implementation Plan **Milestone 1: Core API and Entrypoint (alpha)** -- Add `Notice` type to `pkg/apis/pipeline/v1/` +- Add `Notice` type to `pkg/apis/pipeline/v1/notice_types.go` +- Add `NoticeResultType` to `pkg/result/result.go` - Add `notices` field to `TaskRunStatusFields` and `StepState` -- Mount `/tekton/notices/` volume in pod creation -- Implement notice collection in entrypoint -- Implement notice extraction in reconciler -- Gate behind `enable-notices` feature flag (alpha) -- Unit tests +- Add `os.MkdirAll(StepMetadataDir/notices)` to entrypoint initialization +- Implement notice collection in entrypoint (validation, capping, budget) +- Implement notice extraction in `pkg/pod/status.go` reconciler +- Add `EnableNotices` as `PerFeatureFlag` with `Stability: AlphaAPIFields` + in `pkg/apis/config/feature_flags.go` (following the `DefaultEnableArtifacts` + pattern, not a standalone boolean) +- Gate all paths: entrypoint (`-enable_notices` flag), pod creation, + status extraction, webhook validation +- Unit tests for parsing, validation, truncation, budget priority **Milestone 2: PipelineRun Aggregation** - Add `PipelineRunNotice` type and `notices` to `PipelineRunStatusFields` - Aggregate notices from child TaskRuns in PipelineRun reconciler + (`pkg/reconciler/pipelinerun/resources/apply.go`) +- Implement 100-notice cap and deduplication by + `(pipelineTask, step, level, message)` tuple - Integration tests **Milestone 3: Sidecar-Log Support** @@ -614,7 +735,11 @@ and reconciler infrastructure. - Notices are a new optional field. Existing TaskRuns will not have notices. - No migration needed — the field defaults to `nil`/omitted. -- The feature is gated behind `enable-notices` (alpha, default `false`). +- The feature is gated behind `enable-notices` as a `PerFeatureFlag` with + `Stability: AlphaAPIFields`. It is enabled when `enable-api-fields` is + set to `alpha`, or when the per-feature flag `enable-notices` is + explicitly set to `true`. This follows the same pattern as + `enable-artifacts` (TEP-0147). ### Implementation Pull Requests From b0939d1404f85bf9b2a316c07d81f0d08370a6a7 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Fri, 20 Mar 2026 15:07:55 +0530 Subject: [PATCH 3/8] Update TEP table with TEP-0166 entry Co-Authored-By: Claude Opus 4.6 (1M context) --- teps/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/teps/README.md b/teps/README.md index 776541702..47c0e638b 100644 --- a/teps/README.md +++ b/teps/README.md @@ -151,3 +151,4 @@ This is the complete list of Tekton TEPs: |[TEP-0162](0162-event-based-pruning-of-tekton-resources.md) | event based pruning of tekton resources | proposed | 2025-06-18 | |[TEP-0163](0163-profilebased-dynamic-compute-resources-for-steps.md) | Profile-Based Dynamic Compute Resources for Steps | proposed | 2025-09-01 | |[TEP-0164](0164-tekton-kueue-integration.md) | Tekton Kueue Integration | proposed | 2026-01-28 | +|[TEP-0166](0166-task-notices-and-warnings.md) | Task Notices and Warnings | proposed | 2026-03-20 | From 2dc004a2d9c81b0463baaf1940178d899d9c737f Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Wed, 27 May 2026 04:12:42 +0530 Subject: [PATCH 4/8] docs: address TEP-0166 review feedback --- teps/0166-task-notices-and-warnings.md | 619 ++++++++++++++++++------- 1 file changed, 445 insertions(+), 174 deletions(-) diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md index 61c627f6f..25f1f624d 100644 --- a/teps/0166-task-notices-and-warnings.md +++ b/teps/0166-task-notices-and-warnings.md @@ -2,7 +2,7 @@ status: proposed title: Task Notices and Warnings creation-date: '2026-03-20' -last-updated: '2026-03-20' +last-updated: '2026-05-11' authors: - '@waveywaves' - '@athorp96' @@ -20,17 +20,24 @@ authors: - [Security Scanner Informational Findings](#security-scanner-informational-findings) - [Deprecation Notices](#deprecation-notices) - [Test Coverage Regression](#test-coverage-regression) + - [Controller-Generated Diagnostics](#controller-generated-diagnostics) - [Requirements](#requirements) - [Proposal](#proposal) - [Overview](#overview) - [Notice Type Definition](#notice-type-definition) - [Emitting Notices from Steps](#emitting-notices-from-steps) + - [Emitting Notices from Controllers](#emitting-notices-from-controllers) - [Notice Aggregation in PipelineRun Status](#notice-aggregation-in-pipelinerun-status) + - [Plain Text Fallback](#plain-text-fallback) - [Notes and Caveats](#notes-and-caveats) - [Design Details](#design-details) - [Entrypoint Collection](#entrypoint-collection) - [Reconciler Processing](#reconciler-processing) - [Size Limits and Termination Message Budget](#size-limits-and-termination-message-budget) + - [Eviction Order](#eviction-order) + - [Per-Item Size Limits](#per-item-size-limits) + - [Per-Step Cardinality Limits](#per-step-cardinality-limits) + - [Truncation Signaling](#truncation-signaling) - [Notice File Format](#notice-file-format) - [Design Evaluation](#design-evaluation) - [Reusability](#reusability) @@ -46,7 +53,13 @@ authors: - [Kubernetes Events](#kubernetes-events) - [Annotations on TaskRun](#annotations-on-taskrun) - [Condition Message Encoding](#condition-message-encoding) + - [Tekton Artifacts (TEP-0164)](#tekton-artifacts-tep-0164) + - [Reusable Finally Task](#reusable-finally-task) - [Implementation Plan](#implementation-plan) + - [Phase 1: Controller Notices and Core API (alpha)](#phase-1-controller-notices-and-core-api-alpha) + - [Phase 2: Step-Emitted Notices (alpha)](#phase-2-step-emitted-notices-alpha) + - [Phase 3: PipelineRun Aggregation](#phase-3-pipelinerun-aggregation) + - [Phase 4: Graduation to Beta](#phase-4-graduation-to-beta) - [Test Plan](#test-plan) - [Infrastructure Needed](#infrastructure-needed) - [Upgrade and Migration Strategy](#upgrade-and-migration-strategy) @@ -57,26 +70,35 @@ authors: ## Summary This TEP proposes adding structured **notices** to TaskRun and PipelineRun -status, enabling steps that exit successfully (exit code 0) to emit warnings, -informational messages, and non-fatal errors that are surfaced in the run's -status. +status, enabling steps and controllers to emit warnings and informational +messages that persist in the run's status. Today, Tekton's execution model is binary: a step either succeeds or fails. There is no structured mechanism for a successful step to communicate -"I succeeded, but here are things you should know." This gap prevents CI/CD -systems built on Tekton (such as Konflux, Pipelines as Code) from surfacing +"I succeeded, but here are things you should know." Similarly, the Tekton +reconcilers generate non-fatal diagnostic information (verification +warnings, pod rescheduling events, deprecated API field usage) that is +currently lost as ephemeral Kubernetes Events or transient Conditions +overwritten by the final Succeeded state. This gap prevents CI/CD systems +built on Tekton (such as Konflux, Pipelines as Code) from surfacing warnings as GitHub Check Run annotations, PR comments, or dashboard alerts. This TEP introduces: -- A `Notice` type with `level`, `message`, `step`, and optional source - location fields (`file`, `startLine`, `endLine`) +- A `Notice` type with `level`, `message`, optional `step`, and optional + source location fields (`file`, `startLine`) - A `notices` field on `TaskRunStatusFields` for task-level notices - A `notices` field on `StepState` for per-step notices +- Controller-emitted notices written directly to status (no termination + message path) - Per-step notice directory at `/tekton/steps//notices/` using the existing step metadata directory infrastructure (no new volume mount) - Entrypoint collection of notices alongside results -- PipelineRun aggregation of notices from child TaskRuns (capped at 100) +- PipelineRun summary aggregation of notice counts from child TaskRuns + +The implementation is phased: Phase 1 ships the API and controller-emitted +notices (~200 LOC, no entrypoint changes). Phase 2 adds step-emitted +notices with honest termination message budget assessment. **Example TaskRun status with notices:** @@ -86,6 +108,7 @@ status: - type: Succeeded status: "True" reason: Succeeded + message: "All Steps completed. 2 warning(s) emitted." steps: - name: lint terminated: @@ -110,6 +133,8 @@ status: step: lint file: "src/handler.go" startLine: 15 + - level: warning + message: "trusted resource verification: signature valid but certificate expires in 7 days" ``` ## Motivation @@ -119,36 +144,56 @@ GitHub Actions provides `::warning::`, `::notice::`, and `::error::` that surface as inline annotations on PR diffs and in the Checks summary. GitLab CI supports [code quality reports](https://docs.gitlab.com/ee/ci/testing/code_quality.html) -that annotate merge requests with findings. These mechanisms are essential +that annotate merge requests with findings. Jenkins has a first-class +`UNSTABLE` build status that sits between `SUCCESS` and `FAILURE`, mapped +to GitHub's `neutral` check conclusion. These mechanisms are essential for CI/CD systems to communicate nuanced feedback beyond pass/fail. Tekton has no equivalent. A linter that finds low-severity issues, a security scanner that finds informational CVEs, a build that uses a deprecated API, -or a test suite that detects coverage regression — all exit 0 but have +or a test suite that detects coverage regression all exit 0 but have warnings worth surfacing. Today, these warnings are buried in container logs and invisible to VCS integrations. +Similarly, the Tekton reconcilers themselves produce non-fatal diagnostic +information that has no structured home: + +- **Trusted resources verification warnings** (`VerificationWarn` in + `pkg/trustedresources/verify.go`) currently use a dedicated Condition type + that a general notices field would eliminate. +- **Pod rescheduling history** is lost when `Succeeded: True` overwrites + the transient `Unknown` condition. +- **Pod affinity overwrite** silently mutates user config, visible only as + a 1-hour TTL Kubernetes Event. +- **LimitRange resource adjustments** silently adjust user-specified + resource requests with no record. +- **Result validation failures** are emitted as ephemeral Warning events + only. + Downstream platforms built on Tekton are directly impacted: -- **Konflux** ([KONFLUX-8688](https://issues.redhat.com/browse/KONFLUX-8688)): - Tasks that exit successfully but emit warnings need those warnings reflected - in VCS comments and checks. -- **Pipelines as Code**: Cannot map task warnings to GitHub Check Run - annotations because TaskRun status has no structured warning data. +- Tasks that exit successfully but emit warnings need those warnings + reflected in VCS comments and checks. Without structured notice data in + the TaskRun status, VCS integrations report warnings as success. +- **Pipelines as Code** ([Issue 1235](https://github.com/tektoncd/pipelines-as-code/issues/1235)): + Cannot map task warnings to GitHub Check Run annotations because TaskRun + status has no structured warning data. - **Tekton Dashboard**: Cannot display a "warnings" section for successful TaskRuns because no such data exists in the API. ### Goals -- Steps can emit structured notices (warnings, info, non-fatal errors) that - persist in TaskRun status after successful completion. +- Steps and controllers can emit structured notices (warnings, info) that + persist in TaskRun status after completion. - Notices are available at both the step level (`stepState.notices`) and the task level (`taskRunStatus.notices`) for flexible consumption. - The mechanism follows existing Tekton patterns (similar to Results via `/tekton/results/`). - Downstream systems (PAC, Konflux, Dashboard, `tkn` CLI) can consume notices from the TaskRun status API without parsing logs. -- PipelineRun status aggregates notices from child TaskRuns. +- PipelineRun status summarizes notice counts from child TaskRuns. +- Controller-emitted notices (e.g., verification warnings, deprecated API + field usage) have a structured home instead of ephemeral Events. ### Non-Goals @@ -163,7 +208,17 @@ Downstream platforms built on Tekton are directly impacted: supports file/line information, rendering inline annotations is a Dashboard concern outside this TEP's scope. - **Notice-based routing in Pipelines**: Using notices to conditionally - skip or trigger downstream tasks is deferred to future work. + skip or trigger downstream tasks (e.g., `$(tasks.foo.notices)` variable + substitution) is an explicit non-goal. Tekton's variable substitution + resolves to flat strings, and notices are structured records with multiple + fields. There is no natural scalar projection that preserves useful + information. Downstream consumers that need conditional logic based on + notices should read the TaskRun status via the Kubernetes API, which + every existing consumer (Chains, PAC, Konflux) already does. +- **Kubernetes admission warnings**: Spec-time concerns (deprecated fields + at `kubectl apply` time) are handled by Kubernetes admission warning + headers. This TEP covers execution-time diagnostics persisted in run + status. The two mechanisms are complementary. ### Use Cases @@ -198,16 +253,30 @@ As a **CI administrator**, I want test steps to report when coverage drops below a threshold as a warning (not a failure), so teams are informed but not blocked. +#### Controller-Generated Diagnostics + +As a **platform administrator** reviewing completed runs, I want to see +structured records of non-fatal controller decisions (trusted resource +verification warnings, LimitRange adjustments, pod rescheduling events, +pod affinity overwrites) in the TaskRun status, rather than hunting +through ephemeral Kubernetes Events that may have already been garbage +collected. + ### Requirements - Notices must be structured (not free-form log text) to enable programmatic consumption. -- The mechanism must not affect the TaskRun's Succeeded condition — a - TaskRun with notices is still Succeeded if all steps exit 0. +- The mechanism must not affect the TaskRun or PipelineRun's Succeeded + condition. A run with notices is still Succeeded if all steps exit 0. - Notices must persist in TaskRun status (not ephemeral like Events). -- The mechanism must work within the existing termination message size - constraints or use the sidecar-log approach (TEP-0127) for larger payloads. -- Notices must be attributable to a specific step. +- Step-emitted notices must work within the existing termination message + size constraints or use the sidecar-log approach (TEP-0127) for larger + payloads. +- Controller-emitted notices bypass the termination message path entirely + (written directly to status by the reconciler). +- Step-emitted notices must be attributable to a specific step (the + reconciler populates the `step` field automatically). Controller-emitted + notices have no step attribution. - The mechanism should support optional source location (file, line) for VCS annotation use cases. @@ -216,19 +285,20 @@ but not blocked. ### Overview This proposal introduces notices as a first-class concept in the Tekton -API, following the same architectural pattern as Artifacts (TEP-0147): - -1. **Per-step notices directory**: Each step writes notice files to - `/tekton/steps//notices/`, using the existing step metadata - directory infrastructure. No new volume mount is needed — the entrypoint - already creates per-step directories for results and artifacts. -2. **Entrypoint collection**: The entrypoint binary reads notices after step - completion and includes them in the termination message. -3. **Reconciler processing**: The TaskRun reconciler reads notices from pod - status and populates `TaskRunStatus.Notices` and `StepState.Notices`. -4. **PipelineRun aggregation**: The PipelineRun reconciler aggregates notices - from child TaskRuns into `PipelineRunStatus.Notices`, capped at 100 - notices total to prevent CRD size limit issues. +API, delivered in two phases: + +**Phase 1: Controller notices and core API.** The `Notice` type and +`notices` fields are added to the API. The reconciler can write notices +directly to TaskRun status (e.g., verification warnings, deprecated field +usage). No entrypoint changes are needed. This immediately unblocks +downstream consumers (PAC, Konflux) that can start consuming +`status.notices`. + +**Phase 2: Step-emitted notices.** Steps write notice files to their +per-step notices directory, following the Artifacts (TEP-0147) pattern. +The entrypoint collects notices and includes them in the termination +message. This phase requires honest budget assessment of the termination +message constraints. ### Notice Type Definition @@ -246,15 +316,13 @@ const ( // AllNoticeLevels is the set of valid notice levels for validation. var AllNoticeLevels = []NoticeLevel{NoticeLevelInfo, NoticeLevelWarning} -// Notice represents a structured message emitted by a step that does not -// affect the step's success/failure status. Notices are informational -// messages or warnings that downstream systems can consume for display, -// annotation, or policy evaluation. +// Notice represents a structured message emitted by a step or controller +// that does not affect the run's success/failure status. Notices are +// informational messages or warnings that downstream systems can consume +// for display, annotation, or policy evaluation. type Notice struct { // Level indicates the severity of the notice. // Valid values: "info", "warning". - // Validated by the entrypoint at collection time. Notices with - // unrecognized levels are dropped with a warning log. // +kubebuilder:validation:Enum=info;warning Level NoticeLevel `json:"level"` @@ -264,7 +332,8 @@ type Notice struct { Message string `json:"message"` // Step is the name of the step that emitted this notice. - // Populated automatically by the reconciler; not set by the step author. + // Populated automatically by the reconciler for step-emitted notices. + // Empty for controller-emitted notices. // +optional Step string `json:"step,omitempty"` @@ -279,21 +348,22 @@ type Notice struct { // Pointer type so that absence (nil) is distinguishable from line 0. // +optional StartLine *int `json:"startLine,omitempty"` - - // EndLine is the ending line number in the source file (1-based). - // +optional - EndLine *int `json:"endLine,omitempty"` } ``` +The `endLine` field from the original design has been removed. Linters +report single lines, and GitHub annotations default `end_line = start_line` +in the vast majority of cases. `endLine` can be added in a future revision +(backward-compatible addition) if a concrete use case requires it. + **Level validation:** The entrypoint validates the `level` field when collecting notices. Notices with unrecognized levels are dropped and a warning is logged. The reconciler performs a second validation pass and drops any notices that bypassed entrypoint validation (e.g., from sidecar-log path). Only `"info"` and `"warning"` are valid. The original design considered `"error"` for non-fatal errors, but a non-fatal error -is a warning by definition — keeping only two levels avoids semantic -confusion and simplifies downstream mapping. +is a warning by definition. Two levels keep the semantics clean +and simplify downstream mapping. The `notices` field is added to both `TaskRunStatusFields` and `StepState`: @@ -301,8 +371,8 @@ The `notices` field is added to both `TaskRunStatusFields` and `StepState`: type TaskRunStatusFields struct { // ... existing fields ... - // Notices are structured messages emitted by steps that do not affect - // the task's success/failure status. + // Notices are structured messages emitted by steps or controllers + // that do not affect the task's success/failure status. // +optional // +listType=atomic Notices []Notice `json:"notices,omitempty"` @@ -318,6 +388,13 @@ type StepState struct { } ``` +The `+listType=atomic` marker is consistent with every other status array +in the Tekton v1 API (`Steps`, `Results`, `Sidecars`, `ChildReferences`, +`RetriesStatus`, `Artifacts.Inputs`, `Artifacts.Outputs`). Tekton does not +use Server-Side Apply for status updates, so the list type marker has no +runtime effect. The reconciler reconstructs the entire notices array from +pod termination messages on each reconciliation (single-writer model). + ### Emitting Notices from Steps Steps emit notices by writing JSON files to their per-step notices @@ -325,9 +402,6 @@ directory. The path is `/tekton/steps//notices/`, which the entrypoint creates automatically (same infrastructure as `/tekton/steps//artifacts/`). No new volume mount is needed. -The notice file format is a **JSON array** (one format only, to avoid -parser ambiguity): - ```bash cat > $(step.stepMetadataDir)/notices/lint.json </notices/`, there is no risk of filename collisions between steps. This follows the same pattern that Artifacts (TEP-0147) uses for per-step provenance data. -### Notice Aggregation in PipelineRun Status +**Note on termination message capacity:** Step-emitted notices compete +with results for space in the termination message (see +[Size Limits and Termination Message Budget](#size-limits-and-termination-message-budget)). +For use cases producing many notices (linters, security scanners), +`results-from: sidecar-logs` (TEP-0127) is the recommended configuration. +Without sidecar-logs, typical steps will fit 0-6 notices after results. + +### Emitting Notices from Controllers + +The TaskRun and PipelineRun reconcilers can write notices directly to +`status.notices` during reconciliation. This path bypasses the termination +message entirely and has no size constraint beyond the etcd object limit +(1.5 MiB). -PipelineRun status includes an aggregated `notices` field that collects -notices from all child TaskRuns, capped at **100 notices total** to -prevent CRD size limit issues. When the cap is reached, a final notice -is appended: `{"level": "warning", "message": "N additional notices from -M tasks truncated"}`. +Controller notices have an empty `step` field, which distinguishes them +from step-emitted notices. No additional `Source` field or separate +`ControllerNotice` type is needed. This follows the pattern of Kubernetes +Events, where the source component is contextual. -The 100-notice cap is chosen conservatively: at ~300 bytes per notice -(including attribution fields), 100 notices is ~30KB — well within the -1.5MB CRD limit while leaving room for the rest of PipelineRunStatus. +Controller-emitted notices are capped at **10 per TaskRun** to prevent +accumulation across reconciliation loops. Controller warnings are +low-cardinality by nature (deprecation warnings, verification results, +resource adjustments). + +**Deduplication:** The reconciler deduplicates controller notices by +`(level, message)` tuple before appending to status, preventing duplicate +notices from accumulating across reconciliation loops. + +**Example scenarios for controller notices:** + +| Scenario | Current behavior | With notices | +|----------|-----------------|--------------| +| Trusted resource verification warning | Dedicated `TrustedResourcesVerified` Condition (overwritten by Succeeded) | Notice persists alongside Succeeded | +| Pod rescheduling | Transient `Unknown` condition overwritten by Succeeded | Notice records rescheduling history | +| Pod affinity overwrite | Ephemeral Event (1h TTL) | Notice persists in status | +| LimitRange resource adjustment | Silent, no record | Notice records the adjustment | +| Result validation failure | Warning Event only | Notice + Event (hybrid) | + +### Notice Aggregation in PipelineRun Status + +PipelineRun status includes a summary `noticeSummary` field that +collects notice counts from child TaskRuns, following the +`childReferences` pattern (TEP-0100) of pointers to real data rather +than full copies. ```go -// MaxPipelineRunNotices is the maximum number of notices aggregated -// into PipelineRunStatus from child TaskRuns. -const MaxPipelineRunNotices = 100 +// MaxPipelineRunNoticeSummaries is the maximum number of task summaries +// in PipelineRunStatus. +const MaxPipelineRunNoticeSummaries = 100 type PipelineRunStatusFields struct { // ... existing fields ... - // Notices are aggregated from child TaskRuns. - // Each notice includes the step and task attribution. - // Maximum 100 notices; additional notices are truncated. + // NoticeSummary contains per-task notice counts from child TaskRuns. + // Consumers should fetch the referenced TaskRun for full notice details. // +optional // +listType=atomic - Notices []PipelineRunNotice `json:"notices,omitempty"` + NoticeSummary []PipelineRunNoticeSummary `json:"noticeSummary,omitempty"` } -type PipelineRunNotice struct { - Notice `json:",inline"` +type PipelineRunNoticeSummary struct { + // PipelineTask is the name of the pipeline task. + PipelineTask string `json:"pipelineTask"` - // TaskRun is the name of the child TaskRun that emitted this notice. + // TaskRun is the name of the child TaskRun. TaskRun string `json:"taskRun,omitempty"` - // PipelineTask is the name of the pipeline task. - PipelineTask string `json:"pipelineTask,omitempty"` + // WarningCount is the number of warning-level notices. + WarningCount int `json:"warningCount"` + + // InfoCount is the number of info-level notices. + InfoCount int `json:"infoCount"` } ``` -**Deduplication:** The PipelineRun reconciler deduplicates notices by -`(pipelineTask, step, level, message)` tuple before appending to status. -This prevents duplicate notices from accumulating across reconciliation -loops. Notices with identical tuples but different file/line information -are considered distinct. +This design avoids duplicating full notice payloads into the PipelineRun +CR (which TEP-0100 explicitly removed for TaskRun status). At ~80 bytes +per summary entry, 100 entries consume ~8 KB. Dashboard gets badge data +without additional API calls. PAC and Konflux can target-fetch only +TaskRuns that actually have notices. + +### Plain Text Fallback + +The entrypoint supports a fallback for plain text notice files. When a +file in the notices directory is not valid JSON, the entrypoint wraps +the content as an info-level notice: + +```go +Notice{Level: NoticeLevelInfo, Message: ""} +``` + +The parsing chain is: try `[]Notice`, try single `Notice` object, fall +back to plain text. This follows the same pattern as `ParamValue.UnmarshalJSON` +which has a four-stage fallback chain that never fails. Silently dropping +user data is worse than a degraded parse. ### Notes and Caveats -- Notices are **not** included in the TaskRun's `Succeeded` condition. - A TaskRun with notices is still `Succeeded: True` if all steps exited 0. -- Only two levels are supported: `info` and `warning`. The original design - considered `error` for non-fatal errors, but an error that does not fail - a step is a warning by definition. Two levels keep the semantics clean - and simplify downstream mapping (GitHub: `notice`/`warning`). +- Notices are **not** included in the TaskRun's Succeeded condition + determination. A TaskRun with notices is still `Succeeded: True` if all + steps exited 0. However, the condition **message** includes notice counts + (e.g., `"All Steps completed. 3 warning(s) emitted."`) for visibility in + `kubectl get taskrun` without any API change. +- A future TEP may introduce a `SucceededWithWarnings` condition reason + (following the pattern of `PipelineRunReasonCompleted` for runs with + skipped tasks). This TEP does not commit to that decision. +- Only two levels are supported: `info` and `warning`. - Notice ordering is not guaranteed across steps but is preserved within a single step's notice files. @@ -440,15 +568,16 @@ pattern: ``` 2. After the step process exits (in the deferred `WriteMessage` path), the entrypoint reads all `.json` files from the notices directory. -3. Each file is parsed as a JSON array of `Notice` objects. Files that - are not valid JSON arrays are skipped with a warning log (non-fatal). +3. Each file is parsed with the fallback chain: try `[]Notice`, try + single `Notice` object, fall back to plain text wrapping. 4. Each notice is validated: - `level` must be `"info"` or `"warning"` (invalid levels are dropped) - `message` is truncated to 1024 characters - `file` is truncated to 256 characters -5. Notices are capped at 20 per step. If more exist, they are truncated - and a final notice is appended: - `{"level": "warning", "message": "N additional notices truncated"}`. + - The `step` field is NOT set by the step author; the reconciler + injects it automatically (the entrypoint knows which step it is). +5. Notices are capped per step (see + [Per-Step Cardinality Limits](#per-step-cardinality-limits)). 6. Collected notices are serialized as a `RunResult` with `NoticeResultType` and included in the termination message. @@ -464,7 +593,9 @@ The termination message format is extended to include a notices entry: ### Reconciler Processing -The TaskRun reconciler processes notices from pod termination messages: +The TaskRun reconciler processes notices from two sources: + +**Step-emitted notices (Phase 2):** 1. For each step container, read the `Notices` entry from the termination message. @@ -475,46 +606,92 @@ The TaskRun reconciler processes notices from pod termination messages: This follows the same code path as result extraction in `pkg/pod/status/status.go`. +**Controller-emitted notices (Phase 1):** + +1. During reconciliation, the controller appends notices directly to + `TaskRunStatus.Notices` with an empty `Step` field. +2. Deduplication by `(level, message)` prevents accumulation across + reconciliation loops. +3. Controller notices are capped at 10 per TaskRun. + ### Size Limits and Termination Message Budget -Kubernetes limits container termination messages to **4KB** -(`MaxContainerTerminationMessageLength = 4096` in -`pkg/termination/write.go`). Tekton already uses this space for step -metadata (StartedAt, ExitCode, Reason), Results, and Artifacts. There -is **no existing infrastructure** for reserving portions of this budget. - -Notices are **best-effort** within this constraint. The priority model: - -1. **Results always win.** The entrypoint collects results first (they - are required for pipeline data flow). After results are serialized, - the remaining termination message space is available for notices. -2. **Notices fill remaining space.** The entrypoint serializes collected - notices and checks whether adding them would exceed the 4KB limit. - If so, notices are progressively dropped (last-in-first-dropped) - until the message fits, with a final truncation notice appended. -3. **Per-step cap: 20 notices.** Even before budget checking, the - entrypoint caps at 20 notices per step to bound collection time. -4. **Message length cap: 1024 characters.** Individual notice messages - are truncated to 1024 characters to prevent a single verbose notice - from consuming the entire budget. - -**Practical example:** If results consume 3KB, there is ~1KB for notices -(roughly 3-5 notices). If results consume 3.9KB, there is ~100 bytes -(likely 0 notices — they are silently dropped). If results are small or -empty, up to 20 notices can fit. +Kubernetes limits the **total** termination message size of all +containers in a pod to **12 KB** (issue +[tektoncd/pipeline#4808](https://github.com/tektoncd/pipeline/issues/4808)). +This 12 KB is divided equally across all containers (init containers + +step containers + sidecars). Tekton's entrypoint checks against a +per-container limit of 4096 bytes (`MaxContainerTerminationMessageLength` +in `pkg/termination/write.go`), but the actual per-container budget can +be much smaller. + +**Concrete budget for a 10-step task:** With 13 total containers (10 steps + +3 init containers), each container gets approximately **945 bytes**. After +Tekton's internal metadata (StartedAt, ExitCode, ~170 bytes), approximately +**775 bytes** remain for results and notices combined. If results consume +500 bytes, only ~275 bytes remain for notices (1-2 typical notices). + +This is why Phase 1 delivers controller notices first: they bypass the +termination message entirely and have no size constraint. Step-emitted +notices (Phase 2) are best-effort within this budget. **Sidecar-log fallback:** When `results-from: sidecar-logs` is configured (TEP-0127), notices use the same sidecar-log mechanism, -bypassing the 4KB limit entirely. This is the **recommended -configuration** for use cases with many notices (e.g., linters producing -hundreds of warnings). With sidecar-logs, the 20-notice per-step cap -still applies but the termination message budget constraint does not. +bypassing the 12 KB limit entirely. This is the **required +configuration** for use cases expecting more than 5 notices per step. + +### Eviction Order + +When the termination message budget is insufficient for all data, the +entrypoint evicts items in this order (lowest priority dropped first): + +1. **Internal metadata** (StartedAt, ExitCode) -- never evicted +2. **Results** -- never evicted (required for pipeline data flow) +3. **Artifacts** -- evicted before notices only if artifacts have moved + to external storage (TEP-0164); otherwise evicted after notices +4. **Notices** -- best-effort, progressively dropped (last-in-first-dropped) + +### Per-Item Size Limits + +| Item | Limit | +|------|-------| +| Notice message | 1024 characters | +| Notice file path | 256 characters | + +### Per-Step Cardinality Limits + +| Transport | Per-step cap | Rationale | +|-----------|-------------|-----------| +| Termination message | 20 | Physical limit self-constrains to 2-23 anyway | +| Sidecar-logs | 50 | Matches GitHub's per-job annotation limit | + +The per-step cap is configurable via `max-notices-per-step` in the +feature-flags ConfigMap, following the `max-result-size` pattern. + +Controller notices have a separate cap of 10 per TaskRun. + +The per-TaskRun total cap (step + controller) is 50. + +### Truncation Signaling + +When notices are truncated, signaling depends on the truncation cause: + +| Cause | Signal | +|-------|--------| +| Per-step cap exceeded | Meta-notice: `{"level":"warning","message":"N additional notices truncated"}` | +| Per-TaskRun cap exceeded | Meta-notice appended to `TaskRunStatus.Notices` | +| Termination message budget exhaustion | `noticesTruncated: true` boolean on `StepState` | +| Invalid JSON from step author | Warning log only (task-author bug, not user-facing truncation) | + +The `noticesTruncated` boolean is a fixed 52-byte addition to the +termination message that always fits, providing machine-readable +truncation signaling for the case where a meta-notice itself would not +fit. ### Notice File Format Steps write notices as **JSON arrays** to their per-step notices directory. -Only the array format is supported (no single-object format) to keep the -parser simple and avoid ambiguity: +Single JSON objects and plain text are also accepted via the fallback chain: ```json [ @@ -523,12 +700,19 @@ parser simple and avoid ambiguity: ] ``` -A single notice is written as an array with one element: +A single notice can be written as either an array with one element or a +bare object: ```json -[{"level": "warning", "message": "dependency 'foo' is deprecated", "file": "go.mod", "startLine": 15}] +{"level": "warning", "message": "dependency 'foo' is deprecated", "file": "go.mod", "startLine": 15} ``` +Plain text files are wrapped as info-level notices automatically. + +The `step` field should NOT be included in the file. The reconciler +populates it automatically from the step name, reducing per-notice wire +size by ~20 bytes. + **Valid `level` values:** | Level | Meaning | GitHub Annotation Mapping | @@ -545,19 +729,21 @@ invalid, `"warning"` is valid). ### Reusability Notices follow the same architectural pattern as Artifacts (TEP-0147): -per-step directories under `/tekton/steps//` → entrypoint -collection → termination message → reconciler → status. This reuses +per-step directories under `/tekton/steps//` -> entrypoint +collection -> termination message -> reconciler -> status. This reuses the existing step metadata directory, entrypoint collection, and status propagation infrastructure. No new volume mounts are introduced. Task authors familiar with Results and Artifacts will find Notices intuitive. ### Simplicity -The core user experience is simple: write a JSON array to the step's +The core user experience is simple: write a JSON file to the step's notices directory, and the notices appear in the TaskRun status. No new binaries, sidecars, or configuration is needed for basic usage. -The API addition is minimal — one new type (`Notice`) and one new field +Phase 1 (controller notices) requires no user action at all. + +The API addition is minimal: one new type (`Notice`) and one new field on two existing types (`TaskRunStatusFields.Notices` and `StepState.Notices`). @@ -566,26 +752,30 @@ on two existing types (`TaskRunStatusFields.Notices` and - Notices are consumed by downstream systems, not by Tekton itself. This keeps Tekton's core pipeline logic unchanged while enabling rich integrations. -- The optional `file`/`startLine`/`endLine` fields allow VCS-specific +- The optional `file`/`startLine` fields allow VCS-specific use cases without requiring them for simpler scenarios. -- The `level` taxonomy (`info`, `warning`, `error`) maps naturally to +- The `level` taxonomy (`info`, `warning`) maps naturally to GitHub, GitLab, and other VCS annotation systems. +- Controller-emitted notices cover reconciler-generated diagnostics + without requiring any step-level changes. ### Conformance - This proposal adds new optional fields to the TaskRun and PipelineRun status. No existing fields are modified. -- The `Notice` type introduces no new Kubernetes concepts — it is a +- The `Notice` type introduces no new Kubernetes concepts. It is a plain struct stored in the status subresource. - The API spec would need to document the new `notices` field and the `Notice` type. ### User Experience -- **Task authors**: Write JSON arrays to the step's notices directory — - minimal learning curve. +- **Task authors**: Write JSON to the step's notices directory. Plain + text fallback for convenience. - **Platform engineers**: Read `taskrun.status.notices` to build integrations (dashboards, VCS annotations, alerting). +- **Controller authors**: Append notices directly to status during + reconciliation for non-fatal diagnostics. - **`tkn` CLI users**: `tkn taskrun describe` could include a "Notices" section showing warnings from the run. - **Dashboard users**: A "Warnings" tab or badge could display notices @@ -596,27 +786,29 @@ on two existing types (`TaskRunStatusFields.Notices` and - **Minimal overhead**: Notice collection adds one directory read per step (same as results). If no notice files exist, no work is done. - **Termination message size**: Notices are best-effort after results. - The 20-notice per-step cap and 1024-char message limit bound growth. -- **Reconciler**: Notice parsing adds negligible CPU — it is a JSON - unmarshal of a small payload, done once per step per reconciliation. + Per-step caps and message limits bound growth. +- **Reconciler**: Notice parsing adds negligible CPU. Controller notice + deduplication is O(n) where n <= 10. ### Risks and Mitigations | Risk | Severity | Mitigation | |------|----------|------------| -| **Termination message overflow** | HIGH | Results-first priority model. Notices are best-effort: progressively dropped when space is insufficient. Sidecar-log fallback for large payloads. | -| **PipelineRun CRD size** | MEDIUM | 100-notice cap on PipelineRun aggregation. Deduplication by `(pipelineTask, step, level, message)` prevents accumulation across reconciliation loops. | -| **Abuse (excessively large notices)** | MEDIUM | Message capped at 1024 characters. File path capped at 256 characters. 20-notice per-step cap. | +| **Termination message overflow** | HIGH | Phase 1 uses controller notices (no termination message). Phase 2 uses results-first eviction. Sidecar-log fallback for large payloads. | +| **CR size** | MEDIUM | PipelineRun uses summary counts (~8 KB for 100 tasks) instead of full copies. Per-TaskRun cap of 50 notices. | +| **Abuse (excessively large notices)** | MEDIUM | Message capped at 1024 characters. File path capped at 256 characters. Per-step and per-TaskRun caps. | | **Backward compatibility** | LOW | New optional field, defaulting to empty. No behavior change for existing TaskRuns. | | **Schema bloat** | LOW | One new type, two new fields. Comparable to the recent `Artifacts` addition. | -| **Invalid JSON from step authors** | LOW | Invalid files skipped with warning log. Does not affect step success/failure. | +| **Invalid JSON from step authors** | LOW | Plain text fallback wraps content gracefully. Does not affect step success/failure. | ### Drawbacks - Adds another concept to the TaskRun API surface that users must learn. -- Competes with Results for termination message space. +- Step-emitted notices compete with Results for termination message space. - Downstream consumers must be updated to display notices (Dashboard, CLI, - PAC, Konflux) — the value is only realized when integrations exist. + PAC, Konflux). The value is only realized when integrations exist. +- Phase 1 (controller notices only) delivers partial value. The full + step-emitted notice experience requires Phase 2. ## Alternatives @@ -650,6 +842,14 @@ This requires no API change but: - Events are ephemeral (default 1h TTL) and not reliably queryable - Events lack structured fields (level, file, line) - Downstream consumers cannot depend on Events persisting +- Steps cannot emit Events without Kubernetes API access (the entrypoint + communicates via filesystem only) +- Every consumer would need a separate List call per TaskRun + +**Hybrid approach (recommended alongside this TEP):** Status is the +primary, authoritative store for notices. The reconciler emits one +summary Event per step ("Step 'lint' emitted 5 warnings") for real-time +monitoring. Events are informational, not a dependency. ### Annotations on TaskRun @@ -663,7 +863,7 @@ annotations: This requires no CRD change but: - Annotations have a 256KB total limit and are not individually typed - No per-step attribution -- Annotations are metadata, not status — semantically wrong for +- Annotations are metadata, not status. Semantically wrong for execution output ### Condition Message Encoding @@ -682,37 +882,100 @@ This requires no API change but: - Mixes success confirmation with warning details - Limited space in the condition message +### Tekton Artifacts (TEP-0164) + +Model notices as artifacts with a well-known type (e.g., +`tekton.dev/notice`): + +[TEP-0164](https://github.com/tektoncd/community/pull/1248) extends +TEP-0147 with external storage backends, solving the termination message +size problem. However: +- Artifacts carry provenance metadata (URI, Digest, StorageRef) that is + meaningless for "unused variable at main.go:42" +- Notices need fields (Level, File, StartLine) that `ArtifactValue` + does not have +- TEP-0164 is in draft with no implementation. Blocking TEP-0166 on it + creates a dependency in the wrong direction +- External storage (OCI, S3) is too heavy for 5 lint warnings totaling + 500 bytes + +TEP-0164's `Inline` mode could serve as a transport for notice payloads, +but the data model is a poor fit. The correct architecture is: small +notices go in status (this TEP), large structured reports (SARIF, JUnit) +are artifacts for TEP-0164 when it ships. + +### Reusable Finally Task + +A catalog task (`publish-notices`) in a `finally` block reads results +from previous tasks and posts them to GitHub/GitLab: + +```yaml +finally: + - name: publish-warnings + taskRef: + name: publish-notices + params: + - name: github-token + value: $(params.github-token) + - name: warnings + value: $(tasks.lint.results.warnings) +``` + +This works today with no API changes, but: +- Finally tasks cannot read step directories (separate pods, no volume + access) +- Each pipeline author must wire the task and manage GitHub tokens +- No standard format. Each task invents its own JSON schema, making a + generic publisher impossible +- 4KB result size limit constrains the payload +- Results from failed or skipped tasks are unavailable to finally tasks +- Task authors cannot use finally tasks (they are a Pipeline concern) +- This shifts the integration burden from 1 platform integration point + to N pipeline authors + +A `publish-notices` catalog task solves approximately 40-50% of the +problem: it standardizes the "POST to GitHub" step but cannot enforce +upstream format, does not fix size limits, and requires per-pipeline +wiring. + ## Implementation Plan -**Milestone 1: Core API and Entrypoint (alpha)** +### Phase 1: Controller Notices and Core API (alpha) + +~200 lines of code, no entrypoint changes. + - Add `Notice` type to `pkg/apis/pipeline/v1/notice_types.go` -- Add `NoticeResultType` to `pkg/result/result.go` - Add `notices` field to `TaskRunStatusFields` and `StepState` +- Add `EnableNotices` as `PerFeatureFlag` with `Stability: AlphaAPIFields` + in `pkg/apis/config/feature_flags.go` +- Implement controller notice emission in TaskRun reconciler + (verification warnings, pod rescheduling, affinity overwrite, etc.) +- Include notice count in Succeeded condition message +- Gate all paths behind the feature flag +- Unit tests for notice types, validation, deduplication, caps + +### Phase 2: Step-Emitted Notices (alpha) + +- Add `NoticeResultType` to `pkg/result/result.go` - Add `os.MkdirAll(StepMetadataDir/notices)` to entrypoint initialization -- Implement notice collection in entrypoint (validation, capping, budget) +- Implement notice collection in entrypoint (validation, capping, budget + eviction, plain text fallback) - Implement notice extraction in `pkg/pod/status.go` reconciler -- Add `EnableNotices` as `PerFeatureFlag` with `Stability: AlphaAPIFields` - in `pkg/apis/config/feature_flags.go` (following the `DefaultEnableArtifacts` - pattern, not a standalone boolean) -- Gate all paths: entrypoint (`-enable_notices` flag), pod creation, - status extraction, webhook validation -- Unit tests for parsing, validation, truncation, budget priority - -**Milestone 2: PipelineRun Aggregation** -- Add `PipelineRunNotice` type and `notices` to `PipelineRunStatusFields` -- Aggregate notices from child TaskRuns in PipelineRun reconciler - (`pkg/reconciler/pipelinerun/resources/apply.go`) -- Implement 100-notice cap and deduplication by - `(pipelineTask, step, level, message)` tuple +- Add `noticesTruncated` boolean to `StepState` +- Gate behind `-enable_notices` entrypoint flag +- Integration tests for step-emitted notices + +### Phase 3: PipelineRun Aggregation + +- Add `PipelineRunNoticeSummary` type and `noticeSummary` to + `PipelineRunStatusFields` +- Aggregate notice counts from child TaskRuns in PipelineRun reconciler - Integration tests -**Milestone 3: Sidecar-Log Support** -- Extend sidecar-log mechanism (TEP-0127) to support notices -- Remove 4KB termination message constraint for notices -- Performance testing with large notice payloads +### Phase 4: Graduation to Beta -**Milestone 4: Graduation to beta** - Address feedback from alpha usage +- Extend sidecar-log mechanism (TEP-0127) to support notices - Documentation updates - `tkn` CLI integration (stretch goal) @@ -720,9 +983,10 @@ This requires no API change but: - **Unit tests**: Notice parsing, entrypoint collection, reconciler extraction, size limit enforcement, truncation behavior, invalid JSON - handling. + handling, plain text fallback, controller notice deduplication. - **Integration tests**: End-to-end notice emission from step to - TaskRun status. PipelineRun aggregation. Feature flag gating. + TaskRun status. Controller notice emission. PipelineRun aggregation. + Feature flag gating. - **E2E tests**: Step writes notices, verify they appear in `kubectl get taskrun -o json`. @@ -734,7 +998,7 @@ and reconciler infrastructure. ### Upgrade and Migration Strategy - Notices are a new optional field. Existing TaskRuns will not have notices. -- No migration needed — the field defaults to `nil`/omitted. +- No migration needed. The field defaults to `nil`/omitted. - The feature is gated behind `enable-notices` as a `PerFeatureFlag` with `Stability: AlphaAPIFields`. It is enabled when `enable-api-fields` is set to `alpha`, or when the per-feature flag `enable-notices` is @@ -749,14 +1013,21 @@ To be filled in after implementation. ## References -- [KONFLUX-8688](https://issues.redhat.com/browse/KONFLUX-8688): Tasks - which exit successfully but emit warnings considered "successful" in VCS - comments/checks - [GitHub Actions Workflow Commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message): `::warning::`, `::notice::`, `::error::` annotations - [GitLab Code Quality Reports](https://docs.gitlab.com/ee/ci/testing/code_quality.html): Inline merge request annotations from CI +- [Jenkins UNSTABLE build status](https://www.jenkins.io/doc/book/pipeline/syntax/#post): + First-class intermediate status between SUCCESS and FAILURE +- [GitHub Check Runs API](https://docs.github.com/en/rest/checks/runs): + `neutral` conclusion for non-blocking findings - [TEP-0127: Larger Results via Sidecar Logs](https://github.com/tektoncd/community/blob/main/teps/0127-larger-results-via-sidecar-logs.md): - Mechanism for bypassing 4KB termination message limit + Mechanism for bypassing termination message limits - [TEP-0147: Tekton Artifacts Phase 1](https://github.com/tektoncd/community/blob/main/teps/0147-tekton-artifacts-phase1.md): Recent addition to TaskRun status (similar pattern of extending status) +- [TEP-0164: Tekton Artifacts Phase 2](https://github.com/tektoncd/community/pull/1248): + External storage backends for artifacts and results +- [tektoncd/pipeline#4808](https://github.com/tektoncd/pipeline/issues/4808): + Termination message size constraints and container count impact +- [Pipelines as Code Issue 1235](https://github.com/tektoncd/pipelines-as-code/issues/1235): + Standardized result fields for task warning reporting From 399f128bb8d610c9a1a4021d33f6faf6f104cd67 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Wed, 27 May 2026 13:09:42 +0530 Subject: [PATCH 5/8] docs: clarify notice transport and size tradeoffs --- teps/0166-task-notices-and-warnings.md | 71 ++++++++++++++++++-------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md index 25f1f624d..3decb3c60 100644 --- a/teps/0166-task-notices-and-warnings.md +++ b/teps/0166-task-notices-and-warnings.md @@ -34,7 +34,7 @@ authors: - [Entrypoint Collection](#entrypoint-collection) - [Reconciler Processing](#reconciler-processing) - [Size Limits and Termination Message Budget](#size-limits-and-termination-message-budget) - - [Eviction Order](#eviction-order) + - [Budget Priority](#budget-priority) - [Per-Item Size Limits](#per-item-size-limits) - [Per-Step Cardinality Limits](#per-step-cardinality-limits) - [Truncation Signaling](#truncation-signaling) @@ -402,8 +402,13 @@ directory. The path is `/tekton/steps//notices/`, which the entrypoint creates automatically (same infrastructure as `/tekton/steps//artifacts/`). No new volume mount is needed. +This TEP adds an author-facing variable, `$(step.notices.path)`, that +resolves to the current step's notices directory. It intentionally does +not expose a broad `$(step.stepMetadataDir)` variable; task authors only +get the scoped write path for notices. + ```bash -cat > $(step.stepMetadataDir)/notices/lint.json < $(step.notices.path)/lint.json < $(step.stepMetadataDir)/notices/deprecations.json < $(step.notices.path)/deprecations.json < $(step.stepMetadataDir)/notices/style.json < $(step.notices.path)/style.json < Date: Wed, 27 May 2026 13:30:43 +0530 Subject: [PATCH 6/8] docs: fix TEP-0166 table and artifact references --- teps/0166-task-notices-and-warnings.md | 27 +++++++++++++------------- teps/README.md | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md index 3decb3c60..7a77f399e 100644 --- a/teps/0166-task-notices-and-warnings.md +++ b/teps/0166-task-notices-and-warnings.md @@ -53,7 +53,7 @@ authors: - [Kubernetes Events](#kubernetes-events) - [Annotations on TaskRun](#annotations-on-taskrun) - [Condition Message Encoding](#condition-message-encoding) - - [Tekton Artifacts (TEP-0164)](#tekton-artifacts-tep-0164) + - [Tekton Artifacts External Storage Proposal](#tekton-artifacts-external-storage-proposal) - [Reusable Finally Task](#reusable-finally-task) - [Implementation Plan](#implementation-plan) - [Phase 1: Controller Notices and Core API (alpha)](#phase-1-controller-notices-and-core-api-alpha) @@ -669,7 +669,7 @@ Retention priority is: notices. 3. **Artifacts metadata** -- retained ahead of notices while artifacts still use termination-message transport. If artifacts later move to - external storage (TEP-0164), their inline metadata pressure changes. + external storage, their inline metadata pressure changes. 4. **Notices** -- best-effort, progressively dropped (last-in-first-dropped) after per-step and per-TaskRun caps are applied. @@ -733,8 +733,8 @@ bare object: The entrypoint accepts public field names from task authors, then encodes the termination-message payload with compact field names (`l`, `m`, `p`, -`sl`, `el`) to conserve the Kubernetes termination-message budget. Plain -text files are wrapped as info-level notices automatically. +`sl`) to conserve the Kubernetes termination-message budget. Plain text +files are wrapped as info-level notices automatically. The `step` field should NOT be included in the file. The reconciler populates it automatically from the step name, reducing per-notice wire @@ -909,27 +909,28 @@ This requires no API change but: - Mixes success confirmation with warning details - Limited space in the condition message -### Tekton Artifacts (TEP-0164) +### Tekton Artifacts External Storage Proposal Model notices as artifacts with a well-known type (e.g., `tekton.dev/notice`): -[TEP-0164](https://github.com/tektoncd/community/pull/1248) extends +The Tekton Artifacts external storage proposal in +[community#1248](https://github.com/tektoncd/community/pull/1248) extends TEP-0147 with external storage backends, solving the termination message size problem. However: - Artifacts carry provenance metadata (URI, Digest, StorageRef) that is meaningless for "unused variable at main.go:42" - Notices need fields (Level, File, StartLine) that `ArtifactValue` does not have -- TEP-0164 is in draft with no implementation. Blocking TEP-0166 on it - creates a dependency in the wrong direction +- The external-storage proposal is in draft with no implementation. + Blocking TEP-0166 on it creates a dependency in the wrong direction - External storage (OCI, S3) is too heavy for 5 lint warnings totaling 500 bytes -TEP-0164's `Inline` mode could serve as a transport for notice payloads, -but the data model is a poor fit. The correct architecture is: small -notices go in status (this TEP), large structured reports (SARIF, JUnit) -are artifacts for TEP-0164 when it ships. +The proposal's `Inline` mode could serve as a transport for notice +payloads, but the data model is a poor fit. The correct architecture is: +small notices go in status (this TEP), while large structured reports +(SARIF, JUnit) become artifacts when external artifact storage ships. ### Reusable Finally Task @@ -1052,7 +1053,7 @@ To be filled in after implementation. Mechanism for bypassing termination message limits - [TEP-0147: Tekton Artifacts Phase 1](https://github.com/tektoncd/community/blob/main/teps/0147-tekton-artifacts-phase1.md): Recent addition to TaskRun status (similar pattern of extending status) -- [TEP-0164: Tekton Artifacts Phase 2](https://github.com/tektoncd/community/pull/1248): +- [Tekton Artifacts external storage proposal](https://github.com/tektoncd/community/pull/1248): External storage backends for artifacts and results - [tektoncd/pipeline#4808](https://github.com/tektoncd/pipeline/issues/4808): Termination message size constraints and container count impact diff --git a/teps/README.md b/teps/README.md index 47c0e638b..29b9b3587 100644 --- a/teps/README.md +++ b/teps/README.md @@ -151,4 +151,4 @@ This is the complete list of Tekton TEPs: |[TEP-0162](0162-event-based-pruning-of-tekton-resources.md) | event based pruning of tekton resources | proposed | 2025-06-18 | |[TEP-0163](0163-profilebased-dynamic-compute-resources-for-steps.md) | Profile-Based Dynamic Compute Resources for Steps | proposed | 2025-09-01 | |[TEP-0164](0164-tekton-kueue-integration.md) | Tekton Kueue Integration | proposed | 2026-01-28 | -|[TEP-0166](0166-task-notices-and-warnings.md) | Task Notices and Warnings | proposed | 2026-03-20 | +|[TEP-0166](0166-task-notices-and-warnings.md) | Task Notices and Warnings | proposed | 2026-05-11 | From 6faf3a45978cb3b8785ab45959eded095562451f Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Thu, 11 Jun 2026 02:04:35 +0530 Subject: [PATCH 7/8] docs: address TEP-0166 review feedback --- teps/0166-task-notices-and-warnings.md | 32 ++++++++++++++++++-------- teps/README.md | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md index 7a77f399e..b9b5bbb5d 100644 --- a/teps/0166-task-notices-and-warnings.md +++ b/teps/0166-task-notices-and-warnings.md @@ -2,7 +2,7 @@ status: proposed title: Task Notices and Warnings creation-date: '2026-03-20' -last-updated: '2026-05-11' +last-updated: '2026-06-09' authors: - '@waveywaves' - '@athorp96' @@ -262,6 +262,11 @@ pod affinity overwrites) in the TaskRun status, rather than hunting through ephemeral Kubernetes Events that may have already been garbage collected. +As a **Tekton controller author**, I want a canonical way to reflect +non-failure warnings and information on runs that can be semantically +consumed by other services, integrations, and users. These notices are not +always attributable to a step, so step attribution must remain optional. + ### Requirements - Notices must be structured (not free-form log text) to enable programmatic @@ -529,7 +534,10 @@ Notice{Level: NoticeLevelInfo, Message: ""} The parsing chain is: try `[]Notice`, try single `Notice` object, fall back to plain text. This follows the same pattern as `ParamValue.UnmarshalJSON` which has a four-stage fallback chain that never fails. Silently dropping -user data is worse than a degraded parse. +user data is worse than a degraded parse. The fallback is a convenience +path only: plain text notices have no source location metadata and default +to `info`, so task authors that need VCS annotations or warning severity +should use the JSON format. ### Notes and Caveats @@ -589,7 +597,9 @@ pattern: The termination message format is extended to include a notices entry. Because this transport competes for bytes with Results and Artifacts, the wire payload uses compact JSON field names while the public API keeps the -readable `Notice` schema: +readable `Notice` schema. The compact representation uses one-letter +field names (`l`, `m`, `p`, `sl`) and one-letter levels (`w` for warning, +`i` for info): ```json [ @@ -601,7 +611,9 @@ readable `Notice` schema: The reconciler expands this compact representation into `Notice{Level: "warning", Message: "unused variable", File: "main.go", -StartLine: 42}` before writing status. +StartLine: 42}` before writing status. The compact wire format is an +internal transport detail only; task authors and API consumers use the +readable public field names and level strings. ### Reconciler Processing @@ -649,11 +661,13 @@ notices (Phase 2) are best-effort within this budget. **Sidecar-log fallback:** When `results-from: sidecar-logs` is configured (TEP-0127), notices use the same sidecar-log mechanism, -bypassing the 12 KB limit entirely. This fallback is opt-in because -sidecar-log results are not enabled by default and have their own scaling -trade-offs. Without sidecar-logs, step-emitted notices remain -best-effort and may be truncated when the termination message budget is -exhausted. +bypassing the 12 KB limit. This fallback is opt-in, is not enabled by +default, and inherits the operational and scaling trade-offs of +sidecar-log results. It is not the default answer for large reports. +Without sidecar-logs, step-emitted notices remain best-effort and may be +truncated when the termination message budget is exhausted. Workloads that +produce large, structured reports should publish those reports as +artifacts and use notices only for bounded summary warnings. ### Budget Priority diff --git a/teps/README.md b/teps/README.md index 29b9b3587..f3559e06f 100644 --- a/teps/README.md +++ b/teps/README.md @@ -151,4 +151,4 @@ This is the complete list of Tekton TEPs: |[TEP-0162](0162-event-based-pruning-of-tekton-resources.md) | event based pruning of tekton resources | proposed | 2025-06-18 | |[TEP-0163](0163-profilebased-dynamic-compute-resources-for-steps.md) | Profile-Based Dynamic Compute Resources for Steps | proposed | 2025-09-01 | |[TEP-0164](0164-tekton-kueue-integration.md) | Tekton Kueue Integration | proposed | 2026-01-28 | -|[TEP-0166](0166-task-notices-and-warnings.md) | Task Notices and Warnings | proposed | 2026-05-11 | +|[TEP-0166](0166-task-notices-and-warnings.md) | Task Notices and Warnings | proposed | 2026-06-09 | From d0423e006b8f6263d07036c59ec6f62f2be045f0 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Mon, 15 Jun 2026 16:03:10 +0530 Subject: [PATCH 8/8] docs: narrow TEP-0166 phase 1 scope --- teps/0166-task-notices-and-warnings.md | 253 ++++++++++++++----------- 1 file changed, 141 insertions(+), 112 deletions(-) diff --git a/teps/0166-task-notices-and-warnings.md b/teps/0166-task-notices-and-warnings.md index b9b5bbb5d..1b8154f04 100644 --- a/teps/0166-task-notices-and-warnings.md +++ b/teps/0166-task-notices-and-warnings.md @@ -25,12 +25,11 @@ authors: - [Proposal](#proposal) - [Overview](#overview) - [Notice Type Definition](#notice-type-definition) - - [Emitting Notices from Steps](#emitting-notices-from-steps) - [Emitting Notices from Controllers](#emitting-notices-from-controllers) - - [Notice Aggregation in PipelineRun Status](#notice-aggregation-in-pipelinerun-status) - - [Plain Text Fallback](#plain-text-fallback) + - [Deferred: Emitting Notices from Steps](#deferred-emitting-notices-from-steps) + - [Deferred: Notice Aggregation in PipelineRun Status](#deferred-notice-aggregation-in-pipelinerun-status) - [Notes and Caveats](#notes-and-caveats) -- [Design Details](#design-details) +- [Deferred Step-Emitted Notice Design Details](#deferred-step-emitted-notice-design-details) - [Entrypoint Collection](#entrypoint-collection) - [Reconciler Processing](#reconciler-processing) - [Size Limits and Termination Message Budget](#size-limits-and-termination-message-budget) @@ -48,7 +47,12 @@ authors: - [Performance](#performance) - [Risks and Mitigations](#risks-and-mitigations) - [Drawbacks](#drawbacks) -- [Alternatives](#alternatives) +- [Alternatives and Explored Ideas](#alternatives-and-explored-ideas) + - [StepState Notices and Flattened TaskRun Notices](#stepstate-notices-and-flattened-taskrun-notices) + - [Condition Message Count Updates](#condition-message-count-updates) + - [Configurable Notice Caps](#configurable-notice-caps) + - [Additional Notice Fields and Levels](#additional-notice-fields-and-levels) + - [Plain Text Fallback](#plain-text-fallback) - [Results with Reserved Prefix](#results-with-reserved-prefix) - [Kubernetes Events](#kubernetes-events) - [Annotations on TaskRun](#annotations-on-taskrun) @@ -58,7 +62,7 @@ authors: - [Implementation Plan](#implementation-plan) - [Phase 1: Controller Notices and Core API (alpha)](#phase-1-controller-notices-and-core-api-alpha) - [Phase 2: Step-Emitted Notices (alpha)](#phase-2-step-emitted-notices-alpha) - - [Phase 3: PipelineRun Aggregation](#phase-3-pipelinerun-aggregation) + - [Future Phase: PipelineRun Aggregation](#future-phase-pipelinerun-aggregation) - [Phase 4: Graduation to Beta](#phase-4-graduation-to-beta) - [Test Plan](#test-plan) - [Infrastructure Needed](#infrastructure-needed) @@ -83,24 +87,20 @@ overwritten by the final Succeeded state. This gap prevents CI/CD systems built on Tekton (such as Konflux, Pipelines as Code) from surfacing warnings as GitHub Check Run annotations, PR comments, or dashboard alerts. -This TEP introduces: +This TEP's implementable Phase 1 introduces: - A `Notice` type with `level`, `message`, optional `step`, and optional source location fields (`file`, `startLine`) - A `notices` field on `TaskRunStatusFields` for task-level notices -- A `notices` field on `StepState` for per-step notices - Controller-emitted notices written directly to status (no termination message path) -- Per-step notice directory at `/tekton/steps//notices/` using - the existing step metadata directory infrastructure (no new volume mount) -- Entrypoint collection of notices alongside results -- PipelineRun summary aggregation of notice counts from child TaskRuns -The implementation is phased: Phase 1 ships the API and controller-emitted -notices (~200 LOC, no entrypoint changes). Phase 2 adds step-emitted -notices with honest termination message budget assessment. +The following designs remain documented, but are explicitly deferred until +Phase 1 has usage data: per-step notice files, entrypoint collection, +termination-message transport, sidecar-log fallback, PipelineRun summary +aggregation, and alternate API shapes such as `StepState.Notices`. -**Example TaskRun status with notices:** +**Example TaskRun status after the deferred step-emitted-notices phase:** ```yaml status: @@ -183,17 +183,14 @@ Downstream platforms built on Tekton are directly impacted: ### Goals -- Steps and controllers can emit structured notices (warnings, info) that - persist in TaskRun status after completion. -- Notices are available at both the step level (`stepState.notices`) and - the task level (`taskRunStatus.notices`) for flexible consumption. -- The mechanism follows existing Tekton patterns (similar to Results via - `/tekton/results/`). +- Controllers can emit structured notices (warnings, info) that persist in + TaskRun status after completion. - Downstream systems (PAC, Konflux, Dashboard, `tkn` CLI) can consume notices from the TaskRun status API without parsing logs. -- PipelineRun status summarizes notice counts from child TaskRuns. - Controller-emitted notices (e.g., verification warnings, deprecated API field usage) have a structured home instead of ephemeral Events. +- Step-emitted notices and PipelineRun notice summaries are documented as + deferred follow-up designs, not Phase 1 requirements. ### Non-Goals @@ -274,36 +271,36 @@ always attributable to a step, so step attribution must remain optional. - The mechanism must not affect the TaskRun or PipelineRun's Succeeded condition. A run with notices is still Succeeded if all steps exit 0. - Notices must persist in TaskRun status (not ephemeral like Events). -- Step-emitted notices must work within the existing termination message - size constraints or use the sidecar-log approach (TEP-0127) for larger - payloads. - Controller-emitted notices bypass the termination message path entirely (written directly to status by the reconciler). -- Step-emitted notices must be attributable to a specific step (the - reconciler populates the `step` field automatically). Controller-emitted - notices have no step attribution. +- Controller notices have no step attribution. - The mechanism should support optional source location (file, line) for VCS annotation use cases. +- Step-emitted notices must work within the existing termination message + size constraints or use the sidecar-log approach (TEP-0127) for larger + payloads if/when that deferred phase is implemented. ## Proposal ### Overview This proposal introduces notices as a first-class concept in the Tekton -API, delivered in two phases: - -**Phase 1: Controller notices and core API.** The `Notice` type and -`notices` fields are added to the API. The reconciler can write notices -directly to TaskRun status (e.g., verification warnings, deprecated field -usage). No entrypoint changes are needed. This immediately unblocks -downstream consumers (PAC, Konflux) that can start consuming -`status.notices`. - -**Phase 2: Step-emitted notices.** Steps write notice files to their -per-step notices directory, following the Artifacts (TEP-0147) pattern. -The entrypoint collects notices and includes them in the termination -message. This phase requires honest budget assessment of the termination -message constraints. +API. The implementable scope is intentionally small: + +**Phase 1: Controller notices and core API.** The `Notice` type and a +`notices` field are added to TaskRun status. The reconciler can write +bounded notices directly to TaskRun status (e.g., verification warnings, +pod rescheduling history, deprecated field usage). No entrypoint changes +are needed. This immediately unblocks downstream consumers (PAC, Konflux, +Dashboard) that can start consuming `status.notices`. + +Everything that requires a step transport is deferred. That includes +`$(step.notices.path)`, entrypoint collection, `NoticeResultType`, +termination-message budgeting, sidecar-log fallback, truncation signaling, +PipelineRun summary aggregation, and alternate API shapes such as +`StepState.Notices`. These designs are preserved below as deferred design +notes and explored alternatives so they can be implemented later without +being part of Phase 1. ### Notice Type Definition @@ -370,7 +367,7 @@ design considered `"error"` for non-fatal errors, but a non-fatal error is a warning by definition. Two levels keep the semantics clean and simplify downstream mapping. -The `notices` field is added to both `TaskRunStatusFields` and `StepState`: +Phase 1 adds the `notices` field to `TaskRunStatusFields`: ```go type TaskRunStatusFields struct { @@ -382,25 +379,18 @@ type TaskRunStatusFields struct { // +listType=atomic Notices []Notice `json:"notices,omitempty"` } - -type StepState struct { - // ... existing fields ... - - // Notices are structured messages emitted by this step. - // +optional - // +listType=atomic - Notices []Notice `json:"notices,omitempty"` -} ``` The `+listType=atomic` marker is consistent with every other status array in the Tekton v1 API (`Steps`, `Results`, `Sidecars`, `ChildReferences`, `RetriesStatus`, `Artifacts.Inputs`, `Artifacts.Outputs`). Tekton does not use Server-Side Apply for status updates, so the list type marker has no -runtime effect. The reconciler reconstructs the entire notices array from -pod termination messages on each reconciliation (single-writer model). +runtime effect. -### Emitting Notices from Steps +A separate `StepState.Notices` field was explored and is documented in +[Alternatives and Explored Ideas](#stepstate-notices-and-flattened-taskrun-notices). + +### Deferred: Emitting Notices from Steps Steps emit notices by writing JSON files to their per-step notices directory. The path is `/tekton/steps//notices/`, which the @@ -478,7 +468,7 @@ notices from accumulating across reconciliation loops. | LimitRange resource adjustment | Silent, no record | Notice records the adjustment | | Result validation failure | Warning Event only | Notice + Event (hybrid) | -### Notice Aggregation in PipelineRun Status +### Deferred: Notice Aggregation in PipelineRun Status PipelineRun status includes a summary `noticeSummary` field that collects notice counts from child TaskRuns, following the @@ -521,31 +511,13 @@ per summary entry, 100 entries consume ~8 KB. Dashboard gets badge data without additional API calls. PAC and Konflux can target-fetch only TaskRuns that actually have notices. -### Plain Text Fallback - -The entrypoint supports a fallback for plain text notice files. When a -file in the notices directory is not valid JSON, the entrypoint wraps -the content as an info-level notice: - -```go -Notice{Level: NoticeLevelInfo, Message: ""} -``` - -The parsing chain is: try `[]Notice`, try single `Notice` object, fall -back to plain text. This follows the same pattern as `ParamValue.UnmarshalJSON` -which has a four-stage fallback chain that never fails. Silently dropping -user data is worse than a degraded parse. The fallback is a convenience -path only: plain text notices have no source location metadata and default -to `info`, so task authors that need VCS annotations or warning severity -should use the JSON format. - ### Notes and Caveats - Notices are **not** included in the TaskRun's Succeeded condition determination. A TaskRun with notices is still `Succeeded: True` if all - steps exited 0. However, the condition **message** includes notice counts - (e.g., `"All Steps completed. 3 warning(s) emitted."`) for visibility in - `kubectl get taskrun` without any API change. + steps exited 0. +- Updating the Succeeded condition message with notice counts was explored + but is deferred; the typed `status.notices` field is the Phase 1 contract. - A future TEP may introduce a `SucceededWithWarnings` condition reason (following the pattern of `PipelineRunReasonCompleted` for runs with skipped tasks). This TEP does not commit to that decision. @@ -566,7 +538,7 @@ should use the JSON format. - When `onError: continue` is set, the entrypoint continues normally after step failure and notices are collected as usual. -## Design Details +## Deferred Step-Emitted Notice Design Details ### Entrypoint Collection @@ -769,24 +741,19 @@ invalid, `"warning"` is valid). ### Reusability -Notices follow the same architectural pattern as Artifacts (TEP-0147): +Phase 1 reuses the existing controller status-update path: the reconciler +adds bounded notices directly to TaskRun status. The deferred step-emitted +design follows the same architectural pattern as Artifacts (TEP-0147): per-step directories under `/tekton/steps//` -> entrypoint -collection -> termination message -> reconciler -> status. This reuses -the existing step metadata directory, entrypoint collection, and status -propagation infrastructure. No new volume mounts are introduced. Task -authors familiar with Results and Artifacts will find Notices intuitive. +collection -> termination message -> reconciler -> status. ### Simplicity -The core user experience is simple: write a JSON file to the step's -notices directory, and the notices appear in the TaskRun status. No -new binaries, sidecars, or configuration is needed for basic usage. +Phase 1 requires no task author action: controller notices appear in +TaskRun status when the feature flag is enabled. -Phase 1 (controller notices) requires no user action at all. - -The API addition is minimal: one new type (`Notice`) and one new field -on two existing types (`TaskRunStatusFields.Notices` and -`StepState.Notices`). +The API addition is minimal: one new type (`Notice`) and one new field on +one existing type (`TaskRunStatusFields.Notices`). ### Flexibility @@ -802,8 +769,8 @@ on two existing types (`TaskRunStatusFields.Notices` and ### Conformance -- This proposal adds new optional fields to the TaskRun and PipelineRun - status. No existing fields are modified. +- Phase 1 adds a new optional field to TaskRun status. No existing fields + are modified. - The `Notice` type introduces no new Kubernetes concepts. It is a plain struct stored in the status subresource. - The API spec would need to document the new `notices` field and the @@ -811,8 +778,8 @@ on two existing types (`TaskRunStatusFields.Notices` and ### User Experience -- **Task authors**: Write JSON to the step's notices directory. Plain - text fallback for convenience. +- **Task authors**: No Phase 1 changes. A deferred phase may add a + step-emitted JSON file path. - **Platform engineers**: Read `taskrun.status.notices` to build integrations (dashboards, VCS annotations, alerting). - **Controller authors**: Append notices directly to status during @@ -824,34 +791,96 @@ on two existing types (`TaskRunStatusFields.Notices` and ### Performance -- **Minimal overhead**: Notice collection adds one directory read per step - (same as results). If no notice files exist, no work is done. -- **Termination message size**: Notices are best-effort after results. - Per-step caps and message limits bound growth. -- **Reconciler**: Notice parsing adds negligible CPU. Controller notice - deduplication is O(n) where n <= 10. +- **Phase 1 overhead**: Controller notice deduplication is O(n) where n <= 10. +- **Deferred step transport**: Notice collection would add one directory + read per step and compete with the termination message budget, which is + why it is not part of Phase 1. ### Risks and Mitigations | Risk | Severity | Mitigation | |------|----------|------------| | **Termination message overflow** | HIGH | Phase 1 uses controller notices (no termination message). Phase 2 uses results-first eviction. Sidecar-log fallback for large payloads. | -| **CR size** | MEDIUM | PipelineRun uses summary counts (~8 KB for 100 tasks) instead of full copies. Per-TaskRun cap of 50 notices. | -| **Abuse (excessively large notices)** | MEDIUM | Message capped at 1024 characters. File path capped at 256 characters. Per-step and per-TaskRun caps. | +| **CR size** | MEDIUM | Phase 1 uses a small controller-notice cap. PipelineRun summaries are deferred. | +| **Abuse (excessively large notices)** | MEDIUM | Message capped at 1024 characters. File path capped at 256 characters. Controller notices capped at 10. | | **Backward compatibility** | LOW | New optional field, defaulting to empty. No behavior change for existing TaskRuns. | -| **Schema bloat** | LOW | One new type, two new fields. Comparable to the recent `Artifacts` addition. | -| **Invalid JSON from step authors** | LOW | Plain text fallback wraps content gracefully. Does not affect step success/failure. | +| **Schema bloat** | LOW | One new type, one new status field. | +| **Invalid JSON from step authors** | LOW | Only applies to the deferred step-emitted transport. | ### Drawbacks - Adds another concept to the TaskRun API surface that users must learn. -- Step-emitted notices compete with Results for termination message space. +- Deferred step-emitted notices would compete with Results for termination message space. - Downstream consumers must be updated to display notices (Dashboard, CLI, PAC, Konflux). The value is only realized when integrations exist. - Phase 1 (controller notices only) delivers partial value. The full step-emitted notice experience requires Phase 2. -## Alternatives +## Alternatives and Explored Ideas + +### StepState Notices and Flattened TaskRun Notices + +The original design stored step-emitted notices in both `StepState.Notices` +and the flattened `TaskRunStatusFields.Notices` list: + +```go +type StepState struct { + // ... existing fields ... + + // Notices are structured messages emitted by this step. + // +optional + // +listType=atomic + Notices []Notice `json:"notices,omitempty"` +} +``` + +This makes per-step display cheap, but duplicates the same notice data in +two status locations and requires the reconciler to keep both arrays in +sync. Phase 1 keeps a single canonical `status.notices` array. A later +step-emitted-notices implementation can re-evaluate whether per-step +storage is worth the extra API surface. + +### Condition Message Count Updates + +The original design included notice counts in the Succeeded condition +message, for example `"All Steps completed. 3 warning(s) emitted."`. +This improves `kubectl get` visibility but changes condition messages and +creates UI/test churn outside the typed API. Phase 1 keeps the condition +semantics unchanged. CLI and Dashboard can add notice display by reading +`status.notices` directly. + +### Configurable Notice Caps + +The original design proposed `max-notices-per-step`. Phase 1 does not add +a new configuration key. Controller notices use a hard-coded small cap; +step-emitted notices can revisit configurability after real usage shows +that fixed caps are insufficient. + +### Additional Notice Fields and Levels + +The original design explored `endLine` and an `error` level. Both are +deferred. Single-line annotations cover the initial linter/warning use +case, and non-fatal errors map cleanly to `warning`. `endLine` is a +backward-compatible addition if a concrete consumer needs ranges later. + +### Plain Text Fallback + +The entrypoint supports a fallback for plain text notice files. When a +file in the notices directory is not valid JSON, the entrypoint wraps +the content as an info-level notice: + +```go +Notice{Level: NoticeLevelInfo, Message: ""} +``` + +The parsing chain is: try `[]Notice`, try single `Notice` object, fall +back to plain text. This follows the same pattern as `ParamValue.UnmarshalJSON` +which has a four-stage fallback chain that never fails. Silently dropping +user data is worse than a degraded parse. The fallback is a convenience +path only: plain text notices have no source location metadata and default +to `info`, so task authors that need VCS annotations or warning severity +should use the JSON format. + ### Results with Reserved Prefix @@ -984,15 +1013,15 @@ wiring. ### Phase 1: Controller Notices and Core API (alpha) -~200 lines of code, no entrypoint changes. +No entrypoint changes. - Add `Notice` type to `pkg/apis/pipeline/v1/notice_types.go` -- Add `notices` field to `TaskRunStatusFields` and `StepState` +- Add `notices` field to `TaskRunStatusFields` - Add `EnableNotices` as `PerFeatureFlag` with `Stability: AlphaAPIFields` in `pkg/apis/config/feature_flags.go` - Implement controller notice emission in TaskRun reconciler (verification warnings, pod rescheduling, affinity overwrite, etc.) -- Include notice count in Succeeded condition message +- Keep Succeeded condition semantics unchanged; expose notices through status - Gate all paths behind the feature flag - Unit tests for notice types, validation, deduplication, caps @@ -1007,7 +1036,7 @@ wiring. - Gate behind `-enable_notices` entrypoint flag - Integration tests for step-emitted notices -### Phase 3: PipelineRun Aggregation +### Future Phase: PipelineRun Aggregation - Add `PipelineRunNoticeSummary` type and `noticeSummary` to `PipelineRunStatusFields`