Finish stateless execution cleanup

This commit is contained in:
2026-08-02 00:15:41 +00:00
parent ab9218b124
commit fe176a2abc
6 changed files with 16 additions and 720 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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) {