diff --git a/docs/roadmap/audit.md b/docs/roadmap/audit.md index dc3808d..10540ef 100644 --- a/docs/roadmap/audit.md +++ b/docs/roadmap/audit.md @@ -1,6 +1,6 @@ # Repository Audit Ledger -Status: In progress; Stages 1-8 complete. +Status: In progress; Stages 1-9 complete. This temporary roadmap document is the evidence ledger for the staged audit defined by [the audit plan](audit-plan.md). It records audit evidence and @@ -56,7 +56,16 @@ empty weather-story object is published as available. It also found one low-severity snapshot compatibility defect because validation accepts any nonempty schema version. Default composition, supported-report rules, source-module omission, checked-empty alerts, ordered snapshots, and curated -current/hourly prompt exports otherwise match their contracts. Subsystem +current/hourly prompt exports otherwise match their contracts. Stage 9 found +six medium-severity briefing defects: apparent temperature is +published as heat index, normalized daypart keys can overwrite one another, +planning applies configurable daypart names inconsistently, outdoor-window +ranking ignores snow, ice, and fog, subzero temperatures are described as +positive zero-degree bands, and embedded descriptions labeled as official SPC +definitions have drifted from the current SPC category table. Derived +precipitation timing, deterministic display ordering, missing-data behavior, +SPC overlap and threshold selection, and the intended Today versus +Daily/Tomorrow planning variation otherwise match their contracts. Subsystem conclusions and final disposition remain pending the later stages. ## Baseline Metadata @@ -192,7 +201,7 @@ inventory commands, graph index refresh, and graph architecture inspection. | 6 | Audit weather data acquisition and collection | Complete | | 7 | Audit forecast and fact derivation | Complete | | 8 | Audit module contracts, registry, and source-facing briefing modules | Complete | -| 9 | Audit derived, planning, formatting, and SPC briefing modules | Pending | +| 9 | Audit derived, planning, formatting, and SPC briefing modules | Complete | | 10 | Audit prompt inputs, assets, and neutral execution contracts | Pending | | 11 | Audit Promptkit adaptation and secure prompt debugging | Pending | | 12 | Audit generated-text validation and catalog contracts | Pending | @@ -226,7 +235,7 @@ with evidence about meaningful risks, test ownership, gaps, and duplication. | CLI parsing, output, and exit behavior | `cmd/weatherreporter`, `internal/cli`, representative app-facing CLI tests | 5 | Partly insufficient. Command/flag ownership, paths, one-executor mapping, stream separation, quiet mode, comparison safe errors, and visible result/exit rules are coherent. `AUD-008` records missing signal cancellation, `AUD-009` records late generate date validation, and `AUD-010` records missing assembled non-comparison CLI protection. | | Weather transport and normalized collection | `internal/adapters/weatherapi`, `internal/collect`, `internal/weatherdata` | 6 | Partly insufficient. All eight source requests, required/optional policy, checked-empty products, retryable source statuses, cancellation, normalized provenance, and collection error ownership have focused offline coverage. `AUD-011` through `AUD-015` record unsupported schemes, unsafe response-body diagnostics, an unenforced body-size limit, structurally invalid required hourly periods, and overbroad warmup retries. | | Forecast and fact derivation | `internal/forecast`, `internal/facts` | 7 | Partly insufficient. Half-open selection, stable ordering, alert parsing and clipping, precipitation-window grouping, report-family selection, and SPC checked-empty behavior have focused owners. `AUD-016` through `AUD-019` record unit collapse, overnight alert omission, invalid percentage acceptance, and incomplete ice-text classification. | -| Module and briefing contracts | `internal/module`, `internal/briefing` | 8-9 | Stage 8 is partly insufficient; Stage 9 remains pending. Registry/default composition, report support, missing optional sources, checked-empty alerts, output identity, ordering, and current/hourly prompt curation have focused owners. `AUD-020` through `AUD-022` record inconsistent pointer options, false weather-story availability, and permissive snapshot schema validation. Derived, planning, formatting, and SPC behavior remains assigned to Stage 9. | +| Module and briefing contracts | `internal/module`, `internal/briefing` | 8-9 | Partly insufficient. Registry/default composition, report support, missing optional sources, checked-empty alerts, output identity, ordering, current/hourly prompt curation, precipitation timing, and SPC selection have focused owners. `AUD-020` through `AUD-028` record inconsistent pointer options, false weather-story availability, permissive snapshot schema validation, apparent-temperature mislabeling, daypart-key collisions, inconsistent planning identities, incomplete outdoor hazard scoring, incorrect subzero phrases, and stale official SPC descriptions. | | Prompt inputs, embedded assets, and execution contracts | `internal/promptinput`, `internal/promptassets`, `internal/promptexec` | 10 | Pending | | Promptkit boundary and sensitive debug output | `internal/adapters/promptkit`, `internal/promptdebug` | 11 | Pending | | Generated-text validation | `internal/generatedtext` | 12 | Pending | @@ -1045,6 +1054,219 @@ with evidence about meaningful risks, test ownership, gaps, and duplication. - Related findings: none - Remediation reference: pending +### AUD-023: Apparent temperature is published as heat index + +- Stage: 9 +- Status: candidate +- Severity: medium +- Confidence: high +- Category: correctness +- Area: `internal/briefing.derivedDailySummaryValue` and + `DerivedDailySummaryModule.HeatIndexMaxF` +- Evidence: Daypart derivation aggregates the source's generic + `ApparentTemperatureF` values into `DaypartSummary.ApparentTemperature`. + The daily briefing builder takes the maximum of that range unconditionally + and publishes it as `heat_index_max_f`. There is no heat-indicator, + temperature, humidity, or source-field check that establishes the value as a + heat index. The ordinary focused test uses a hot apparent value and therefore + confirms only the coincidentally valid case; no cold, mild, or wind-chill + case exists. +- Contract at risk: User-facing derived fields must retain the meteorological + meaning of their source values rather than relabel a generic feels-like + temperature as a specific heat hazard measurement. +- Impact: Cold and ordinary days can carry a purported heat-index maximum that + is actually wind chill or another apparent-temperature estimate. Prompt and + render consumers can then make a false heat claim from a correctly collected + source value. +- Recommendation: Preserve the field as apparent temperature with unit-aware + naming, or populate a heat-index field only from an explicit heat-index + source or a documented, validated derivation under heat-appropriate + conditions. +- Test implications: Add hot, mild, and below-freezing apparent-temperature + cases. Assert that only a semantically established heat index receives that + label, while generic apparent temperature remains available under its own + identity. +- Validation: Cold and mild inputs never emit a heat-index claim; hot inputs + retain an accurately named value; focused briefing and downstream schema or + render tests pass. +- Related findings: `AUD-016` +- Remediation reference: pending + +### AUD-024: Normalized daypart keys can silently overwrite summaries + +- Stage: 9 +- Status: candidate +- Severity: medium +- Confidence: high +- Category: correctness +- Area: `buildDerivedDaypartSummariesModule`, `daypartKey`, `normalizedKey`, + and daypart configuration validation +- Evidence: The builder stores every derived daypart in a map keyed by a + lowercase name whose non-alphanumeric runs become underscores. It assigns + without checking whether the key already exists. Configuration requires only + a nonblank name and parseable clocks; it does not require unique names or + unique normalized names. Consequently duplicate names, or distinct names + such as `early morning` and `early-morning`, produce the same key and the + later summary silently replaces the earlier one. Existing tests exercise + ordinary unique default names only. +- Contract at risk: Every configured local-time window selected into derived + facts must remain represented in the briefing module and prompt package. +- Impact: A valid configuration can silently lose a complete daypart's + temperature, precipitation, hazard, and alert facts, causing generated + reports to reason from an incomplete day. +- Recommendation: Define one canonical daypart identity policy and reject + duplicate canonical identities during configuration validation. Retain a + defensive collision error in the module builder so programmatic facts cannot + overwrite data silently. +- Test implications: Add exact-duplicate, case-only, punctuation-normalized, + and non-colliding Unicode/name cases at configuration and builder boundaries. + Assert an explicit error rather than map length alone. +- Validation: Every accepted daypart maps to one distinct output entry, and + every collision is rejected before prompt construction. +- Related findings: `AUD-025` +- Remediation reference: pending + +### AUD-025: Planning applies configurable daypart identities inconsistently + +- Stage: 9 +- Status: candidate +- Severity: medium +- Confidence: high +- Category: correctness +- Area: `buildTodayPlanning`, `buildMorningCommuteOvernightPlanning`, and + `daypartNamed` +- Evidence: Morning, overnight, afternoon, and evening role lookup uses + case-insensitive `daypartNamed`, while the commute/workday loop excludes only + names exactly equal to lowercase `overnight` or `evening`. Daypart names are + configurable nonblank strings, and neither configuration nor derivation + canonicalizes them into semantic roles. A configured `Overnight` window is + therefore used for overnight-watch guidance and also treated as a daytime + commute/workday window; renamed windows silently lose the special planning + role altogether. Focused planning tests use only lowercase default names. +- Contract at risk: Equivalent accepted configuration identities must produce + equivalent planning behavior, and display labels must not double as + inconsistently compared semantic keys. +- Impact: Harmless casing changes can duplicate overnight/evening hazards into + workday concerns, while customized labels can replace real readiness or + late-day analysis with generic fallback text. +- Recommendation: Separate stable semantic daypart roles from display names, + or explicitly constrain and canonicalize the names accepted by + configuration. Apply that policy once across every planning lookup and + exclusion. +- Test implications: Exercise default, mixed-case, and renamed configurations + through Today, Tomorrow, and Daily planning, asserting which windows feed + morning, workday, late-day, and overnight sections. +- Validation: Equivalent daypart configurations yield identical planning + classification, and unsupported role configurations fail explicitly rather + than degrading to fallback prose. +- Related findings: `AUD-024` +- Remediation reference: pending + +### AUD-026: Outdoor-window ranking ignores snow, ice, and fog + +- Stage: 9 +- Status: candidate +- Severity: medium +- Confidence: high +- Category: correctness +- Area: `scoreOutdoorWindow` and Today outdoor planning +- Evidence: Outdoor scoring considers precipitation probability, gust speed, + alert count, heat, and cold. It does not inspect the already-derived snow, + ice, or fog indicators. When one of those indicators occurs without a high + precipitation probability, gust, alert, heat, or cold signal, the daypart + receives score zero and the reason `quiet weather`; it can be selected as the + best outdoor window. The same planning helper file correctly treats snow and + ice as travel concerns and fog as a morning visibility concern, proving the + facts are available. Existing outdoor tests cover precipitation, wind, + alerts, heat, and cold but no indicator-only winter or fog case. +- Contract at risk: Best/worst outdoor guidance and its reasons must account + for material weather constraints already present in the selected daypart. +- Impact: A snowy, icy, or foggy period can be recommended as the day's best + outdoor window and explicitly called quiet, creating misleading planning + advice even when upstream classification is correct. +- Recommendation: Give each supported outdoor hazard an explicit scoring and + reason policy, sharing the indicator vocabulary with planning notes where + appropriate. Document tie behavior and avoid implying safety merely from a + zero score. +- Test implications: Add isolated snow, ice, and fog cases plus mixed hazards + and deterministic ties. Assert both ranking and user-facing reasons. +- Validation: None of the supported hazard-only periods is described as quiet, + and best/worst selection reflects the documented relative policy. +- Related findings: `AUD-019` +- Remediation reference: pending + +### AUD-027: Subzero temperatures are rendered as positive zero-degree bands + +- Stage: 9 +- Status: candidate +- Severity: medium +- Confidence: high +- Category: correctness +- Area: `temperatureBandPhrase`, `temperatureBandIndex`, and derived daypart + temperature trend prose +- Evidence: Both helpers compute a decade with Go integer division toward zero. + Values from `-9` through `-1` therefore receive decade `0`; the absolute + remainder then produces phrases such as `mid 0s` or `upper 0s`, which denote + positive temperatures. The same band index can also distort trend ordering + around zero. Focused phrase tests cover only positive values in the upper + 60s through lower 80s, so realistic cold-weather values are unprotected. +- Contract at risk: Temperature phrases and trend summaries must preserve the + sign and ordering of the underlying Fahrenheit values. +- Impact: A dangerous below-zero forecast can be understated by roughly ten + degrees and described on the wrong side of zero in prompt-facing daypart + summaries. +- Recommendation: Define explicit signed temperature-band semantics, including + conventional `below zero` wording and monotonic band ordering across zero, + rather than applying positive-decade arithmetic to negative values. +- Test implications: Add table-driven values on both sides of every zero-area + boundary, including `-11`, `-10`, `-9`, `-5`, `-1`, `0`, `1`, and `9`, plus + rising/falling trends that cross zero. +- Validation: Every negative input produces unambiguously below-zero wording, + and band indices remain monotonic from colder to warmer values. +- Related findings: `AUD-016` +- Remediation reference: pending + +### AUD-028: Embedded official SPC category descriptions have drifted + +- Stage: 9 +- Status: candidate +- Severity: medium +- Confidence: high +- Category: correctness +- Area: `assets/spc_convective_outlook_definitions.json` and + `spcOutlookBackgroundDefinition` +- Evidence: The embedded asset labels one field `official_description` but + carries no source URL, source date, or revision metadata. The + [current NOAA Storm Prediction Center outlook reference](https://www.spc.noaa.gov/about/outlooks/), + updated March 3, 2026, defines + Thunderstorms as an area with a 10% or higher thunderstorm probability and + describes Marginal through High using current organization, coverage, and + intensity language. Multiple embedded strings differ materially, including + Thunderstorms as only `No severe thunderstorms expected` and Slight as + isolated intense storms that are short-lived or not widespread. Tests lock + the key set and nonblank grammar but do not compare externally owned meaning + or provenance. The CIG vocabulary and conditional-versus-probability warning + otherwise align with the current NOAA explanation. +- Contract at risk: Content presented as an official external definition must + identify and track its authoritative source closely enough to avoid + misrepresenting current risk terminology. +- Impact: Every matching SPC outlook can carry stale or inaccurately attributed + background text into prompts and reports, where users may treat it as NOAA's + current official description. +- Recommendation: Refresh the categorical descriptions from the current SPC + reference, record source URL and revision date beside the asset, and define a + periodic review/update owner. If repository prose is intentionally + interpretive, rename the field so it is not represented as verbatim official + guidance. +- Test implications: Retain schema/key/lookup tests, add required provenance, + and protect stable domain identifiers rather than freezing unsourced prose. + A maintainer review should compare externally owned wording at each update. +- Validation: Every `official_description` is traceable to the recorded + authoritative revision, and repository-authored summaries are labeled as + such; asset and briefing tests pass. +- Related findings: none +- Remediation reference: pending + ## Retained Decisions ### RET-001: Keep the application package as the explicit composition owner @@ -1251,6 +1473,29 @@ distinction is useful prompt and template information and has a focused behavioral test. Reconsider only if the normalized source availability model changes or all downstream consumers gain an equivalent status channel. +### RET-019: Keep Today planning richer than Daily and Tomorrow planning + +Daily and Tomorrow intentionally share one morning, commute/workday, and +overnight planning shape, while Today adds outdoor-window guidance and a +late-day change watch. The distinction follows the report horizon: Today can +act on the remainder of the current day, whereas Daily and Tomorrow retain a +compact next-day readiness structure. Builders reuse the common planning +helper and focused tests protect both shapes. This is meaningful product +variation rather than drift. Reconsider only if report schemas intentionally +converge or a new horizon requires the same additional planning sections. + +### RET-020: Keep SPC outlook records separate from selected briefing digests + +The SPC outlook module preserves all selected overlapping outlook records and +adds background definitions only for known type/label pairs. Its risk digest +then applies a location-specific categorical severity threshold, while the +discussion module separately selects discussion days from qualifying +categorical outlooks. Focused tests cover type, rank, location, day, overlap, +unknown definitions, and checked-empty behavior. Keeping the complete selected +record set distinct from narrower editorial digests preserves evidence without +forcing every consumer through one filter. Reconsider only if SPC product +semantics or the report contract adopts a single shared selection policy. + ## Open Questions No Stage 1 open questions or unexplained baseline failures remain. @@ -1387,6 +1632,23 @@ Stage 8 routed these investigation leads to their assigned later stages: should verify the complete serialized prompt boundary and intended curation; Stage 8 accounts only for their source-to-module transformations. +Stage 9 routed these investigation leads to their assigned later stages: + +- The derived and planning modules amplify the unit collapse in `AUD-016`, the + overnight-alert omission in `AUD-017`, invalid percentages in `AUD-018`, and + incomplete ice classification in `AUD-019`. Stage 9 did not duplicate those + upstream causes; remediation and later end-to-end tests should protect the + downstream wording and thresholds as well. +- `titleWord` uppercases the first byte rather than the first rune, and several + daypart-oriented helpers repeat display and semantic-name handling. The + consequential identity behavior is captured by `AUD-024` and `AUD-025`; + Stage 23 should assess Unicode-safe shared formatting and removal of repeated + day-style policy without turning display labels into domain identities. +- Prompt serialization and rendering of the newly audited fields remain with + Stages 10 and 13. Stage 24 should decide where the externally maintained SPC + definition source and review date belong in canonical documentation after + `AUD-028` is remediated. + ## Stage Log ### Stage 1: Establish The Baseline And Audit Ledger @@ -1784,3 +2046,58 @@ Stage 8 routed these investigation leads to their assigned later stages: - Retained decisions: `RET-016`, `RET-017`, and `RET-018`. - Open questions: the four leads recorded above are routed to their assigned later stages. + +### Stage 9: Audit Derived, Planning, Formatting, And SPC Briefing Modules + +- Status: Complete. +- Scope reviewed: the remaining production code and focused tests in + `internal/briefing`: daily and daypart summaries, precipitation timing, + outdoor windows, Daily/Today/Tomorrow planning, shared summary and formatting + helpers, SPC outlooks and discussions, and the embedded SPC definition + asset; plus relevant briefing, facts, configuration, and authoritative SPC + references needed to judge the domain rules. +- Exclusions: Complete prompt serialization and embedded prompt assets remain + Stage 10; generated-text schemas and render contexts remain Stages 12-13; + application preparation remains Stage 14; test-portfolio and cross-cutting + cleanup remain Stages 19-23; canonical documentation reconciliation remains + Stage 24; and adversarial dynamic checks remain Stage 25. + +#### Derived And Planning Rule Accounting + +| Module or risk | Threshold, ordering, missing-data, and test evidence | Disposition | +| --- | --- | --- | +| Daily summary | Selects narrative high/low and daily precipitation when present, falls back to daypart aggregates, preserves deterministic conditions/hazards, and emits timing/gust facts. | Ordinary and fallback selection are exercised. Apparent temperature is incorrectly relabeled as heat index in `AUD-023`; unit fallback remains upstream `AUD-016`. | +| Daypart summaries | Emit local periods, ranges, condition/hazard flags, precipitation and gust maxima, temperature phrases/trends, and configured normalized keys; date prefixes prevent ordinary multi-day collisions. | Field and positive-temperature behavior is exercised. Canonical key collisions are `AUD-024`, subzero wording is `AUD-027`, and unit interpretation remains `AUD-016`. | +| Precipitation timing | Maps dry and rainy windows, open-ended periods, expectation thresholds, type phrases, thunder mentions, and local labels without mutating derived order. | Matches the reviewed briefing contract with focused rainy, dry, threshold, phrase, and missing-daypart cases. Percentage-domain defects remain `AUD-018`. | +| Outdoor windows | Scores precipitation, gusts, alerts, heat, and cold; preserves deterministic input-order ties and exposes best/worst periods and reasons to Today planning. | Incomplete hazard policy: snow, ice, and fog can score as quiet; `AUD-026`. | +| Planning variants | Daily and Tomorrow share morning, workday, and overnight sections; Today intentionally adds outdoor and late-day sections. Empty daily summaries produce empty modules or defined fallback prose according to builder context. | Report variation is intentional and retained as `RET-019`. Configurable name classification is inconsistent in `AUD-025`; upstream unit/alert/text defects are not duplicated. | +| User-facing formatting | Time/date labels use the requested location with UTC fallback, wind compass boundaries are focused-tested, strings are deduplicated in stable order, and sentence casing is Unicode-aware. | Ordinary formatting is coherent. Signed temperature bands are incorrect in `AUD-027`; byte-oriented title casing is routed to Stage 23 after the identity defects. | +| SPC outlooks | Preserve selected overlapping records and checked/missing status, attach definitions only for recognized normalized keys, and compute a location-specific categorical risk digest at severity rank 3 or higher. | Selection, ordering, threshold equality, type, location, overlap, unknown definitions, and empty/missing states have focused tests; separation retained as `RET-020`. Official categorical descriptions have drifted in `AUD-028`. | +| SPC discussion | Includes selected discussions only for days with an overlapping categorical outlook meeting the rank threshold; ignores high-rank noncategorical products and nonoverlapping outlooks. | Matches the explicit regional discussion policy with focused threshold, day, type, overlap, missing-text, and missing-source cases. | +| Complexity and repetition | Scoped builders perform small linear maps over configured dayparts, precipitation windows, outlooks, and discussions. The highest production cognitive scores are in planning, precipitation type selection, and outdoor scoring, with no material service-sized cost. | No Stage 9 performance finding. Repeated daypart role/display logic is routed to Stage 23 with concrete evidence. | +| Test ownership | One derived-module suite owns daily/daypart/precipitation/planning behavior, format-helper tests own compass mapping, and dedicated SPC suites own risk/discussion/definition behavior. | Mostly behavioral. Missing semantic regressions are attached to `AUD-023` through `AUD-028`, not inferred from coverage alone. | + +#### Commands And Evidence + +- Used graph symbol and augmented-code search, source snippets, inbound traces, + and complexity queries for every remaining builder/helper, configured + daypart identities, apparent-temperature flow, outdoor hazard inputs, signed + temperature bands, SPC selection/definition lookup, and their focused tests. + Bounded text inspection was used for scoped documents, configuration + examples, and the embedded JSON asset. +- Compared SPC vocabulary and externally attributed definitions with NOAA/NWS + Storm Prediction Center's current convective-outlook reference and the + [2026 Conditional Intensity service-change notice](https://www.weather.gov/media/notification/pdf_2026/scn26-11_SPC_conditional-intensity.pdf). + Categorical definition drift is recorded as `AUD-028`; supported CIG + identifiers and the distinction between occurrence probability and + conditional intensity remain coherent. +- Ran + `go test -coverprofile=/tmp/weatherreporter-stage9-cover.out ./internal/briefing ./internal/config ./internal/forecast ./internal/facts`; + all focused packages passed. Statement coverage was 82.5% for briefing, + 86.1% for config, 89.7% for forecast, and 73.4% for facts; coverage guided + missing-edge inspection but was not itself a finding. +- Findings: `AUD-023`, `AUD-024`, `AUD-025`, `AUD-026`, `AUD-027`, and + `AUD-028`. +- Retained decisions: `RET-019` and `RET-020`. +- Open questions: the three leads recorded above are routed to their assigned + later stages.