Document implemented integration contracts

This commit is contained in:
2026-05-29 19:56:33 +00:00
parent 4f530b2b6a
commit 25ad8959a6
2 changed files with 144 additions and 393 deletions

View File

@@ -1,34 +1,17 @@
# weatherreporter Subprocess Integration
# Scriptorium Integration
This document describes the external Scriptorium CLI contract used by
`weatherreporter`.
## Purpose
This document defines the supported subprocess contract for weatherreporter invoking Scriptorium through the public CLI.
`weatherreporter` invokes Scriptorium as a subprocess to preflight prompt input
and generate Markdown reports. This page documents the CLI surface the adapter
uses, not the full Scriptorium product.
This is a CLI contract, not an internal Go package integration.
## Commands Used
## Supported Commands
weatherreporter should invoke:
- `scriptorium run`
- `scriptorium render`
Use `run` for generation.
Use `render` for preflight/debug output without LLM execution.
## Recommended Invocation Shapes
Run:
```bash
scriptorium run \
--prompt <prompt_id> \
--input data_package=<path> \
--out <artifact_path>
```
Render:
Render preflight:
```bash
scriptorium render \
@@ -37,78 +20,78 @@ scriptorium render \
--format json
```
weatherreporter may add:
Report generation:
- `--config <path>`
- `--profile <profile_id>`
- repeatable `--input name=path`
- repeatable `--var name=value`
- runtime overrides when explicitly needed (`--model`, `--llm-base-url`, `--timeout`, etc.)
```bash
scriptorium run \
--prompt <prompt_id> \
--input data_package=<path> \
--out <artifact_path>
```
## Config And Directory Behavior
`weatherreporter` always passes prompt input as
`--input data_package=<path>`. The data package is structured JSON created by
`internal/promptinput`.
weatherreporter can rely on resolved app config or pass explicit paths.
## Configured Arguments
- default config search order:
1. `/usr/local/etc/scriptorium/config.yml`
2. `/etc/scriptorium/config.yml`
- explicit `--config` requires file existence and valid syntax
- CLI flags override config values
The adapter can prepend configured flags before prompt-specific arguments:
## Profile Selection
- `--config <path>` from `scriptorium.config_path`
- `--profile <profile>` from `scriptorium.profile`
Profile selection follows runner behavior:
It appends `scriptorium.extra_args` after the built-in arguments. Extra
arguments are passed directly as argv items.
1. explicit `--profile`
2. prompt `default_profile`
3. error if neither is available
`scriptorium.binary` selects the executable name or path. If unset inside the
adapter, it falls back to `scriptorium`.
weatherreporter should treat prompt/profile IDs as deployment configuration, not hardcoded logic.
## Execution Behavior
## Input And Variable Contract
The adapter runs Scriptorium without shell interpolation. Arguments are passed
through `exec.CommandContext`.
- Inputs use repeated `--input name=path`.
- Input names must match prompt definition input names.
- Variables use repeated `--var name=value` for small metadata values.
- Prefer file inputs for large content.
`scriptorium.timeout` limits each subprocess call when configured. Context
cancellation or timeout returns an execution error.
## Environment Contract
Stdout and stderr are captured separately. Each stream is capped at 1 MiB and
the result records whether truncation occurred.
- Pass through required API-key environment variables referenced by `api_key_env`.
- Never pass raw API keys via CLI arguments.
- Keep subprocess environment scoped to required variables.
## Results
## Output And Error Handling
Render results include:
`run`:
- full argv recorded as `command`
- stdout
- stderr
- exit code
- truncation flags when applicable
- stdout: artifact body unless `--out` is used
- `--out`: writes artifact to file
- stderr: success summary and errors
Run results include the same fields plus the requested output path.
`render`:
`weatherreporter` persists render preflight JSON when orchestration reaches the
preflight save point. The final Markdown artifact is written by Scriptorium to
the `--out` path.
- stdout: prepared-run output unless `--out` is used
- stderr: errors
## Failure Behavior
weatherreporter should capture stdout and stderr separately.
The adapter validates required request fields before starting Scriptorium:
## Exit Status Contract
- prompt ID
- data package path
- output path for `run`
- `0`: success
- `1`: parse/config/load/render/generation/IO/runtime error
- `2`: run completed but validation failed
Nonzero exits return both the captured result and an error containing the exit
code and stderr. A `run` exit code such as `2` is still treated as an error by
the adapter, even if Scriptorium wrote output to the requested artifact path.
A `run` exit code `2` can still produce output (stdout or `--out`).
Subprocess start failures, context cancellation, and timeouts return errors
without fabricating a successful result.
## Security Notes
- Treat generated artifacts and stderr logs as potentially sensitive.
- Avoid logging full rendered prompts by default in production contexts.
- Use controlled output paths and access controls for persisted artifacts.
## Canonical References
- CLI behavior: [CLI reference](https://gitea.maximumdirect.net/eric/scriptorium/docs/cli.md)
- Config behavior: [Configuration reference](https://gitea.maximumdirect.net/eric/scriptorium/docs/config.md)
- Operations and failure handling: [Operations guide](https://gitea.maximumdirect.net/eric/scriptorium/docs/operations.md), [Troubleshooting](https://gitea.maximumdirect.net/eric/scriptorium/docs/troubleshooting.md)
- The adapter does not invoke a shell.
- Generated artifacts, rendered prompt context, stdout, and stderr can contain
operationally sensitive data.
- API keys should be provided through the Scriptorium environment or
Scriptorium configuration, not through `weatherreporter` CLI arguments.

View File

@@ -1,354 +1,122 @@
# weatherapi External API
# Weather API Integration
This document describes the public HTTP API exposed by `weatherapi` for external consumers.
This document describes the external Weather API contract used by
`weatherreporter`.
## Purpose
`weatherreporter` uses a configured Weather API base URL to fetch normalized
weather source data and assemble a `forecast.Bundle`. This is an integration
contract for the project adapter, not a complete public API reference for the
upstream service.
## Base URL
The service is typically served at your deployment host, for example:
`weather_api.base_url` must be an absolute URL. Adapter requests join this base
URL with the endpoint paths listed below. Generation and explicit bundle fetches
fail before any HTTP request when the base URL is empty or not absolute.
- `https://weather.api.rakestrawhome.com`
The HTTP client uses `weather_api.timeout`.
All paths below are relative to the service root.
## Response Envelope
## Common Conventions
### Response envelope
All endpoints return a top-level envelope:
- `data`: endpoint payload or `null` when no current/latest resource is available.
JSON example:
Every response used by the adapter must be JSON with a top-level `data` field:
```json
{
"data": {"...": "..."}
"data": {}
}
```
### Output format
`data: null` is treated as a missing source. Missing optional sources follow the
configured missing-source policy. Missing hourly forecast data fails bundle
fetching because hourly periods are required for report generation.
Supported via `format` query parameter (case-insensitive):
Malformed JSON envelopes, non-2xx statuses, and response read failures include
endpoint context in returned errors. Decode errors include source context when
they fail the fetch; optional malformed sources follow the missing-source policy.
- `json` (default)
- `xml`
- `text`
## Query Parameters
### Units
The adapter sends these query parameters:
Supported via `units` query parameter (case-insensitive):
- `format`: from `weather_api.format`; the implemented configuration requires
`json`
- `units`: from `weather_api.units`
- `precision`: from `weather_api.precision` on observations, current
conditions, hourly forecast, and narrative forecast requests
- `tz`: from `weather_api.timezone` on hourly forecast, narrative forecast, and
discussion requests
- `metric` (default)
- `us`
Alerts do not receive `precision` or `tz`.
Endpoints that include unit-based numeric fields return either metric or US field variants depending on this value.
## Endpoints Used
### Precision
The adapter fetches these endpoints once per bundle:
Supported where documented via `precision` query parameter:
- `/observations`
- `/conditions/current`
- `/forecast/hourly`
- `/forecast/narrative`
- `/alerts/active`
- `/discussion`
- integer range: `0` to `2`
- controls decimal rounding of numeric output fields
`weatherreporter` does not call day-slice forecast endpoints or discussion
subsection endpoints. Report-period selection and daypart summarization happen
inside Go after the full hourly and narrative products are fetched.
### Timezone (`tz` / `TZ`)
## Required And Optional Sources
Supported where documented:
Hourly forecast is required:
- accepted values include:
- IANA timezone names (example: `America/Chicago`)
- common US abbreviations (example: `CDT`, `EST`)
- UTC offsets in `±H`, `±HH`, or `±HH:MM` (example: `-5`, `+09:30`)
- aliases including `Chicago` and `Stl`
- `tz` and `TZ` are treated equivalently
- if both are provided, they must match exactly or the request fails
- `data: null` for `/forecast/hourly` fails the fetch.
- an hourly forecast with no `periods` fails the fetch.
- malformed hourly data fails the fetch.
Timezone affects datetime rendering and day-slice filtering for `/today` and `/tomorrow` forecast routes.
Other fetched sources are optional and follow `missing_source.default` or a
source-specific `missing_source.sources` policy:
### Query validation
- `observations` for `/observations`
- `current` for `/conditions/current`
- `narrative` for `/forecast/narrative`
- `alerts` for `/alerts/active`
- `discussion` for `/discussion`
- Unknown query parameters are rejected with `400 Bad Request`.
- Invalid parameter values are rejected with `400 Bad Request`.
The adapter also creates missing stub source records for `daily` and
`weather_story` because those source slots exist in the internal bundle but are
not fetched from the Weather API.
Error response body follows the service error envelope; exact fields may vary by error type.
Policy behavior:
## Endpoints
- `error`: fail the fetch for that source
- `warn`: omit the source data, add a warning, and continue
- `none`: omit the source data and continue without a warning
## `GET /observations`
## Source Identity
Returns the latest weather observation.
For non-null source payloads, the adapter records:
Query parameters:
- source name
- endpoint path
- query parameters sent
- fetch time
- source issue and update timestamps when present in the payload
- SHA-256 hash of the compact raw `data` JSON
- `units`: `metric` | `us`
- `format`: `json` | `xml` | `text`
- `precision`: `0..2`
Warnings are recorded both on the affected source and on the bundle-level
warnings list.
Response `data` fields:
## Compatibility Assumptions
- `stationId` (string, optional)
- `stationName` (string, optional)
- `timestamp` (RFC3339 datetime, required)
- `conditionCode` (integer WMO code, required)
- `isDay` (boolean, optional)
- `textDescription` (string, optional)
- Metric mode fields:
- `temperatureC`, `dewpointC`, `windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`, `relativeHumidityPercent`, `apparentTemperatureC` (number, optional)
- `windDirectionDegrees` (number, optional)
- US mode fields:
- `temperatureF`, `dewpointF`, `windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`, `relativeHumidityPercent`, `apparentTemperatureF` (number, optional)
- `windDirectionDegrees` (number, optional)
- `presentWeather` (array, optional)
The adapter expects payload fields compatible with the internal forecast bundle
types in `internal/forecast/bundle.go`, including:
## `GET /alerts/active`
- observation timestamps and observation values
- current condition values
- forecast run metadata and `periods`
- active alert run data
- discussion metadata, key messages, and short/long-term sections
Returns the latest active alert run.
Query parameters:
- `units`: `metric` | `us` (accepted; does not materially alter alert payload)
- `format`: `json` | `xml` | `text`
Response `data` fields:
- Weather alert run object from canonical model (includes run metadata and active alerts list).
## `GET /conditions/current`
Returns current conditions synthesized from latest observation/forecast data.
Query parameters:
- `units`: `metric` | `us`
- `format`: `json` | `xml` | `text`
- `precision`: `0..2`
Response `data` fields:
- Common:
- `conditionText` (string, optional)
- `isDay` (boolean, optional)
- `relativeHumidityPercent` (number, optional)
- `windDirectionDegrees` (number, optional)
- Metric mode:
- `temperatureC`, `apparentTemperatureC`, `dewpointC`, `windSpeedKmh` (number, optional)
- US mode:
- `temperatureF`, `apparentTemperatureF`, `dewpointF`, `windSpeedMph` (number, optional)
## Forecast endpoints
- `GET /forecast/hourly`
- `GET /forecast/hourly/today`
- `GET /forecast/hourly/tomorrow`
- `GET /forecast/narrative`
- `GET /forecast/narrative/today`
- `GET /forecast/narrative/tomorrow`
Query parameters:
- `units`: `metric` | `us`
- `format`: `json` | `xml` | `text`
- `precision`: `0..2`
- `tz` or `TZ`: timezone selector
Day-slice routes:
- `/today` returns periods with `period.startTime` in the current calendar day for the resolved timezone.
- `/tomorrow` returns periods with `period.startTime` in the next calendar day for the resolved timezone.
Response `data` fields:
- Run-level:
- `locationId` (string, optional)
- `locationName` (string, optional)
- `issuedAt` (RFC3339 datetime, required)
- `updatedAt` (RFC3339 datetime, optional)
- `product` (string, required; e.g. `hourly`, `narrative`)
- `latitude`, `longitude` (number, optional)
- Metric mode: `elevationMeters` (number, optional)
- US mode: `elevationFeet` (number, optional)
- `periods` (array, required)
- Period fields:
- `startTime`, `endTime` (RFC3339 datetime, required)
- `name` (string, optional)
- `isDay` (boolean, optional)
- `conditionCode` (integer WMO code, optional)
- `textDescription` (string, optional)
- Metric mode (optional):
- `temperatureC`, `temperatureCMin`, `temperatureCMax`, `dewpointC`, `windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`, `apparentTemperatureC`, `cloudCoverPercent`, `probabilityOfPrecipitationPercent`, `precipitationAmountMm`, `snowfallDepthMM`, `uvIndex`, `relativeHumidityPercent`, `windDirectionDegrees`
- US mode (optional):
- `temperatureF`, `temperatureFMin`, `temperatureFMax`, `dewpointF`, `windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`, `apparentTemperatureF`, `cloudCoverPercent`, `probabilityOfPrecipitationPercent`, `precipitationAmountIn`, `snowfallDepthIn`, `uvIndex`, `relativeHumidityPercent`, `windDirectionDegrees`
Notes:
- Narrative periods may omit `conditionCode`.
- Text format uses forecast-specific templates (`hourly` and `narrative`).
## Discussion endpoints
- `GET /discussion`
- `GET /discussion/key-messages`
- `GET /discussion/short-term`
- `GET /discussion/long-term`
Query parameters:
- `units`: `metric` | `us` (accepted; does not materially alter discussion payload)
- `format`: `json` | `xml` | `text`
- `tz` or `TZ`: timezone selector
Response `data` fields:
- `/discussion`:
- `officeId` (string, optional)
- `officeName` (string, optional)
- `product` (string, required)
- `issuedAt` (RFC3339 datetime, required)
- `updatedAt` (RFC3339 datetime, optional)
- `keyMessages` (array of string)
- `shortTerm` (object, optional)
- `longTerm` (object, optional)
- `/discussion/key-messages`:
- `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt`
- `keyMessages` (array of string)
- `/discussion/short-term`:
- `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt`
- `shortTerm` (object, optional)
- `/discussion/long-term`:
- `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt`
- `longTerm` (object, optional)
Discussion section object fields:
- `title` (string, optional)
- `narrative` (string, optional)
- `issuedAt` (RFC3339 datetime, optional)
## Examples
### Observation (JSON, metric)
```http
GET /observations?format=json&units=metric&precision=1
```
```json
{
"data": {
"stationId": "KSTL",
"timestamp": "2026-05-29T14:00:00Z",
"conditionCode": 3,
"isDay": true,
"textDescription": "Partly cloudy",
"temperatureC": 24.4,
"windSpeedKmh": 17.2,
"relativeHumidityPercent": 56.0
}
}
```
### Alerts (JSON)
```http
GET /alerts/active?format=json
```
```json
{
"data": {
"asOf": "2026-05-29T14:00:00Z",
"alerts": []
}
}
```
### Current conditions (JSON, US)
```http
GET /conditions/current?format=json&units=us&precision=1
```
```json
{
"data": {
"conditionText": "Partly cloudy",
"isDay": true,
"temperatureF": 75.9,
"apparentTemperatureF": 76.1,
"windSpeedMph": 10.7,
"relativeHumidityPercent": 56.0
}
}
```
### Forecast narrative (JSON, optional `conditionCode`)
```http
GET /forecast/narrative?format=json&units=metric&precision=1&tz=America/Chicago
```
```json
{
"data": {
"locationId": "nws-lsx-grid-90-74",
"issuedAt": "2026-05-29T10:30:00-05:00",
"product": "narrative",
"periods": [
{
"startTime": "2026-05-29T13:00:00-05:00",
"endTime": "2026-05-29T19:00:00-05:00",
"name": "Today",
"isDay": true,
"textDescription": "Partly sunny, with a high near 81.",
"temperatureC": 27.2,
"windSpeedKmh": 18.0,
"probabilityOfPrecipitationPercent": 10.0
}
]
}
}
```
### Forecast hourly today (text)
```http
GET /forecast/hourly/today?format=text&units=us&precision=1&tz=CDT
```
```text
<plain text forecast output>
```
### Discussion key messages (JSON)
```http
GET /discussion/key-messages?format=json&tz=Chicago
```
```json
{
"data": {
"officeId": "LSX",
"product": "discussion",
"issuedAt": "2026-05-29T09:25:00-05:00",
"keyMessages": [
"Scattered showers possible this evening.",
"Warmer temperatures this weekend."
]
}
}
```
### Invalid timezone error example
```http
GET /forecast/narrative?tz=not-a-timezone
```
```json
{
"error": {
"code": "invalid_parameter",
"message": "tz must be a valid timezone"
}
}
```
The adapter intentionally keeps upstream transport and envelope details inside
`internal/adapters/weatherapi`; downstream packages consume the normalized
bundle.