Refresh internal state and adapter documentation

This commit is contained in:
2026-07-31 01:26:20 +00:00
parent c6f8570474
commit f9eef80233
4 changed files with 210 additions and 459 deletions

View File

@@ -1,134 +1,63 @@
# Distributor Adapter Internals
This document describes the distributor upload adapter in
`internal/adapters/distributor`.
`internal/adapters/distributor` translates a local delivery request into the
Distributor Go client's upload and status calls, then returns a local delivery
result. The external API, authentication, and idempotency contract is owned by
the [Distributor API guide](../integrations/distributor/api.md) and
[Distributor bundle guide](../integrations/distributor/pkg-bundle.md).
## Purpose
## Client construction
The adapter submits generated weatherreporter Markdown reports to a configured
distributor HTTP upload endpoint. It supports one or more file mappings per
upload request. It isolates distributor package types, token-env lookup, upload
client construction, source-bundle file mapping, timeout handling, status
polling, and upload error wrapping from app orchestration.
`Client` holds the endpoint, the name of the environment variable containing
the token, an optional timeout, and an injectable upstream-client factory.
`New` validates its configuration before creating the adapter. For each upload,
the adapter reads the token from the configured environment variable and builds
the upstream client with that endpoint, token, and an HTTP client whose timeout
matches the local positive timeout.
## Inputs And Outputs
The upstream client is an implementation dependency, not a source of
application configuration: retry ownership, pipeline selection, path
templates, and report rendering are defined by
[configuration](../config.md) and [application orchestration](app-orchestration.md).
Inputs:
## Upload translation
- distributor endpoint URL
- token environment variable name
- upload timeout
- pipeline ID
- bundle ID
- idempotency key
- source Markdown report paths and bundle-relative path mappings
- bundle created timestamp
- context for cancellation
Before calling the dependency, `Upload` validates the endpoint and token
configuration plus the local pipeline ID, bundle ID, idempotency key, and every
file's source and bundle paths. It maps the request as follows:
Outputs:
| Local request | Distributor client value |
| --- | --- |
| Pipeline ID | Upload pipeline identifier |
| Bundle ID | Bundle identifier |
| Idempotency key | Upload idempotency key |
| File source and bundle paths | Bundle file entries |
| Creation timestamp | Bundle creation time |
- accepted distributor run ID
- accepted distributor upload status
- distributor run status, status polling error, and raw run report JSON when available
- weatherreporter-owned idempotency conflict error when applicable
The call inherits the caller's context and applies the configured positive
timeout. The adapter does not read report files, construct bundle layouts, or
persist notification artifacts.
## Boundaries
## Status and errors
`internal/adapters/distributor` is the only weatherreporter package that imports
`gitea.maximumdirect.net/eric/distributor/pkg/upload` or
`gitea.maximumdirect.net/eric/distributor/pkg/bundle`.
An accepted upload is followed by one status request. When a timeout is
configured, a nonterminal result is polled until `succeeded` or `failed`, or
until the context ends. The translated `UploadResult` contains the run ID,
status, and `RunStatus`, including pipeline ID, lifecycle timestamps, report,
and remote error details.
The app layer passes weatherreporter-owned request values to the adapter. The
adapter does not choose report types, render templates, select output copies,
decide whether an upload represents one report or a batch, configure
destinations, wait for downstream publication, transform Markdown, or persist
notification state.
Status lookup or polling errors are preserved in `UploadResult.StatusError` so
the caller can record an accepted-but-unconfirmed delivery. A terminal failed
run returns that result and an error. Upload failures return no result. Upstream
idempotency conflicts become the local `IdempotencyConflictError`, which adds
endpoint, pipeline, bundle, idempotency, and file-path context while redacting
the token.
Full upstream distributor package and HTTP contract details stay under
`docs/integrations/distributor/`.
## Verification
## Config Fields Used
Focused tests cover configuration validation, request mapping, timeouts and
polling, status translation, conflict handling, and token redaction:
The adapter is built from `notify.distributor` config:
- `endpoint`
- `token_env`
- `timeout`
The app layer renders single-report pipeline ID, bundle ID, idempotency key,
and bundle paths from:
- `pipeline_id_template`
- `bundle_id_template`
- `idempotency_key_template`
- report-specific path templates
For batch uploads, the app layer renders pipeline ID, bundle ID, and
idempotency key from `notify.distributor.batch.*`, resolves report-specific
path templates once per included report, and passes the resulting multi-file
request to this adapter.
Report-specific path resolution happens entirely in the app layer. Explicit
`reports.<report>.distributor.path_templates` overrides take precedence over
report definition defaults.
The token value is read from the environment variable named by `token_env`
after config loading and `secrets.directory` processing.
## Upload Behavior
The adapter calls distributor `UploadFiles` with one or more file mappings:
- pipeline ID: the rendered distributor workflow selector
- source paths: managed Markdown report paths selected by app orchestration
- bundle paths: rendered bundle-relative report paths for each source
- created: the report or batch generation timestamp
The adapter creates a distributor upload client with the configured endpoint,
bearer token, and timeout-backed HTTP client. It also wraps the upload context
with the configured timeout when the timeout is greater than zero.
After upload acceptance, the adapter polls distributor `Status` for the accepted
run ID until the run reaches `succeeded` or `failed`, or until the configured
timeout expires. It returns the latest status, error text, and raw report JSON in
weatherreporter-owned types so app orchestration can persist them in the
notification debug artifact. Status lookup failures or timeout before a terminal
state are kept as debug status errors on an otherwise accepted upload. A
terminal distributor run status of `failed` is returned as a notification failure
with the status report preserved.
## Failure Behavior
The adapter validates required endpoint, token env name, token value, pipeline
ID, bundle ID, idempotency key, upload files, source paths, bundle paths, and
upload client inputs before uploading.
Upload failures include endpoint, pipeline ID, bundle ID, idempotency key,
source paths, and bundle paths context. Token values are redacted from adapter
errors.
Distributor idempotency conflicts are exposed as a weatherreporter-owned
`IdempotencyConflictError`, so callers do not depend on upstream distributor
types.
## Tests
Inspect:
- `internal/adapters/distributor/client_test.go`
- `internal/app/app_test.go`
- `internal/cli/root_test.go`
Adapter tests use a fake upload client factory and do not require a live
distributor service.
## Invariants
- Distributor package types do not leak outside the adapter.
- Only managed Markdown report paths selected by app orchestration are
uploaded.
- The adapter never scans the workspace.
- Token values are not included in errors, CLI output, metadata, docs, or
examples.
- Destination routing and Markdown-to-HTML transformation belong to
distributor, not weatherreporter.
```sh
go test ./internal/adapters/distributor
```

View File

@@ -1,110 +1,51 @@
# Scriptorium Adapter Internals
This document describes the subprocess adapter in
`internal/adapters/scriptorium`.
`internal/adapters/scriptorium` translates Weather Reporter render requests to
Scriptorium process arguments and translates process results back to local
types. The external CLI and output contract belongs to the
[Scriptorium integration guide](../integrations/scriptorium.md); prompts,
template inputs, and report ownership remain outside this adapter.
## Purpose
## Request-to-command translation
The adapter runs `scriptorium render` for prompt preflight and `scriptorium run`
for Markdown report generation or structured generated-text output. It isolates
subprocess execution, argv construction, timeout handling, output capture, and
exit-code interpretation from app and domain packages.
`Runner` accepts a binary, config path, profile, timeout, extra arguments, and
an injectable command executor. Its defaults are the `scriptorium` binary and
the real `ExecRunner`. Optional configuration flags are placed before the
operation-specific arguments, and extra arguments are appended last.
## Inputs And Outputs
| Local operation | Required values | Translated arguments |
| --- | --- | --- |
| `Render` | prompt ID, data-package path | `render [--config …] [--profile …] --prompt <id> --input data_package=<path> --format json [extra …]` |
| `Run` | prompt ID, data-package path, output path | `run [--config …] [--profile …] --prompt <id> --input data_package=<path> --out <path> [extra …]` |
| `StructuredRun` | prompt ID, data-package path, output path | Same translation as `Run` |
Inputs:
Blank required values fail before a command starts. The adapter does not add
schema flags or interpret a prompt's payload; it only gives Scriptorium the
named `data_package` input.
- prompt ID
- YAML prompt input data package path
- report output path for `run`
- raw generated-text output path for structured `run`
- configured binary, config path, profile, timeout, and extra arguments
- context for cancellation
## Command execution and result translation
Outputs:
`ExecRunner` uses `exec.CommandContext`, never a shell. A positive configured
timeout creates a child context. Standard output and standard error are
captured independently, each with a 1 MiB limit, and the executed command is
retained for diagnostics.
- argv used for execution
- captured stdout and stderr
- truncation flags for captured output
- exit code
- report output path for `run`
- raw generated-text output path for structured `run`
`RenderResult`, `RunResult`, and `StructuredRunResult` expose the command,
captured output, truncation markers, and exit code. Run results also retain the
requested output path. Exit status zero is successful. A nonzero process exit
returns its result and an error, while a start failure, cancellation, or
deadline failure returns no result and the execution error.
## Boundaries
The adapter does not parse rendered JSON, validate a generated report, write
state, or upload a report. Those responsibilities sit with
[application orchestration](app-orchestration.md), [state internals](state.md), and the
relevant delivery adapter.
`internal/adapters/scriptorium` owns Scriptorium command construction and
subprocess execution. It does not choose report types, build prompt input,
collect weather data, decide workflow order, or persist workflow metadata.
## Verification
The adapter exposes request and result structs for render, Markdown run, and
structured generated-text run operations. State persistence uses state-owned
artifact shapes; app orchestration converts adapter results before saving.
Focused tests cover argument order, validation, bounded capture, timeout and
cancellation handling, and exit-status translation:
## Config Fields Used
- `scriptorium.binary`
- `scriptorium.config_path`
- `scriptorium.profile`
- `scriptorium.timeout`
- `scriptorium.extra_args`
## Commands
Render preflight argv starts with:
```text
scriptorium render --prompt <prompt_id> --input data_package=<path> --format json
```sh
go test ./internal/adapters/scriptorium
```
Report generation argv starts with:
```text
scriptorium run --prompt <prompt_id> --input data_package=<path> --out <path>
```
Structured generated-text argv uses the same `scriptorium run` form, with the
`--out` value set to the raw generated-text JSON artifact path. The adapter
does not add `--format`, schema path, or JSON Schema flags for structured
generation; Scriptorium selects the structured output schema from prompt
configuration.
Configured `--config` and `--profile` flags are inserted after the subcommand
and before prompt-specific arguments. Extra arguments are appended after the
built-in arguments.
## Execution Behavior
The adapter runs commands without shell interpolation. The same private
execution path is used by render, Markdown run, and structured run after
command-specific request validation and argv construction.
When `scriptorium.timeout` is greater than zero, each subprocess call uses a
context with that timeout. Stdout and stderr are captured separately, capped at
1 MiB each, and marked as truncated when the cap is reached.
## Failure Behavior
- Missing prompt ID or data package path returns an error before subprocess
execution.
- Missing run output path returns an error before subprocess execution.
- Subprocess start errors, context cancellation, and timeouts are wrapped with
operation context by the caller-facing method.
- Nonzero render, Markdown run, and structured run exits return the captured
result plus an error containing the exit code and stderr.
## Tests
Inspect:
- `internal/adapters/scriptorium/runner_test.go`
- `internal/app/app_test.go`
- `internal/cli/root_test.go`
## Invariants
- No shell interpolation is used.
- The Scriptorium input name is `data_package`.
- The file at the data package path is YAML produced by `internal/promptinput`.
- Render, Markdown run, and structured run preserve command-specific result
structs.
- Scriptorium-specific flags stay inside adapter and config boundaries.

View File

@@ -1,178 +1,91 @@
# State Internals
This document describes filesystem state in `internal/state`.
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](../operations.md).
## Purpose
## Store construction and artifact paths
`internal/state` owns managed workspace paths, atomic JSON writes, persisted
metadata, prior snapshot lookup, and read-only artifact inspection helpers.
`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.
## Inputs And Outputs
| 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` |
| 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` |
Inputs:
Notification records use their own configured date directory, because they
are not necessarily tied to a report valid period. Report producers create
parent directories as needed and write the report body; state is responsible
for the surrounding paths and saved run artifacts.
- workspace configuration
- resolved report definition and valid period
- module snapshot
- prompt input data package
- preflight artifact
- generated-text raw, run-result, validated text, and render-context artifacts
- rendered report path preparation request
- RunID for inspection lookups
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.
Outputs:
## Metadata and durable writes
- module snapshot JSON path
- prompt input data package YAML path
- render preflight JSON path
- generated-text raw JSON path
- generated-text run-result JSON path
- validated generated-text JSON path
- render context JSON path
- managed Markdown report path
- metadata JSON path
- distributor notification debug artifact paths
- prior comparable snapshot metadata
- loaded module snapshot, data package, generated text, generated-text run
result, or render context
- recent report records for inspection
`Metadata` is the durable inventory for a run. It records its schema version,
run identity, generated and valid timestamps, artifact group and mode, source
content and provenance, and the module snapshot, data-package, preflight,
report, generated-artifact, and notification locations when present.
## Boundaries
`BuildMetadataFromBriefingMetadata` establishes the common fields; the
application adds locations as artifacts are produced. `SaveMetadata` requires
the run ID and the module snapshot, data-package, preflight, and metadata
paths. The package also saves module snapshots, data packages, preflight
records, generated-text artifacts, render contexts, and notifications. JSON
writes use `fileutil.WriteJSONAtomic`, so readers do not observe a partially
written state file.
`internal/state` owns local filesystem layout, path validation, durable writes,
metadata reads, prior lookup, and report listing. It does not fetch weather
data, derive forecasts, build prompt input content, compare module contents,
invoke Scriptorium, import adapter result types, or parse CLI flags.
The data package itself follows the shared
[weather-data contract](weather-data.md). Report text, templates, and external
delivery payloads remain owned by their respective packages and integration
references.
Preflight persistence uses the state-owned `PreflightArtifact` shape. The app
converts adapter render results into that shape before saving.
## Prior reports and inspection
## Config Fields Used
`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:
- `workspace.root`
- `workspace.snapshots_dir`
- `workspace.reports_dir`
- `workspace.data_packages_dir`
- `workspace.preflight_dir`
- `workspace.notifications_dir`
- `same_valid_date` finds an earlier generated report for the same valid day.
- `weekend_window` finds a prior comparable weekend window.
Workspace subdirectories must be relative paths that stay under
`workspace.root`.
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.
## Managed Layout
`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.
Paths are derived from the resolved report definition's artifact group, the
valid-period start date, and the RunID. Filenames put the artifact kind before
the RunID.
## Boundaries and verification
```text
<workspace.root>/
reports/<artifact_group>/<YYYY-MM-DD>/report.<run_id>.md
snapshots/<artifact_group>/<YYYY-MM-DD>/modules.<run_id>.json
snapshots/<artifact_group>/<YYYY-MM-DD>/metadata.<run_id>.json
snapshots/<artifact_group>/<YYYY-MM-DD>/generated_text_raw.<run_id>.json
snapshots/<artifact_group>/<YYYY-MM-DD>/generated_text_result.<run_id>.json
snapshots/<artifact_group>/<YYYY-MM-DD>/generated_text.<run_id>.json
snapshots/<artifact_group>/<YYYY-MM-DD>/render_context.<run_id>.json
data-packages/<artifact_group>/<YYYY-MM-DD>/data_package.<run_id>.yaml
preflight/<artifact_group>/<YYYY-MM-DD>/render.<run_id>.json
notifications/<artifact_group>/<YYYY-MM-DD>/distributor.<run_id>.json
notifications/batches/<batch>/<YYYY-MM-DD>/distributor.<batch_run_id>.json
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:
```sh
go test ./internal/state
```
Metadata is stored beside module snapshots and links the module snapshot, data
package, preflight, report paths, notification path when attempted, and
configured prompt location. For generated-text-template reports, metadata also
records the generated text schema ID and links the raw generated text,
Scriptorium run result, validated generated text, and render context artifacts.
Markdown-report metadata omits those generated-text fields. Report listing
walks metadata files under the snapshots directory.
Batch notification artifacts are stored under the notifications tree rather
than report metadata because they describe a batch-level upload. The date
directory is the batch start date in the effective report timezone.
## Prior Lookup
Prior snapshot lookup reads stored metadata through the shared lookup path and
selects the latest earlier snapshot whose report ID is compatible with the
current report definition.
- Daily Report compares with prior Daily Report snapshots for the same valid
local date.
- Today Report compares with prior Today Report snapshots for the same valid
local date.
- Tomorrow Report compares with prior Tomorrow Report snapshots for the same
valid local date.
- 3-Day Outlook compares with prior 3-Day snapshots for the same valid local
date.
- Weekend Outlook compares with prior Weekend snapshots for the same weekend
window.
- Hourly Report uses the rolling-window comparison strategy and currently
returns no prior snapshot from filesystem lookup.
- Storm Report has no prior lookup because explicit event-window comparison is
not searched by the filesystem store.
## Writes And Inspection
Durable JSON writes use shared atomic file helpers. Generated-text raw and
validated JSON artifacts are written atomically as bytes; generated-text run
result and render context artifacts are written atomically as JSON. Managed
Markdown reports are prepared by creating their parent directory; Scriptorium
writes the report body to the prepared path. Extra Markdown copies are handled
by app orchestration. Distributor notification debug artifacts are written
atomically when notification is attempted and include rendered distributor
pipeline ID, bundle ID, idempotency key, bundle paths, upload status, latest
run status, and redacted errors.
Single-report notification artifacts use schema version
`weatherreporter.distributor_notification.v1` and record one managed source
path plus that source's bundle paths. Batch notification artifacts use schema
version `weatherreporter.batch_distributor_notification.v1` and record:
- `batch`
- `batchRunId`
- `attemptedAt`
- `endpoint`
- `pipelineId`
- `bundleId`
- `idempotencyKey`
- `bundleCreated`
- `includedReports`, each with `reportId`, `runId`, `sourcePath`, and
`bundlePaths`
- `status`
- `upload`
- `runStatus`
- `statusError`
- `error`
Inspection helpers read existing metadata, module snapshot, data package,
generated text, generated-text run result, and render context files. Missing
metadata directories return no inspection records or no prior snapshot rather
than creating state.
## Failure Behavior
- Invalid workspace paths return validation errors.
- Missing required metadata fields prevent metadata writes.
- JSON writes use a temporary file followed by rename where practical.
- Read and decode failures include path context.
- Unknown RunIDs produce an actionable lookup error.
## Tests
Inspect:
- `internal/state/filesystem_test.go`
- `internal/app/app_test.go`
## Invariants
- Managed paths stay under the configured workspace root.
- Artifact grouping comes from report definitions.
- Metadata links artifacts produced for a run.
- Generated-text artifacts live under the snapshots tree beside module
snapshots and metadata.
- Batch notification artifacts live under `notifications/batches` and are not
linked from report metadata.
- Prior lookup is based on structured metadata, not rendered report text.
See [application orchestration](app-orchestration.md) for the order in which
these artifacts are created and [report templates](../templates.md) for the
user-facing report contract.

View File

@@ -1,101 +1,69 @@
# Weather Data Internals
This document describes Weather API ingestion into `weatherdata.Bundle`.
`internal/weatherdata` owns the normalized, wire-independent weather bundle
that passes from collection through rendering and persistence. The Weather API
adapter translates provider responses into these types; its request, response,
and availability contract is documented in the
[Weather API integration guide](../integrations/weatherapi.md).
## Purpose
## Bundle contract
`internal/adapters/weatherapi` fetches normalized weather data from the
configured Weather API and assembles the bundle consumed by forecast derivation
and module builders. Module builders expose normalized current conditions and
weather story context when those sources are available.
`Bundle` has a collection timestamp (`FetchedAt`), source provenance
(`Sources`), and collection-level warnings (`Warnings`). Its product fields are
optional so an allowed missing source can be represented without manufacturing
weather data.
## Inputs And Outputs
| Field | Normalized product |
| --- | --- |
| `Observation` | Station observation |
| `Current` | Current conditions |
| `Hourly` | Hourly forecast periods |
| `Narrative` | Narrative forecast |
| `Alerts` | Active-alert check, including an explicitly empty result |
| `Discussion` | Forecast discussion and its time-range sections |
| `Daily` | Daily forecast periods when supplied |
| `WeatherStory` | Latest weather story |
| `SPCConvectiveOutlooks` | Convective outlook run, discussions, and GeoJSON geometry |
Inputs:
The bundle carries values rather than provider request details. Consumers use
it to construct report facts and data packages; they should not infer a
provider endpoint or retry policy from the normalized types. See
[collection](collect.md) for assembly and
[report templates](../templates.md) for the values exposed to authors.
- `config.Config` with Weather API URL, timeout, format, units, timezone,
precision, and missing-source policy
- HTTP responses using the Weather API `data` envelope
## Source provenance
Outputs:
Every checked source is represented by a `Source` entry. The record identifies
the source (`Name`), request location and query (`Endpoint`, `Query`), fetch
time, provider issue and update times when available, a SHA-256 digest of the
source data, and whether the source was unavailable (`Missing`). Its warnings
stay with that source in addition to the bundle-level warning list.
- `weatherdata.Bundle` with observation, current conditions, hourly forecast,
narrative forecast, active alerts, discussion, latest weather story, source
records, source warnings, and typed SPC convective outlook data when that
optional source is available
- optional saved bundle JSON through app fetch helpers
An empty product can be meaningful checked data. For example, an explicit
empty alerts result is not missing and retains its source hash. A source is
marked missing only when the adapter's missing-source policy treats the
response or parsing failure as unavailable. The policy itself belongs to the
[configuration reference](../config.md).
## Boundaries
## Warning semantics
- The adapter owns HTTP calls, response-envelope handling, source hashing, and
decoding into internal bundle types.
- It does not derive dayparts, resolve report periods, build module values, compare
snapshots, write report state, or invoke Scriptorium.
`SourceWarning` has a source name, stable code, severity, explanatory message,
endpoint, and `CompletenessImpact`. When collection proceeds with a warning,
the same warning appears in `Source.Warnings` and `Bundle.Warnings` so both
local provenance and whole-run consumers see it. A policy that treats a missing
source as an error returns no partial bundle.
## Config Fields Used
Warnings describe data completeness, not rendering or delivery failures.
Those failures are recorded by the application and state layers; see
[application orchestration](app-orchestration.md) and [state internals](state.md).
- `weather_api.base_url`
- `weather_api.timeout`
- `weather_api.format`
- `weather_api.units`
- `weather_api.timezone`
- `weather_api.precision`
- `missing_source.default`
- `missing_source.sources`
## Boundaries and verification
## External Adapters Used
This package defines data shapes and has no HTTP client, configuration loader,
filesystem access, or template behavior. Focused tests cover the normalized
types and the Weather API adapter verifies translation into them:
- Weather API HTTP service
See [Weather API integration](../integrations/weatherapi.md) for the external
contract used by this project.
## State Or Manifest Behavior
The adapter records source name, endpoint, query, fetch time, source timestamps
when available, SHA-256 hash over compact raw `data` JSON, missing status, and
source warnings. Successful `data: null` responses from `/alerts/active`
represent a checked empty active-alert list, not a missing source. Successful
non-null `/outlooks/convective` responses with empty outlook and discussion
arrays represent checked empty outlook data.
`app.FetchAndSaveBundle` can write bundle JSON atomically for inspection.
SPC convective outlook data is stored on
`weatherdata.Bundle.SPCConvectiveOutlooks`. The collected run keeps upstream
run metadata, location identifiers, ordered outlook records, discussion
records, and each outlook's raw GeoJSON geometry. Source provenance for this
payload uses the `spc_convective_outlooks` source name, endpoint
`/outlooks/convective`, the query sent by the adapter, timestamps, and a hash
of the raw `data` object.
## Skip And Resume Behavior
No resume behavior. Optional missing or malformed sources may be omitted,
warned, or treated as errors according to missing-source policy. Hourly forecast
data is required and cannot be skipped.
## Failure Behavior
- Missing or invalid `weather_api.base_url` prevents client construction.
- HTTP errors, response read failures, and envelope decode failures include
endpoint context.
- Missing hourly data or hourly forecasts with no periods fail bundle fetch.
- Optional sources follow missing-source policy.
- Explicit `data: null` from `/alerts/active` produces an empty, non-missing
alert run.
- Explicit `data: null` from `/outlooks/convective` follows optional
missing-source policy.
## Tests
Inspect:
- `internal/adapters/weatherapi/client_test.go`
- `internal/app/app_test.go`
## Invariants
- Weather facts come from normalized source data.
- Full hourly and narrative products are fetched; Go owns report-period
selection.
- Source provenance and warnings remain inspectable downstream.
```sh
go test ./internal/weatherdata
go test ./internal/adapters/weatherapi
```