Files
weatherreporter/docs/internal/state.md

5.7 KiB

State Internals

The internal/state package owns filesystem-backed run state: safe path derivation, metadata persistence, prior-report lookup, and read-only report inspection. It does not decide which reports to generate or deliver. For the operator-facing layout and retention procedures, see the operations guide.

Store construction and artifact paths

NewFilesystemStore requires a workspace root and rejects absolute or escaping values for every configured state directory. Paths then validates a run ID and artifact group before deriving all paths from the report's valid start date (YYYY-MM-DD). This keeps a run's artifacts together while making the paths safe to use below the configured workspace.

Artifact Derived location
Module snapshot snapshots/<group>/<date>/modules.<run-id>.json
Metadata snapshots/<group>/<date>/metadata.<run-id>.json
Data package data-packages/<group>/<date>/data_package.<run-id>.yaml
Prompt preparation preflight/<group>/<date>/prompt_preparation.<run-id>.json
Prompt execution snapshots/<group>/<date>/prompt_execution.<run-id>.json
Render preflight preflight/<group>/<date>/render.<run-id>.json
Notification record notifications/<group>/<date>/distributor.<run-id>.json
Managed report reports/<group>/<date>/report.<run-id>.md
Generated text snapshots/<group>/<date>/generated_text.<run-id>.json
Generated-text source and result snapshots/<group>/<date>/generated_text_raw.<run-id>.json and generated_text_result.<run-id>.json
Generated-text render context snapshots/<group>/<date>/render_context.<run-id>.json

The configured notification root separates notification artifacts from report artifacts; single-report notification paths use the report's valid date. Report producers create parent directories as needed and write the report body; state is responsible for the surrounding paths and saved run artifacts.

Batch Distributor notifications are derived separately as notifications/batches/<batch>/<local-date>/distributor.<batch-run-id>.json. Their date is calculated from the batch start in its configured location, and the batch identity and run ID receive the same path-segment validation as single-report artifact identifiers.

Metadata and durable writes

Metadata is the durable inventory for a run. It records its schema version, run identity, generated and valid timestamps, artifact group, source content and provenance, and the module snapshot, data-package, prompt preparation, prompt execution, report, generated-artifact, and notification locations when present. New prompt records use weatherreporter.metadata.v2; historic weatherreporter.metadata.v1 records remain readable and retain their legacy JSON field names when inspected.

BuildMetadataFromBriefingMetadata establishes legacy common fields, while BuildPromptMetadataFromBriefingMetadata establishes the V2 record. The application adds locations only after the corresponding artifacts are produced. SaveMetadata requires the run ID, module snapshot, data package, metadata path, and the matching preparation reference for its schema. The package also saves module snapshots, data packages, prompt preparation and execution records, legacy preflight records, generated-text artifacts, render contexts, and notifications. JSON writes use atomic replacement, so readers do not observe a partially written state file.

Explicit prompt debug storage

PromptDebugWriter is a separate, opt-in boundary for content-rich prompt diagnostics. It is constructed with an explicit absolute operator root, rather than a workspace-derived path. A blank root produces a disabled writer that does not access the filesystem.

Enabled debug captures are grouped as <root>/<report-id>/<valid-date>/<run-id>/ and contain preparation.json and execution.json. The writer rejects symlinks, unsafe path segments, path escape, and non-directory roots; it creates its directories with 0700 and writes files atomically with 0600. Normal state discovery and inspection do not read this root. Its wire records map only approved project-owned fields; credentials and dependency objects are not persisted.

The data package itself follows the shared prompt-input contract. Report text, templates, and external delivery payloads remain owned by their respective packages and integration references.

Prior reports and inspection

FindPriorSnapshot searches metadata rather than guessing from filenames. It only considers an earlier compatible report in the same artifact group and supports the comparison strategies defined by the report request:

  • same_valid_date finds an earlier generated report for the same valid day.

The newest eligible metadata record wins; the current run is excluded. Unreadable or malformed candidate metadata is ignored so a damaged historical record does not block a new run.

ListReports walks saved metadata, returns results ordered newest-first by generation time, and treats a missing snapshots directory as an empty history. LoadMetadataByRunID builds on that inspection path. These APIs are read-only; repairing or pruning stored state is an operational concern.

Boundaries and verification

The package rejects unsafe path components and incomplete metadata before writing. Callers must provide a valid report request, artifact group, and store configuration. Its focused tests cover path derivation, atomic persistence, metadata validation, comparison eligibility, and report listing:

go test ./internal/state

See application orchestration for the order in which these artifacts are created and report templates for the user-facing report contract.