diff --git a/docs/adr/0001-stateless-execution.md b/docs/adr/0001-stateless-execution.md index 89cf4a8..50ab61a 100644 --- a/docs/adr/0001-stateless-execution.md +++ b/docs/adr/0001-stateless-execution.md @@ -17,9 +17,10 @@ whose recovery and compatibility obligations are disproportionate to the value of an ephemeral weather report. It also exists solely to support local Recent Changes comparison for a rarely used report section. -The accepted [Stateless Execution Roadmap](../roadmap/ephemeral-state.md) -defines the feature scope and implementation plan. This decision records the -durable rationale for the resulting architecture. +The temporary roadmap that defined the feature scope and implementation plan +has been retired under the repository's documentation lifecycle. The +[architecture policy](../policy/architecture.md) defines the resulting system +invariants; this decision records their durable rationale. ## Decision diff --git a/docs/roadmap/ephemeral-state.md b/docs/roadmap/ephemeral-state.md deleted file mode 100644 index a11bb2c..0000000 --- a/docs/roadmap/ephemeral-state.md +++ /dev/null @@ -1,324 +0,0 @@ -# Stateless Execution Roadmap - -Status: Implemented. - -## Purpose - -Weatherreporter should be a stateless report transformation pipeline. Each -invocation fetches current inputs from the Weather API, derives deterministic -facts, executes Promptkit, renders Markdown, and writes or distributes the -completed report. It does not retain application-owned history or operational -state between invocations. - -Weather reports are ephemeral products rather than business records. Forecasts -and current conditions change continuously, and the normal response to an old -or failed report is to generate a new one rather than recover, replay, or -inspect the prior generation. - -This roadmap defines the desired lifecycle, output contract, compatibility -policy, and architectural boundaries. - -## User Intent - -The target design reflects these product expectations: - -- each invocation is independent and requires no prior Weatherreporter state; -- routine operation creates only the requested report outputs; -- reports are written somewhere useful even when the operator omits an - explicit output flag; -- Weatherreporter does not maintain forecast history merely to compare runs; -- regenerating replaces a same-named output atomically instead of creating an - archive; -- Distributor receives the completed report produced by the current - invocation; and -- sensitive diagnostics are retained only through an explicit, - operator-controlled debug option. - -## Former State - -Before the stateless implementation, each generation wrote a run-addressed collection containing a -module snapshot, data package, prompt preparation receipt, prompt execution -receipt, raw generated text, validated generated text, render context, managed -report, metadata, and optional notification receipt. Successful and failed -runs accumulated beneath the configured workspace. - -Metadata linked these artifacts and supported lookup by RunID. The CLI could list -historical runs and inspect their metadata, modules, data packages, prior -snapshots, and source provenance. The reader retained V1 compatibility while -new runs wrote V2 metadata. - -Weatherreporter also loaded a compatible prior module snapshot for Daily, -Today, and Tomorrow and compared it with the current snapshot. Configurable -thresholds determined whether structured Recent Changes were included in the -Promptkit data package. This was the only product behavior that depended on -state from an earlier invocation. - -## Desired End State - -Weatherreporter has no durable application-managed workspace. Its dataflow is: - -```text -Weather API input - -> deterministic facts and modules - -> Promptkit data package and generated text - -> repository-owned Markdown rendering - -> operator-owned report output - -> optional Distributor upload -``` - -Intermediate values remain in memory wherever practical. A narrowly scoped -temporary file may be used when an external interface requires a file path, -but it exists only for the active invocation and is removed on return. -Temporary material is not a supported recovery or inspection surface. - -RunIDs may remain as in-process correlation identifiers in action summaries, -errors, Distributor idempotency values, and explicit debug paths. They do not -identify a collection that Weatherreporter can locate or decode later. - -Configuration files, operator-owned report outputs, and explicitly requested -debug captures are inputs or outputs, not Weatherreporter state. - -## Report Output Contract - -Every successful report generation writes one operator-owned Markdown output. -An explicit output option selects its destination. When no explicit destination -is supplied, Weatherreporter uses the process working directory captured at -invocation start. - -Default single-report filenames are: - -| Report | Default output | -| --- | --- | -| Daily | `daily-YYYY-MM-DD.md`, using the report's valid local date | -| Today | `today.md` | -| Tomorrow | `tomorrow.md` | -| Hourly | `hourly.md` | - -`--out PATH` replaces the default destination for `generate`; it no longer -means an extra copy of a separately managed report. Relative paths are resolved -from the invocation's working directory. - -For `run`, `--out-dir PATH` selects the report-output directory and the current -working directory is the default when it is omitted. Existing batch filenames -remain: `today.md`, `tomorrow.md`, and `daily-YYYY-MM-DD.md`. Successful items -in a partially failed batch retain their outputs. - -Output publication is atomic. A completed generation may atomically replace an -existing file at the selected path, but a failed or canceled generation must -not truncate or partially replace it. Weatherreporter does not rotate, archive, -expire, or otherwise manage an output after publication. - -The JSON action summary reports the final output path. It does not expose paths -to transient intermediates. `--quiet` continues to suppress the action summary, -not report creation. - -## Distributor Contract - -Distributor uploads use the completed Markdown output from the active -invocation. There is no separate durable managed-report copy and no workspace -scan. - -For a single report, local output publication completes before notification. -A notification failure leaves the report output intact and returns a safe -error through the active result. It does not create a notification receipt. - -Batches continue to suppress per-report notification and attempt their -batch-level upload only after every planned report succeeds. A report failure -leaves other successful output files intact, skips the batch upload, and -returns an aggregate failure. Distributor status remains active-workflow -information rather than retained Weatherreporter history. - -## Recent Changes Deprecation And Removal - -The local Recent Changes feature was deprecated by this accepted direction and -removed when the stateless execution change landed. Its low practical value did -not justify retaining a historical state subsystem. - -Removal includes: - -- prior-snapshot discovery and compatibility rules; -- the `internal/changes` comparison implementation; -- report-registry comparison strategies and compatible-prior declarations; -- `recent_change` configuration and threshold validation; -- Recent Changes values in application results, prompt inputs, fixtures, and - tests; -- the `recent_changes` Promptkit data-package stanza; and -- prompt instructions that refer to supplied recent changes. - -Removing the prompt-input field was a breaking prompt contract change. The data -package schema advanced from `weatherreporter.data_package.v3` to -`weatherreporter.data_package.v4`, and all four prompt definitions advanced -from version `1.1.0` to `2.0.0` together. Generated-text schemas and report -templates did not change; neither had a direct dependency on Recent Changes. - -Weatherreporter does not retain an empty compatibility field and does not keep -a local fallback comparator. Reports simply omit change commentary after this -feature is removed. - -A future upstream Weather API change product may allow Weatherreporter to -reintroduce structured change commentary without local state. That work is -recorded in [Future Roadmap](future.md#upstream-forecast-change-product) and is -not a prerequisite for this refactor. - -## Debugging - -Prompt inspection before weather collection and prepared execution remain -runtime safety requirements. Preparation details, effective profile and model, -validation outcomes, source warnings, and safe errors remain available to the -active workflow and its action summary where useful. - -The existing `--llm-debug-dir PATH` option remains the explicit exception to -stateless output. When supplied, it may retain rendered prompts, schemas, input -bodies, generated bodies, and effective parameters according to its secure -capture contract. The operator selects and manages that location; -Weatherreporter does not clean or archive it. Credentials remain excluded. - -Adding a broader logging framework or an implicit persistent debug directory -is outside this feature. A future rename or generalization to `--debug` should -be considered separately so this state refactor does not expand the sensitive -capture contract incidentally. - -## Removed State And Inspection Contracts - -The implementation removed: - -- the `workspace` configuration section and its directory settings; -- durable module snapshots, data packages, preparation and execution receipts, - raw and validated generated text, render contexts, run metadata, managed - reports, and notification receipts; -- historical artifact path fields from action results and summaries; -- V1 and V2 metadata models, readers, validators, and compatibility behavior; -- state lookup, prior selection, and run-history discovery; and -- `inspect reports`, `inspect metadata`, `inspect modules`, - `inspect data-package`, `inspect prior`, and `inspect sources`. - -No replacement inspection command or current-state manifest is introduced. -The active action summary, final output, command error, and opt-in debug capture -are the supported diagnostic surfaces. - -State-independent atomic file helpers remain outside a state abstraction. The -implemented architecture does not preserve `internal/state` merely as a -compatibility wrapper after its state responsibilities disappeared. - -## Compatibility And Upgrade Policy - -This was an intentional breaking change to the CLI, configuration, prompt-input, -workspace, and inspection contracts. Weatherreporter does not migrate old V1 -or V2 metadata or other historical artifacts. - -Legacy workspace trees are no longer ignored by the repository. Weatherreporter -must not discover, interpret, or automatically delete them. Release notes and -operations documentation should identify the obsolete configuration and -commands and provide a precise manual cleanup procedure for operators who want -to remove the legacy workspace. - -Existing report files explicitly written through `--out` or `--out-dir` remain -operator-owned and are never treated as legacy workspace material. - -Any release containing this implementation should identify the removed Recent -Changes behavior, configuration fields, inspection commands, artifact paths, -and metadata compatibility; the new default output destinations; and any -operator action required during upgrade. - -## Required Architecture Decision Record - -The implemented state includes an Accepted ADR recording the durable decision to -make Weatherreporter stateless. - -The ADR records: - -- the mismatch between run-addressed provenance storage and ephemeral weather - reports; -- the decision to remove local change detection rather than retain state for a - rarely used report section; -- the transformation-pipeline model and operator-owned output boundary; -- the absence of historical inspection and backward-compatibility guarantees; -- atomic output and failure behavior; -- the separation of explicit debug capture from ordinary execution; -- the upstream-service path for any future forecast comparison; and -- alternatives considered, including the former bounded-current-state design, - time-based retention, and a bounded run history. - -Once accepted, the ADR owns the rationale. Architecture owns the resulting -stateless invariant, while CLI, operations, configuration, integrations, and -focused internal documents own implemented contracts. - -## Scope - -The completed feature includes: - -- removal of local Recent Changes and all prior-run dependencies; -- removal of the durable workspace, artifact persistence, metadata - compatibility, and historical inspection surfaces; -- in-memory or invocation-temporary processing with safe cleanup; -- atomic operator-owned output for every successful report; -- current-working-directory defaults and explicit output overrides; -- preservation of report generation, batch membership, Promptkit execution, - Distributor delivery, action summaries, and opt-in secure debug capture; -- removal or simplification of state-only packages, fields, configuration, - fixtures, tests, and documentation; -- exact-version prompt updates for the breaking input-contract change; -- deterministic offline tests for output atomicity, failure isolation, default - path selection, Distributor sequencing, and absence of durable state; -- an Accepted ADR documenting the decision; and -- canonical documentation describing the implemented contract and clear - release-note requirements for the release that publishes it. - -## Non-Goals - -This feature does not include: - -- the future upstream forecast-change API or reintroduced change commentary; -- a cache, database, archive, retention engine, manifest, or resume mechanism; -- migration or automatic deletion of legacy workspace artifacts; -- retaining a bounded report or snapshot history; -- changing weather derivation, report periods, report membership, generated - prose schemas, profile selection, or the provider model ladder; -- changing Distributor's remote API or bundle-content contract; -- deleting or managing operator-owned report and debug outputs; -- a general logging or observability subsystem; or -- live external services in the default test suite. - -## Safety And Testing Policy - -Tests should emphasize observable stateless behavior rather than removed file -choreography. Important risks requiring durable offline coverage include: - -- a failed or canceled generation truncating or replacing an existing output; -- default paths resolving somewhere other than the invocation working - directory; -- Daily output using the wrong valid local date; -- a batch item overwriting another planned output; -- a notification attempt occurring before its report output is complete; -- one batch failure deleting or corrupting another report's successful output; -- summaries exposing nonexistent transient paths; -- temporary files surviving an ordinary success or handled failure; -- default operation recreating a workspace or historical artifact tree; and -- debug capture leaking credentials or being created without explicit opt-in. - -Tests remain deterministic, offline, credential-free, and use real temporary -directories plus narrow external-boundary fakes. Existing tests whose only -purpose is to preserve removed state, metadata, inspection, or Recent Changes -contracts should be deleted rather than translated into assertions about -private replacement mechanics. - -## Completion Criteria - -The implemented state has these properties: - -- a clean invocation requires no prior Weatherreporter-created files; -- ordinary runs leave only their selected Markdown outputs; -- omitted output flags resolve to the documented filenames in the current - working directory; -- failed and canceled attempts preserve any existing destination file; -- no local prior-run comparison or Recent Changes contract remains; -- no workspace configuration, run-history command, metadata compatibility, or - durable intermediate-artifact contract remains; -- Distributor uses only completed current-invocation outputs; -- debug files are created only through explicit secure capture; -- repeated successful and failed runs do not create application-owned history; -- the test suite proves the stateless, atomic-output, batch, notification, and - security boundaries offline; -- an Accepted ADR records the architectural decision; and -- canonical current-state documentation describes only the implemented - stateless lifecycle. diff --git a/docs/roadmap/future.md b/docs/roadmap/future.md index aabb088..07005a3 100644 --- a/docs/roadmap/future.md +++ b/docs/roadmap/future.md @@ -7,8 +7,8 @@ status; current behavior is documented outside `docs/roadmap/`. Status: Proposed upstream feature request; unimplemented. -Weatherreporter's local Recent Changes feature is deprecated for removal by -the accepted [stateless execution roadmap](ephemeral-state.md). Forecast +Weatherreporter's local Recent Changes feature was removed by the accepted +[stateless execution decision](../adr/0001-stateless-execution.md). Forecast version history and comparison are better owned by the Weather API, where the underlying forecast issuances can be retained and compared consistently for all consumers. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md deleted file mode 100644 index 7fda297..0000000 --- a/docs/roadmap/implementation.md +++ /dev/null @@ -1,381 +0,0 @@ -# Stateless Execution Implementation Plan - -Status: Complete. - -## Purpose - -This plan implements the accepted [Stateless Execution -Roadmap](ephemeral-state.md). The roadmap is authoritative for product intent, -policy choices, and the desired end state. This document records the completed -migration and records the remediation work completed during post-implementation -review. - -Stages 1-18 are complete and are summarized below. - -## Implementation Rules - -1. Keep the repository buildable and `go test ./...` passing after every stage. -2. Follow all policies under `docs/policy/`, especially the architecture, - documentation, and risk-based testing requirements. -3. Preserve the accepted stateless contracts: no application-owned durable - workspace, no local Recent Changes comparison, one operator-owned output per - successful report, atomic publication, and explicit-only prompt debug - capture. -4. Preserve prompt and profile inspection before weather collection, exact - prompt versions, structured-output validation, repository-owned rendering, - deterministic report periods, batch membership, and Distributor upload of - published Markdown files. -5. Keep default tests deterministic and offline. Do not contact live Weather - API, Promptkit provider, or Distributor services. -6. Use behavior-focused regression tests at the narrowest stable boundary. - Avoid tests that merely encode private helper structure or call order. -7. Update canonical documentation in the same stage as any user-visible or - architectural behavior change. Do not create release notes until a release - version is selected. -8. Do not modify or reinterpret the Accepted stateless-execution ADR. A changed - architectural decision would require a new ADR; the remaining work does not - require one. - -## Fixed Remediation Decisions - -The following decisions are complete and require no further product input: - -- A canceled generation must not publish or replace its selected output if - cancellation is observable before atomic publication begins. -- A batch must validate every planned report destination before starting any - report's Promptkit execution or output publication. Dynamic Daily - destinations may be validated after weather collection and batch planning, - because those dates are not known earlier. -- `BatchResult.Total`, `Succeeded`, and `Failed` count reports only. A batch - notification failure changes overall batch status and exit behavior through - the top-level notification result; it does not increment `Failed`. -- Batch report items do not expose per-report notification fields because batch - reports deliberately suppress per-report notification. -- Completed roadmap prose must distinguish the former persistent architecture - from current stateless behavior, and repository hygiene must no longer hide - an accidentally recreated root-level `workspace` directory. - -## Completed Stages - -### Stage 1: Record The Stateless Architecture Decision — Complete - -Added the Accepted ADR recording the stateless transformation pipeline, -operator-owned output boundary, removal of local comparison and historical -inspection, atomic publication, and explicit debug-capture exception. - -### Stage 2: Remove Recent Changes From The Prompt Contract — Complete - -Removed Recent Changes from prompt input, advanced the data package to -`weatherreporter.data_package.v4`, and advanced all four embedded prompts to -exact version `2.0.0`. - -### Stage 3: Delete Dormant Local Comparison Policy — Complete - -Removed the local comparison implementation and `recent_change` configuration; -strict configuration loading now rejects the obsolete field. - -### Stage 4: Establish The Operator-Owned Output Contract — Complete - -Made every successful report publish one atomic Markdown output, added -current-working-directory defaults and explicit destination overrides, and -centralized report output naming. - -### Stage 5: Separate Explicit Debug Capture From State — Complete - -Moved secure opt-in Promptkit diagnostics into `internal/promptdebug` without -introducing implicit diagnostics or ordinary artifact persistence. - -### Stage 6: Remove Notification Persistence — Complete - -Removed notification receipts and changed Distributor delivery to consume the -published operator-owned output from the active workflow. - -### Stage 7: Replace The Persisted Generation Workflow — Complete - -Converted single and batch generation to in-memory orchestration and removed -ordinary persistence of snapshots, prompt packages, generated text, render -contexts, metadata, and managed reports. - -### Stage 8: Remove Historical Inspection And Prior Compatibility — Complete - -Removed the `inspect` command family, historical lookup, prior-run selection, -and metadata compatibility surfaces. - -### Stage 9: Delete The Workspace And State Subsystem — Complete - -Removed `internal/state`, workspace configuration, state-only helpers, and -legacy artifact models. Strict loading rejects the obsolete `workspace` stanza. - -### Stage 10: Consolidate Stateless Behavioral Coverage — Complete - -Replaced state-oriented fixtures with focused offline coverage of generation, -batching, atomic output, partial success, notification ordering, prompt -inspection, summaries, and explicit debug capture. - -### Stage 11: Publish User, Operator, And Policy Documentation — Complete - -Updated the CLI, operations, architecture, configuration, and documentation -policy owners for stateless operation and manual legacy-workspace cleanup. - -### Stage 12: Reconcile Internal And Integration Documentation — Complete - -Updated focused internal and integration documents to describe in-memory -orchestration, published-output notification sources, and the absence of -historical inspection. - -### Stage 13: Run The Repository Exit Gate — Complete - -Ran the complete offline test, race, vet, build, help, formatting, stale-string, -and documentation review gates and marked the initial migration implemented. - -## Stage 14: Prevent Publication After Cancellation — Complete - -### Goal - -Close the cancellation window between successful Promptkit execution and -atomic output publication. - -### Work - -1. In the report workflow, check the active context after validation and - rendering have completed and immediately before calling the atomic file - writer. -2. If `ctx.Err()` is `context.Canceled`, wrap it in a - `promptexec.Canceled` error; if it is `context.DeadlineExceeded`, wrap it in - a `promptexec.DeadlineExceeded` error. Return that classified error through - the existing report-error surface. Preserve `errors.Is` behavior for the - underlying context error; do not collapse either case into a generic - rendering or output error. -3. Do not remove an output that was published before a later cancellation. - Cancellation observed after publication remains subject to the existing - notification and result behavior. -4. Add a focused app regression test whose fake executor returns valid output - but cancels the context before returning. Prove that generation fails and a - pre-existing destination remains byte-for-byte unchanged. This test must not - depend on the fake executor voluntarily returning a cancellation error. -5. Ensure the same workflow check protects every single-report and batch item; - do not add duplicate cancellation logic in CLI or batch orchestration. - -### Tests - -Run: - -```sh -go test ./internal/app -go test ./... -go test -race ./internal/app -git diff --check -``` - -### Exit Gate - -Cancellation observable before publication prevents the atomic write, the -selected destination remains unchanged, and cancellation identity is retained. - -## Stage 15: Preflight Every Planned Batch Output — Complete - -### Goal - -Ensure a structural destination error cannot appear midway through a batch -after earlier reports have already been published. - -### Work - -1. After weather collection and `planBatchRun` have produced the complete - dynamic report set, resolve and validate the output path for every planned - report before executing the first report prompt. -2. Store each validated absolute output path with its planned report for use by - the generation loop. Do not recompute or revalidate destinations inside the - loop. -3. Treat any invalid path, including an existing directory at a report's final - filename, as a batch preflight error. Return before Promptkit execution and - before publication of any batch item. Weather collection may already have - occurred because eligible Daily dates depend on collected coverage. -4. Keep missing parent-directory creation in the atomic publication helper; - preflight must not create report files or introduce a new managed directory - lifecycle. -5. Add a focused app test using a real temporary output directory where a later - planned filename, such as `tomorrow.md`, already exists as a directory. - Assert that prompt inspection still occurs before collection, the executor's - prompt-execution method is never called, and no earlier report output is - created or replaced. -6. Retain the existing independent report-failure behavior after successful - preflight: a provider, validation, rendering, or publication failure for one - report remains an item failure, later items continue, and successful outputs - remain available. - -### Tests - -Run: - -```sh -go test ./internal/app -go test ./internal/fileutil -go test ./... -git diff --check -``` - -### Exit Gate - -Every selected batch destination is validated before any batch report executes, -and a destination collision cannot yield an unreported partial batch. - -## Stage 16: Separate Report Counts From Batch Notification Status — Complete - -### Goal - -Restore coherent batch counters while preserving failed status and non-zero -exit behavior when the batch notification fails. - -### Work - -1. Define and enforce the invariant - `Total == Succeeded + Failed == len(Reports)` after report execution. - `Succeeded` and `Failed` count only report-item statuses. -2. Remove the increment of `BatchResult.Failed` when `notifyBatch` returns an - error. Preserve the failed top-level `BatchNotificationResult`, including - its safe error and identity fields. -3. Update the `RunBatch` wrapper to return `BatchError` when either a report - failed or the top-level batch notification failed. Keep - `RunBatchDetailed` returning the populated result according to its existing - detailed-result contract. -4. Retain CLI behavior in which the batch summary status is failed and the - command exits unsuccessfully for a batch notification failure, even though - all report counters show success. -5. Make `BatchError` use report counts only for report-failure wording and its - existing notification-specific wording when the reports succeeded but the - notification failed. -6. Add focused app and CLI tests for a batch whose reports all publish - successfully but whose batch notifier fails. Assert: - - `Total == Succeeded == len(Reports)` and `Failed == 0`; - - every output remains present; - - the top-level notification status is `failed`; - - the summary status is `failed`; - - the returned error describes notification failure rather than claiming a - report failed; and - - the action exits unsuccessfully. - -### Tests - -Run: - -```sh -go test ./internal/app ./internal/cli -go test ./... -git diff --check -``` - -### Exit Gate - -Batch report counters are internally consistent, while notification failure -still produces a failed summary, safe diagnostic, retained outputs, and -non-zero command result. - -## Stage 17: Remove Impossible Per-Report Notification State — Complete - -### Goal - -Align batch result types and stderr output with the architecture in which -per-report notification is suppressed and delivery is represented once at the -batch level. - -### Work - -1. Remove `NotificationStatus`, `NotificationRunID`, - `NotificationPipelineID`, and `NotificationError` from - `BatchReportResult`. -2. Remove the unreachable copying of `ReportResult.Notification` into a batch - item. Rename `copyBatchReportPaths` to reflect that it copies the current - safe report result fields rather than only paths, or replace it with an - equally clear narrow helper. -3. Remove per-report notification formatting from `writeBatchStatus`. Keep the - top-level `batchNotification` status line and the batch summary - `notification` object unchanged. -4. Search tests and documentation for the removed per-report fields. Delete - stale assertions or descriptions rather than adding compatibility fields; - no backward compatibility is required for this pre-release result cleanup. -5. Retain single-report notification fields and behavior. This stage changes - only batch report items. - -### Tests - -Run: - -```sh -go test ./internal/app ./internal/cli -go test ./... -git diff --check -``` - -### Exit Gate - -Batch items expose only report-generation facts, and all batch delivery state -is represented by the single top-level notification result. - -## Stage 18: Reconcile Documentation And Run The Remediation Exit Gate — Complete - -### Goal - -Remove the remaining documentation and repository-hygiene traces of the former -workspace architecture and verify the remediated implementation as a whole. - -### Work - -1. In `docs/roadmap/ephemeral-state.md`, rename `Current State` to - `Former State`, convert its description of persistence and Recent Changes to - past tense, and reconcile other implementation-future phrasing with the - roadmap's Implemented status. Preserve the roadmap's product intent and - desired end state; do not turn it back into a staged plan. -2. Remove the root-level `/workspace` ignore rule from `.gitignore` and update - its adjacent comment. Legacy workspace cleanup remains an explicit operator - procedure in `docs/operations.md`; removing the ignore rule must not delete - any operator data or add automated cleanup. -3. Review the canonical CLI, operations, architecture, app-orchestration, and - testing documentation for the Stage 14-17 behavior. Update only documents - whose owned contract changed, avoiding duplicate definitions. -4. Search production code, tests, examples, and current-state documentation - for: - - stale per-report batch notification fields; - - report counters that include notification failures; - - output validation performed inside the batch execution loop; - - claims that cancellation can publish an output; - - present-tense descriptions of the removed workspace; and - - active ignore rules or defaults that conceal a workspace tree. -5. Confirm ordinary generation and batch tests use isolated temporary - directories and leave only the selected Markdown outputs unless explicit - prompt debug capture is requested. -6. Run formatting and all repository validation gates. Review the complete - remediation diff for unrelated changes, content leakage, compatibility - shims, and unnecessary abstractions. -7. When every exit gate passes, mark Stages 14-18 and this plan Complete. Keep - the feature roadmap marked Implemented. Do not create, tag, or publish a - release in this plan. - -### Tests - -Run: - -```sh -git diff --name-only --diff-filter=ACM -- '*.go' | xargs -r gofmt -w -go test ./... -go test -race ./... -go vet ./... -go build ./... -go run ./cmd/weatherreporter --help -git diff --check -``` - -Verify all changed repository-relative documentation links resolve and inspect -`git status --short` for an accidentally created root-level `workspace` tree. - -### Exit Gate - -The remediated stateless workflow honors cancellation before publication, -preflights complete batch destinations, reports coherent batch counts, exposes -only reachable notification state, and has documentation and repository -hygiene consistent with the implemented architecture. - -## Open Questions - -None. The roadmap, accepted ADR, and remediation decisions above provide all -product and architectural choices required to implement Stages 14-18. diff --git a/internal/app/app.go b/internal/app/app.go index c7888e3..b270bcb 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -367,7 +367,7 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro result.Reports = append(result.Reports, item) } result.Total = len(result.Reports) - batchNotification, _ := notifyBatch(ctx, req.Config, req.Batch, batchRunID(startedAt, req.Batch), startedAt, result, plannedReports, req.Notifier) + batchNotification := notifyBatch(ctx, req.Config, req.Batch, batchRunID(startedAt, req.Batch), startedAt, result, plannedReports, req.Notifier) if batchNotification != nil { result.Notification = batchNotification } diff --git a/internal/app/batch_notification.go b/internal/app/batch_notification.go index 11a6ef3..bc7403e 100644 --- a/internal/app/batch_notification.go +++ b/internal/app/batch_notification.go @@ -46,31 +46,31 @@ func batchRunID(startedAt time.Time, batch BatchKind) string { return startedAt.UTC().Format(runIDTimestampLayout) + "_" + string(batch) } -func notifyBatch(ctx context.Context, cfg config.Config, batch BatchKind, runID string, startedAt time.Time, result *BatchResult, planned []plannedBatchReport, notifier Notifier) (*BatchNotificationResult, error) { +func notifyBatch(ctx context.Context, cfg config.Config, batch BatchKind, runID string, startedAt time.Time, result *BatchResult, planned []plannedBatchReport, notifier Notifier) *BatchNotificationResult { if !cfg.Notify.Distributor.Enabled { - return nil, nil + return nil } if !cfg.Notify.Distributor.Batch.Enabled { - return nil, nil + return nil } if result == nil { - return nil, fmt.Errorf("batch result is required") + return failedBatchNotificationResult(batchNotificationRequest{}, fmt.Errorf("batch result is required")) } if result.Failed > 0 { return &BatchNotificationResult{ Status: "skipped", Reason: "one or more reports failed", - }, nil + } } req, err := buildBatchNotificationRequest(cfg, batch, runID, startedAt, result.Reports, planned) if err != nil { - return failedBatchNotificationResult(batchNotificationRequest{}, err), err + return failedBatchNotificationResult(batchNotificationRequest{}, err) } batchNotifier, err := resolveBatchNotifier(cfg, notifier) if err != nil { - return failedBatchNotificationResult(req, err), err + return failedBatchNotificationResult(req, err) } notification, notifyErr := batchNotifier.NotifyBatch(ctx, req) @@ -82,9 +82,9 @@ func notifyBatch(ctx context.Context, cfg config.Config, batch BatchKind, runID if wrappedErr != nil { batchResult.Status = "failed" batchResult.Error = wrappedErr.Error() - return batchResult, wrappedErr + return batchResult } - return batchResult, nil + return batchResult } func resolveBatchNotifier(cfg config.Config, notifier Notifier) (batchNotifier, error) {