255 KiB
Repository Audit Ledger
Status: In progress; Stages 1-15 complete.
This temporary roadmap document is the evidence ledger for the staged audit defined by the audit plan. It records audit evidence and status, not implemented product behavior. Current contracts remain with the canonical owners identified by the documentation policy.
Executive Summary
Stage 1 established a clean, reproducible baseline. The repository-wide test suite, CLI help check, formatting check, and vet check all pass. Stage 2 found that the implemented package graph and principal workflows follow the intended dependency direction and assigned ownership. It recorded one low-severity candidate finding for unused internal persistence helpers left outside the documented stateless workflows. Stage 3 found one medium-severity correctness defect in wall-clock window construction on daylight-saving transition days and one medium-severity test gap around Daily run-ID uniqueness. Report registry identities, command and configuration lookup policies, batch membership, civil-day periods, and rolling-hour periods otherwise match their documented contracts. Stage 4 found three medium-severity validation defects: unsupported missing-source keys are silently accepted, two single-report notification identities may render blank, and programmatically constructed report overrides are silently ignored. It also found one low-severity secret loading atomicity defect. Defaults, file and CLI precedence, strict YAML field handling, active Promptkit settings, output-path preservation, and maintained examples otherwise match their contracts. Stage 5 found two medium-severity CLI correctness defects: operating-system interrupts never become workflow cancellation, and generate validates report dates only after constructing its executor. It also found a medium-severity test gap around assembled generate and ordinary batch result/exit behavior. Command ownership, flag acceptance, output routing, quiet mode, comparison error bounding, and the implemented result-to-exit mapping otherwise follow the documented contract. Stage 6 found four medium-severity defects at the Weather API boundary: unsupported URL schemes pass validation until transport, non-2xx response bodies flow verbatim into normal diagnostics, the nominal response limit silently truncates instead of rejecting oversized bodies, and required hourly products accept periods without usable time bounds. It also found one low-severity retry-policy defect because warmup retries permanent HTTP failures. Source availability policy, checked-empty products, query construction, cancellation, provenance, normalization ownership, and focused offline fixtures otherwise match their contracts. Stage 7 found three medium-severity derivation defects: metric fallback values lose their unit and are compared or presented as US customary values, overnight dayparts omit alerts that begin after midnight, and precipitation probabilities outside the percentage domain flow into summaries. It also found one low-severity text classification defect because ordinary “icy” conditions do not set the ice indicator. Half-open period selection, deterministic ordering, alert parsing, precipitation-window grouping, checked-empty SPC handling, report-family fact selection, and realistic derivation cost otherwise match their contracts. Stage 8 found two medium-severity module-boundary defects: pointer-shaped AFD options pass registry/config validation but fail during construction, and an 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. 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. Stage 10 found two high-severity prompt-contract defects: the shared prompt names nonexistent paths for primary derived facts and alert metadata, and it incorrectly characterizes every retained SPC product as location-matched. It also found three low-severity boundary and contract gaps: raw source-warning types and transport paths cross into provider input, the exported YAML loader accepts several malformed package shapes, and stanza serialization failures lack a focused regression test. Prompt/profile/schema identities, deterministic category ordering, neutral execution lifecycle and error categories, safe bounded errors, debug opt-in, and output-copy isolation otherwise match their contracts. Stage 11 found two medium-severity security defects in the explicit debug path: common credential aliases and URL forms survive heuristic redaction, and pathname-based symlink checks can be raced before sensitive files are created or replaced. Promptkit type containment, source precedence, exact inline execution, credential preflight, cancellation, safe error classification, callback ordering, restrictive modes, atomic file replacement, and ordinary concurrent execution otherwise match their contracts. Stage 12 found one medium-severity catalog defect because known schema/template pairs are accepted for the wrong report, one low-severity schema/decoder mismatch because Go accepts case-variant property names, one medium-severity resource and diagnostic-boundary defect because generated prose and unknown-field errors are unbounded, and one low-severity test maintenance defect from duplicated day-style and misplaced schema assertions. Typed normalization, required-field semantics, immutable schema copies, and the current registry's four identities otherwise match their contracts. Stage 13 found two medium-severity rendering-boundary defects: context builders do not reject conflicting report identities, and unescaped generated prose can create Markdown structure that impersonates deterministic sections. It also found one low-severity missing-value defect because Today can emit an empty daypart heading when every row lacks displayable conditions. Typed module projection, stable daypart ordering, conditional deterministic sections, template/partial lookup, actionable execution errors, and repeated-render determinism otherwise match their contracts. Stage 14 found two medium-severity application-boundary defects: generated-text catalog compatibility is checked only after weather collection and report construction, and execution proceeds without reconciling inspected, prepared, callback, completed-execution, and schema-validation provenance. Side-effect-free prompt/profile inspection, credential preflight, immutable prepared values, one-build comparison reuse, per-execution copies, typed generated-text validation, debug failure handling, and partial execution results otherwise match their contracts. Stage 15 found one medium-severity destination-safety defect because ordinary publication replaces non-regular filesystem objects, one low-severity path handling defect because a valid long output basename makes the temporary name overflow only after expensive work and leaves its newly created directory, and one medium-severity cancellation defect because context is checked before the temporary write rather than at the rename linearization point. Ordinary regular-file replacement, prior-output preservation before rename, restrictive new-file modes, temporary cleanup, committed-path result truthfulness, and post-publication notification semantics otherwise match their contracts. Subsystem conclusions and final disposition remain pending the later stages.
Baseline Metadata
| Item | Recorded baseline |
|---|---|
| Audit date | 2026-08-12 (UTC) |
| Commit | e7c7262404ba0e8e74009ed840d38f7a0142b347 (Add audit workflow plan) |
| Expected commit from audit plan | 151c536cb91ebedb6039270b752bae219dfe33d0 (Add comparison diagnostics to the future roadmap) |
| Baseline difference | The audit uses the current main, one commit ahead of the expected commit. The intervening commit adds the audit workflow plan used to conduct this audit. |
| Branch | main |
| Initial worktree | Clean; git status --porcelain=v1 produced no entries before the audit ledger was created. |
| Go toolchain | go version go1.26.5 linux/amd64; go env GOVERSION reported go1.26.5. |
| Module | gitea.maximumdirect.net/eric/weatherreporter; module file /home/eric/Workspace/weatherreporter/go.mod; go 1.26. |
| Workspace context | Module mode with no go.work; go env GOWORK was empty and the repository contains only ./go.mod. |
| Graph project | home-eric-Workspace-weatherreporter |
| Graph refresh | Refreshed from the baseline checkout in moderate mode without a persisted artifact: 2,674 nodes and 11,572 edges. The indexer excluded docs, examples, embedded-asset directories, and test-data directories, so their inventories below come from tracked files rather than the graph. |
| Baseline exclusions | None. There were no pre-existing worktree changes. |
| Environmental limitations | None encountered. Validation was deterministic and offline. |
Scope And Inventory
Stage 1 inventories the checked-out repository without judging subsystem correctness or test sufficiency. The repository has 219 tracked files. Its Go inventory contains 24 packages, 83 non-test source files, and 50 test files.
Go Packages And Files
| Package directory | Production .go files |
_test.go files |
|---|---|---|
cmd/weatherreporter |
1 | 0 |
internal/adapters/distributor |
1 | 1 |
internal/adapters/promptkit |
1 | 1 |
internal/adapters/weatherapi |
1 | 1 |
internal/app |
10 | 11 |
internal/briefing |
21 | 6 |
internal/buildinfo |
1 | 0 |
internal/cli |
4 | 5 |
internal/collect |
1 | 1 |
internal/comparison |
2 | 2 |
internal/config |
7 | 2 |
internal/facts |
1 | 1 |
internal/fileutil |
1 | 1 |
internal/forecast |
3 | 1 |
internal/generatedtext |
8 | 7 |
internal/module |
1 | 1 |
internal/promptassets |
1 | 1 |
internal/promptdebug |
1 | 1 |
internal/promptexec |
2 | 1 |
internal/promptinput |
1 | 1 |
internal/report |
8 | 1 |
internal/reporttemplate |
2 | 1 |
internal/timeutil |
3 | 2 |
internal/weatherdata |
1 | 1 |
| Total | 83 | 50 |
Fixtures
Nine tracked fixtures are present:
internal/adapters/weatherapi/testdata/alerts.jsoninternal/adapters/weatherapi/testdata/convective_outlooks.jsoninternal/adapters/weatherapi/testdata/current.jsoninternal/adapters/weatherapi/testdata/discussion.jsoninternal/adapters/weatherapi/testdata/hourly.jsoninternal/adapters/weatherapi/testdata/narrative.jsoninternal/adapters/weatherapi/testdata/observations.jsoninternal/adapters/weatherapi/testdata/weather_story.jsoninternal/forecast/testdata/daily_bundle.json
Embedded Assets
The three //go:embed declarations cover 26 tracked assets:
- one SPC definition file under
internal/briefing/assets/; - three Promptkit profiles, ten prompt files, and four generated-text schemas
under
internal/promptassets/assets/; and - four report templates and four template partials under
internal/reporttemplate/templates/.
The exact embedding owners are
internal/briefing/spc_convective_outlook_definitions.go,
internal/promptassets/promptassets.go, and
internal/reporttemplate/reporttemplate.go.
Canonical Documents And Maintained Examples
There are 38 tracked, non-roadmap canonical documents and three maintained example files. The canonical inventory is:
- product and maintainer references:
README.md,docs/cli.md,docs/config.md,docs/development.md,docs/operations.md,docs/release.md, anddocs/templates.md; - policies:
docs/policy/architecture.md,docs/policy/documentation.md, anddocs/policy/testing.md; - architecture decisions:
docs/adr/0001-stateless-execution.md; - integration contracts:
docs/integrations/comparison-bundle.md,docs/integrations/promptkit.md,docs/integrations/weatherapi.md, and the three documents underdocs/integrations/distributor/; - internal documents: all 17 tracked documents under
docs/internal/; and - release notes: the four tracked documents under
docs/releases/.
The maintained examples are examples/config.yml,
examples/minimal-config.yml, and
examples/weather-light-local-profile.yml. Roadmaps are coordination records,
not canonical current-state documents; the four pre-existing roadmap files are
therefore outside the canonical count.
Baseline Validation
| Command | Result | Evidence |
|---|---|---|
go test ./... |
Pass | Exit 0. All 24 packages were evaluated; 22 package test suites passed and cmd/weatherreporter plus internal/buildinfo reported no test files. |
go run ./cmd/weatherreporter --help |
Pass | Exit 0. Help printed usage for help, version, four report generators, two batch commands, and comparison. |
git diff --check |
Pass | Exit 0 with no output on the clean pre-ledger baseline. |
go vet ./... |
Pass | Exit 0 with no diagnostics. |
Supplementary baseline commands were git rev-parse HEAD,
git rev-list --left-right --count 151c536...HEAD,
git status --porcelain=v1, git branch --show-current, go version,
go env GOVERSION GOMOD GOWORK GO111MODULE, go list ./..., tracked-file
inventory commands, graph index refresh, and graph architecture inspection.
Stage Coverage
| Stage | Scope | Status |
|---|---|---|
| 1 | Establish the baseline and audit ledger | Complete |
| 2 | Audit architecture and dependency direction | Complete |
| 3 | Audit report identity and time foundations | Complete |
| 4 | Audit configuration, secrets, and validation | Complete |
| 5 | Audit CLI parsing, wiring, and output contracts | Complete |
| 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 | Complete |
| 10 | Audit prompt inputs, assets, and neutral execution contracts | Complete |
| 11 | Audit Promptkit adaptation and secure prompt debugging | Complete |
| 12 | Audit generated-text validation and catalog contracts | Complete |
| 13 | Audit render contexts, templates, and Markdown rendering | Complete |
| 14 | Audit application preparation and prompt preflight | Pending |
| 15 | Audit single-report generation and atomic output | Pending |
| 16 | Audit batch orchestration and Distributor notification | Pending |
| 17 | Audit comparison contracts and transactional publication | Pending |
| 18 | Audit comparison execution and CLI integration | Pending |
| 19 | Audit test hermeticity and execution hygiene | Pending |
| 20 | Audit test risk coverage and ownership | Pending |
| 21 | Audit test durability, duplication, and maintenance cost | Pending |
| 22 | Audit cross-cutting efficiency and complexity | Pending |
| 23 | Audit cross-cutting refactoring and deduplication opportunities | Pending |
| 24 | Audit documentation coherence and executable contracts | Pending |
| 25 | Run dynamic robustness and final diagnostic validation | Pending |
| 26 | Verify, consolidate, and triage findings | Pending |
| 27 | Produce the remediation roadmap and close the audit | Pending |
Risk-To-Test Coverage
Stage 1 records where tests exist but does not infer sufficiency from package counts or a passing suite. Assigned stages will replace these pending entries with evidence about meaningful risks, test ownership, gaps, and duplication.
| Risk area | Current test owner or evidence source | Audit stage | Coverage assessment |
|---|---|---|---|
| Architecture and dependency direction | Assembled app and CLI tests; graph traces | 2 | Sufficient at the architectural boundary: direct imports are acyclic, external dependency types remain adapter-local, and representative workflow ordering and publication boundaries have focused tests. AUD-001 records unused persistence APIs that do not participate in normal execution. |
| Report identity, periods, dates, and timezones | internal/report, internal/timeutil, plus focused internal/app and internal/forecast callers |
3 | Partly insufficient. Registry identities, alias policies, batch membership, civil-day boundaries, and elapsed six-hour periods are coherent and exercised. AUD-002 records incorrect wall-clock windows on daylight-saving transition days, and AUD-003 records the missing regression test for Daily run-ID uniqueness across dates. |
| Configuration, validation, and secrets | internal/config, focused internal/cli and Promptkit adapter mapping, maintained examples |
4 | Partly insufficient. Defaults, precedence, known-field parsing, active URL/duration/template checks, output-path preservation, profile-source rules, secret redaction, and examples are exercised. AUD-004 through AUD-007 record unsupported missing-source keys, blank notification identities, constructed-report override inconsistency, and non-atomic secret environment mutation. |
| 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 | 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 | Partly insufficient. Prompt/profile/schema identities, deterministic grouping, validation/execution result categories, safe bounded errors, explicit debug, and copy isolation have focused owners. AUD-029 through AUD-033 record raw warning types and transport paths crossing the boundary, stale embedded prompt paths, incorrect SPC locality instructions, permissive YAML loading, and missing serialization-failure coverage. |
| Promptkit boundary and sensitive debug output | internal/adapters/promptkit, internal/promptdebug |
11 | Partly insufficient. Exact prompt/profile inspection, source precedence, inline execution, credential checks, cancellation, safe classified errors, callback ordering, debug opt-in, static symlink rejection, restrictive modes, atomic replacement, and distinct concurrent captures have focused owners. AUD-034 and AUD-035 record incomplete credential redaction and a check/use symlink race. |
| Generated-text validation | internal/generatedtext |
12 | Partly insufficient. The four current registry definitions dispatch to typed validators with coherent required-field and normalization behavior, and schema bytes are independently copied. AUD-036 through AUD-039 record report-independent catalog matching, case-insensitive Go field aliases, unbounded prose/diagnostics, and duplicated or misplaced contract tests. |
| Render contexts and templates | internal/generatedtext, internal/reporttemplate |
13 | Partly insufficient. Typed module extraction, deterministic daypart ordering, report-specific sections, partial conditions, template lookup, and semantic output assertions are coherent. AUD-040 through AUD-042 record conflicting report identities, structural Markdown injection, and Today's empty daypart section. |
| Prompt preflight and prepared inputs | internal/app |
14 | Partly insufficient. Exact prompt input/output shape, profile selection, credentials, immutable preparation, per-execution copies, typed output validation, rendering, callback errors, and partial execution outcomes have focused owners. AUD-043 and AUD-044 record late generated-text catalog validation and missing end-to-end execution-provenance reconciliation. |
| Single-report publication and preservation | internal/app, internal/fileutil |
15 | Partly insufficient. Destination precedence, regular-file publication, same-directory temporary replacement, prior-output preservation, temporary cleanup, restrictive modes, truthful committed paths, and notification-after-publication have focused owners. AUD-045 through AUD-047 record destructive non-regular target handling, late failure for valid long basenames, and cancellation not checked at the rename boundary. |
| Batch partial success and notification | internal/app, internal/adapters/distributor |
16 | Pending |
| Comparison identity and transactional publication | internal/comparison |
17 | Pending |
| Comparison concurrency and CLI behavior | internal/app, internal/cli |
18 | Pending |
| Hermeticity, execution hygiene, portfolio coverage, and durability | Repository-wide suite | 19-21 | Pending |
| Cross-cutting efficiency and maintainability | Graph metrics plus focused tests | 22-23 | Pending |
| Documentation and executable-contract coherence | Canonical documents, code, schemas, templates, examples | 24 | Pending |
| Dynamic robustness and diagnostic checks | Repository-wide deterministic checks | 25 | Pending |
Findings
AUD-001: Unused persistence helpers remain after the stateless redesign
- Stage: 2
- Status: candidate
- Severity: low
- Confidence: high
- Category: architecture
- Area:
internal/promptinput.Save,internal/adapters/weatherapi.SaveBundle, andinternal/fileutil.WriteJSONAtomic - Evidence: Graph inbound traces show
promptinput.Savehas no callers,weatherapi.SaveBundleis called only byTestSaveBundle, andfileutil.WriteJSONAtomicis called only bySaveBundleand its focused test. Repository text search found no documentation or other call sites. Representative generate, batch, comparison, and collection traces do not reach any of these helpers.promptinput.SaveandSaveBundlewrite intermediate prompt input or normalized weather data to arbitrary paths, while normal publication usesfileutil.WriteFileAtomicfor selected Markdown andcomparison.Publishfor selected bundles. - Contract at risk: The architecture policy and ADR 0001 define normal execution as an in-memory stateless transformation whose durable files are operator-selected report outputs, comparison bundles, or explicitly requested secure prompt debugging.
- Impact: These unreachable exported functions do not create state during current workflows, but they retain an unsupported persistence surface and low-value tests that can invite accidental reintroduction of intermediate artifacts or require maintenance despite having no product caller.
- Recommendation: Remove the two unused domain/adapter save functions, remove
WriteJSONAtomicif it then has no production use, and delete or consolidate tests that protect only those retired APIs. - Test implications:
TestSaveBundleandTestWriteJSONAtomicprotect unused persistence mechanisms;promptinput.Savehas no focused test. Preserve tests forWriteFileAtomic, normal report publication, comparison publication, and explicit prompt-debug writes. - Validation: Graph and text searches show no remaining production references
to the retired helpers;
go test ./...passes; normal output, comparison, and debug-publication tests remain unchanged and pass. - Related findings: none
- Remediation reference: pending
AUD-002: Clock windows shift on daylight-saving transition days
- Stage: 3
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/timeutil.ClockWindowandinternal/forecast.ResolveDayparts - Evidence:
ClockWindowobtains the local civil-day start and creates its boundaries with elapsed-duration additions such asday.Start.Add(17 * time.Hour). InAmerica/Chicago, adding 17 elapsed hours to local midnight produces 18:00 on the 2026-03-08 spring transition and 16:00 on the 2026-11-01 fall transition; adding six hours produces 07:00 and 05:00 respectively. Its only production caller isforecast.ResolveDayparts, which uses those boundaries for daily summaries and facts. The focused time test covers only an overnight window in a fixed offset zone, so both transition cases pass unnoticed. - Contract at risk: Configured daypart clocks are local wall-clock boundaries. Civil-day and timezone policy requires those clocks to retain their stated local hour across 23-hour and 25-hour days.
- Impact: On daylight-saving transition dates, observations can enter or leave a configured daypart an hour early or late, shifting derived daily summaries and any downstream facts or report content that consume them.
- Recommendation: Construct each boundary as a civil date plus local clock in
the target location, including explicit handling for
24:00and overnight windows, rather than adding elapsed hours to midnight. - Test implications: Add table-driven
America/Chicagocoverage for both spring-forward and fall-back dates, asserting the requested wall-clock start and end and the intended overnight date rollover. Retain the existing half-open period assertions. - Validation: The new regression test fails against the elapsed-addition
implementation and passes when transition-day boundaries retain their
configured local clocks; focused
timeutilandforecastsuites pass. - Related findings: none
- Remediation reference: pending
AUD-003: Daily run-ID uniqueness lacks a focused contract test
- Stage: 3
- Status: candidate
- Severity: medium
- Confidence: high
- Category: testing
- Area:
internal/report.Resolved.runID,validStartDateRunIDDisambiguator, and focused report tests - Evidence: Daily run IDs append the resolved valid-start date to the shared
generation timestamp and report ID. This distinguishes multiple Daily
reports planned for different dates in one batch, where
GeneratedAtis the same. Focused report tests assert that registry identities are nonempty and app tests use nonempty or fixture run IDs, but no test resolves two Daily dates at one clock instant and asserts distinct IDs. DownstreamplannedReportsByRunIDrejects duplicate IDs before building a batch notification, and Distributor idempotency also consumes the run ID. - Contract at risk: A run ID must identify one resolved report product, while one batch may legitimately contain several Daily products for different valid dates.
- Impact: An accidental removal or weakening of the date disambiguator can give distinct Daily products the same identity, causing batch notification construction to fail and creating Distributor idempotency collisions even though the existing focused suite remains green.
- Recommendation: Add a focused report test that resolves two Daily dates with
the same injected
Now, asserts distinct nonempty run IDs, and verifies that each ID carries the corresponding valid-start date. Prefer this relational contract over freezing unrelated timestamp formatting. - Test implications: The regression must fail if the Daily disambiguator is removed while allowing harmless changes to the common run-ID timestamp representation. An app-level assertion may additionally protect the multi-Daily batch consumer, but should not duplicate the report invariant.
- Validation: Removing the date disambiguator makes the new focused test fail;
with the invariant intact, report and multi-Daily batch tests pass and
plannedReportsByRunIDreceives unique keys. - Related findings: none
- Remediation reference: pending
AUD-004: Unsupported missing-source keys are silently accepted
- Stage: 4
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/config.Validate,MissingSourceConfig.Sources, andinternal/adapters/weatherapi.policyFor - Evidence: The configuration contract lists seven optional source keys and
states that hourly forecast data is always required.
Validatechecks only that each map key is nonblank and its policy is one oferror,warn, ornone; it never checks the key against the supported set. The Weather API adapter looks up policies by its own literal source names and otherwise uses the default. Its required-hourly path returns an error before consulting the policy. Consequently a typo such asalert: error, an arbitrary key, or an ineffectivehourly: noneloads successfully. Focused config tests cover an invalid policy value but no unsupported key. - Contract at risk:
missing_source.sourcesaccepts only the documented optional source identities, and invalid configuration must fail loading rather than silently changing the effective failure policy. - Impact: An operator can believe a source-specific
error,warn, ornonerule is active while runtime collection ignores it and applies the default; the mistake is visible only when that source is missing or malformed. - Recommendation: Give
internal/configone authoritative supported-source set and reject every other key, includinghourly. Keep adapter source names aligned with that owner without introducing a second independently maintained list. - Test implications: Add loaded and constructed configuration cases for every
supported key plus representative typo, unknown, blank, and required-hourly
keys. Assert behavior through
ValidateorLoadFile, not private map iteration. - Validation: The new unsupported-key cases fail against the current validator and pass when validation rejects keys that cannot affect optional-source policy; focused config and Weather API policy tests remain green.
- Related findings: none
- Remediation reference: pending
AUD-005: Single-report notification identities may render blank
- Stage: 4
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
validateDistributorNotify,RenderDistributorBundleID, andRenderDistributorIdempotencyKey - Evidence: Enabled single-report notification validation rejects an empty
template and unknown variables, and
RenderDistributorPipelineIDrejects a whitespace-only result. In contrast, the bundle-ID and idempotency-key renderers return whitespace unchanged and validation does not perform a nonblank result check. Batch renderers consistently reject whitespace-only pipeline, bundle, and idempotency identities. The Distributor adapter checks only equality with the empty string, so whitespace values also pass its local request checks. Focused tests cover the single pipeline case and all three batch cases, but omit the two single-report cases. - Contract at risk: Enabled notification requires usable pipeline, bundle, and idempotency identities; configuration validation must reject invalid active templates before report execution.
- Impact: A whitespace bundle ID or idempotency key can reach Distributor after report publication, risking rejected uploads or collisions between otherwise distinct report deliveries while configuration preflight reports success.
- Recommendation: Apply the same trimmed-nonempty invariant to all three single-report identity renderers and exercise those renderers during active configuration validation.
- Test implications: Extend the existing single-report validation table with whitespace-rendered bundle and idempotency identities, and retain the batch table as the parallel contract owner.
- Validation: Both new cases fail against the current single-report renderers and pass when all active rendered identities are nonblank; focused config, app notification, and Distributor adapter tests pass.
- Related findings: none
- Remediation reference: pending
AUD-006: Constructed report overrides depend on inaccessible presence flags
- Stage: 4
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
ReportConfig,ReportDistributorConfig,Config.ReportModuleOverrides, andConfig.ReportDistributorPathOverrides - Evidence: YAML unmarshalling sets private
deterministicModulesSetandpathTemplatesSetflags to distinguish omission from an explicit list. Both public override accessors skip exportedDeterministicModulesandPathTemplatesvalues unless the corresponding private flag is true. Code outside packageconfigtherefore cannot construct an equivalent override: setting a nonempty exported slice inConfig.Reportsis silently ignored, andValidateskips its contents as well. Tests named for loaded/constructed consistency reside inside packageconfigand manually set the private flags, so they do not exercise the configuration shape available to app callers. Graph traces show the accessors feed app report resolution and single/batch Distributor path rendering. - Contract at risk: Loaded and programmatically constructed configurations with the same exported values should validate and produce the same report module and path overrides.
- Impact: Repository callers and tests that construct a
config.Configcan silently run default modules or Distributor paths instead of their requested values, while invalid constructed overrides can pass validation unused. - Recommendation: Represent presence through an API available to downstream callers, such as constructors/setters or an optional-list representation that preserves omitted versus explicitly empty YAML without private state that exported values cannot establish.
- Test implications: Move or add behavior tests from an external config test package or an app boundary so they can use only exported construction APIs; assert both nonempty overrides and explicit-empty rejection.
- Validation: The external-boundary test fails with direct exported-field construction today and passes when loaded and constructed forms yield the same overrides and validation result.
- Related findings: none
- Remediation reference: pending
AUD-007: Failed configuration loads can leave secrets applied
- Stage: 4
- Status: candidate
- Severity: low
- Confidence: high
- Category: security
- Area:
internal/config.LoadandloadSecrets - Evidence:
LoadcallsloadSecretsbeforeValidate. The secret loader validates and applies directory entries one at a time withos.Setenv, with no preflight of all entries and no restoration of prior environment values. A valid secret is therefore installed even when later configuration validation fails, and an earlier secret remains installed when a later invalid entry, unreadable value, or failed environment assignment aborts the directory load. Existing tests cover successful replacement, line-ending trimming, rejection diagnostics, and omission of secret values from errors, but do not assert failure atomicity. - Contract at risk: A rejected configuration should not partially apply its secret-bearing environment interaction, and repeatable in-process loads should not inherit state from an unsuccessful attempt.
- Impact: The normal CLI process exits after a load failure, limiting the operational effect, but embedded runners, repeated actions, and tests can observe or consume credentials from a configuration that was never accepted.
- Recommendation: Validate non-secret configuration before secret application, preflight and read the complete secret directory before mutation, and either apply environment changes atomically with rollback or return a scoped lookup rather than mutating process-global state incrementally.
- Test implications: Add cases for a valid secret followed by an invalid entry and for valid secrets paired with a later-invalid configuration, asserting that prior set/unset environment state is restored.
- Validation: Both failure cases leave the environment exactly as it was before loading; successful overwrite and single-line-ending behavior remain intact.
- Related findings: none
- Remediation reference: pending
AUD-008: Process interrupts do not cancel active workflows
- Stage: 5
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
cmd/weatherreporter.mainand CLI cancellation propagation - Evidence:
maincallscli.Runwithcontext.Background(). Repository code search found nosignal.Notify,signal.NotifyContext,os.Interrupt, or termination-signal handling.Runner.Runcorrectly forwards the supplied context to generate, batch, and comparison workflows, and focused app tests prove that context cancellation prevents pending publication and joins comparison execution. The production executable never supplies a context that an operator interrupt can cancel, so those paths are reachable only by programmatic callers and tests. - Contract at risk: Documented cancellation failures must reach workflow cancellation checks so pre-publication destinations are preserved and a bounded failed result can be finalized when available.
- Impact:
SIGINTorSIGTERMterminates the process abruptly instead of canceling in-flight collection, Promptkit work, publication preflight, or notification. The CLI cannot emit its documented canceled result path or allow workflow cleanup/coordination to finish, increasing interruption risk around output and comparison transactions. - Recommendation: Derive the root context from
signal.NotifyContextfor operator interrupt and termination signals, stop signal delivery on return, and pass that context through the existing CLI boundary. Preserve the current nonzero failure exit and bounded error behavior. - Test implications: Add a narrow executable-boundary test or a testable signal context seam proving that an interrupt cancels the context passed to CLI. Keep detailed destination-preservation and goroutine-join assertions with their existing app/publication owners.
- Validation: An interrupt observed during a controlled blocking action cancels its workflow context, returns the expected failed/canceled result when available, and leaves the existing destination intact; ordinary help, version, and successful exits are unchanged.
- Related findings:
AUD-010 - Remediation reference: pending
AUD-009: Generate validates report dates after constructing its executor
- Stage: 5
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/cli.Runner.resolveGenerateAction - Evidence: Generate validates the command name and syntactic flags, then loads
configuration and constructs the Promptkit executor before checking that
Daily has
--dateor parsing a supplied Daily/Today date. Executor construction can open and parse configured profile files/directories and can fail first. The equivalent comparison resolver validates profile selection before configuration and performs report-date checks before constructing its one executor. Focused comparison tests assert that input failures construct no executor, but generate has no corresponding test. The productiongenerate dailydiagnostic confirms the missing-date failure is pre-result, but source tracing shows the executor has already been constructed. - Contract at risk: Basic command input should be rejected before constructing an external-dependency adapter or performing work unrelated to the invalid request; pre-run errors should identify the user's actionable input failure.
- Impact: A missing or malformed report date can instead surface a profile
source/configuration error, needlessly reads configured Promptkit sources,
and follows secret loading from
AUD-007even though the report request can never execute. - Recommendation: After flag parsing, reject a missing Daily date immediately; then load configuration for the effective timezone, parse/default the date, resolve paths, and construct the executor only after all request-local validation succeeds.
- Test implications: Add generate input-failure cases with an injected executor
factory and assert zero factory/application calls and no stdout/stderr for a
missing Daily date, malformed Daily/Today date, unsupported flag, and
unexpected argument. Avoid duplicating
timeutilparser cases. - Validation: Every generate input failure returns its input diagnostic without constructing an executor or emitting a partial summary; valid requests still construct exactly one executor.
- Related findings:
AUD-007,AUD-010 - Remediation reference: pending
AUD-010: Assembled non-comparison CLI result paths lack durable tests
- Stage: 5
- Status: candidate
- Severity: medium
- Confidence: high
- Category: testing
- Area:
internal/cli.Runner.Run, generate/run command tests, andcmd/weatherreporter - Evidence: The CLI suite directly tests generate summary field copying and
shared output helpers, but has no assembled
Runner.Rungenerate success, result-bearing failure, quiet failure, or pre-result failure test. Generate callsapp.GenerateDetaileddirectly, unlike the injectable batch and comparison functions, which makes its user-visible wiring difficult to test. Batch has one assembled notification-failure case but no representative ordinary success, report partial failure, quiet failure, or returned-error mapping case. Comparison has comprehensive parser, construction, success, partial failure, cleanup failure, quiet, and pre-execution tests. The command package has no test for executable exit or signal behavior. - Contract at risk: Generate and run must translate result presence, action status, quiet mode, stdout JSON, batch stderr lines, returned errors, and the executable's success/failure exit consistently with the CLI reference.
- Impact: A change can suppress a required failed summary, emit routine output in quiet mode, return success for a failed result, write to the wrong stream, or construct dependencies for invalid input without a focused CLI test failing, even while summary-helper and lower app tests remain green.
- Recommendation: Add the smallest generate application injection seam symmetrical with batch/comparison, then cover one success, one result-bearing failure, quiet failure, and pre-result failure. Add only representative batch success/report-failure cases and one executable exit/cancellation check; leave workflow details with app tests.
- Test implications: Prefer structured JSON/status/error assertions and typed errors over complete output snapshots or private call choreography. These tests should own CLI translation, not repeat publication, collection, prompt, or notification mechanics.
- Validation: Mutations to result/error branching, quiet gating, stream selection, or failure return status break a focused CLI test, while internal app refactors that preserve request/result contracts require no CLI edits.
- Related findings:
AUD-008,AUD-009 - Remediation reference: pending
AUD-011: Unsupported Weather API URL schemes fail only at transport
- Stage: 6
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/config.Validateandinternal/adapters/weatherapi.New - Evidence: Both boundaries accept any parsed absolute URL with a scheme and
host. The adapter always constructs an
http.Requestand executes it withhttp.Client, so a configured URL such asftp://weather.example.testpasses configuration and adapter construction but fails during warmup with an unsupported-protocol transport error. The integration contract defines an HTTP request boundary, and focused configuration and adapter tests cover malformed or missing URLs but no unsupported absolute scheme. - Contract at risk: A syntactically valid configuration must select a transport the Weather API adapter can execute, and intrinsic endpoint incompatibility should fail validation rather than an attempted collection.
- Impact: An operator can pass configuration validation and Promptkit preflight only to receive a runtime weather-fetch failure for a base URL that can never work. This obscures an actionable configuration defect as external availability and performs avoidable setup before failure.
- Recommendation: Define
httpandhttpsas the supported Weather API schemes, document that bound in the configuration and integration owners, and reject every other scheme during config validation and defensively in adapter construction. - Test implications: Add relational config and adapter cases that accept local HTTP and HTTPS shapes and reject a representative unsupported absolute URL without making a request. Do not duplicate generic URL-parser cases.
- Validation: Unsupported schemes fail before Weather API or Promptkit work; ordinary HTTP test servers and configured HTTPS URLs remain accepted.
- Related findings: none
- Remediation reference: pending
AUD-012: Weather API failures expose upstream response bodies
- Stage: 6
- Status: candidate
- Severity: medium
- Confidence: high
- Category: security
- Area:
internal/adapters/weatherapi.Client.warmupOnceandfetchHTTPOnce - Evidence: Both non-2xx branches interpolate the complete body read by their
10 MiB reader into the returned error. That error is wrapped without
redaction by
fetchHTTP,FetchBundle, andcollect.Run, then reaches the application/CLI error path. Focused tests require endpoint and status context but neither require response text nor prove that marker secrets or large provider diagnostics are absent. The integration contract promises endpoint context; it does not make arbitrary upstream bodies part of the diagnostic contract. - Contract at risk: Normal errors and summaries must remain bounded and must not expose provider response detail, credentials echoed by an intermediary, internal infrastructure diagnostics, or large HTML/error payloads.
- Impact: A failing or compromised service can place up to 10 MiB of arbitrary text into normal stderr and wrapped action errors. Besides disclosure, this can overwhelm machine-readable output consumers and make the actionable endpoint/status diagnostic difficult to use.
- Recommendation: Return the relative endpoint, status code, and a stable classification only. If response text is operationally necessary, admit a small sanitized allowlisted excerpt or capture it only through an explicit secure diagnostic mechanism.
- Test implications: Serve non-2xx bodies containing a unique secret marker and a large payload; assert that neither appears in the adapter/collection error while endpoint, status, retry identity, and cancellation remain inspectable.
- Validation: Normal failure output is bounded and marker-free for warmup and source requests, with existing actionable and retry tests still passing.
- Related findings:
AUD-013 - Remediation reference: pending
AUD-013: The Weather API response limit silently truncates bodies
- Stage: 6
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/adapters/weatherapi.Client.warmupOnceandfetchHTTPOnce - Evidence: Both readers call
io.ReadAll(io.LimitReader(resp.Body, 10<<20))and never determine whether more bytes remain. Most oversized JSON is incidentally rejected as truncated syntax, but a complete JSON envelope followed by enough whitespace or later invalid bytes can be accepted from the first 10 MiB while the remainder is ignored. Warmup accepts every 2xx prefix regardless. No focused test defines behavior at, below, or above the nominal limit. - Contract at risk: The documented per-response input bound must distinguish a complete response within the limit from an oversized response; provenance hashes and decoding must describe the complete accepted source payload.
- Impact: A buggy or hostile service can make Weatherreporter accept a partial response, hash only its prefix, or treat an oversized warmup as healthy. Ordinary oversized products usually fail with a misleading JSON decode error rather than an actionable limit error.
- Recommendation: Give the transport one shared bounded-body reader that reads at most the maximum plus one byte, rejects excess input explicitly, and is used by both warmup and source attempts. Keep the configured timeout and body closure ownership unchanged.
- Test implications: Exercise one response at the limit and one over it, including a valid JSON prefix with ignored trailing content; assert a stable size failure and no source decoding or retry for a non-transient size error.
- Validation: No over-limit warmup or source response is accepted, while a complete at-limit response remains readable and response bodies always close.
- Related findings:
AUD-012 - Remediation reference: pending
AUD-014: Required hourly periods can lack usable time bounds
- Stage: 6
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/adapters/weatherapi.bundleBuilder.fetchHourlyandinternal/weatherdata.ForecastRun - Evidence: Required hourly validation rejects a missing/null payload, a JSON
type error, and a zero-length
periodsslice. Go decoding leaves absentissuedAt,startTime, andendTimefields as zerotime.Timevalues, so{"data":{"periods":[{}]}}passes collection with one hourly period. The adapter also does not reject a period whose end is not after its start. Downstream period selection depends on those bounds, and the focused required hourly test covers only explicitnullrather than structurally unusable periods. - Contract at risk: Hourly is the required normalized forecast product; a present array must contain usable forecast periods rather than merely one decodable object.
- Impact: Collection can report success and provenance for an hourly product that cannot contribute to any report period. Later derivation may present an empty or incomplete forecast as a successful collection instead of the required-source failure.
- Recommendation: Validate the smallest required hourly invariant at the
normalization boundary: a nonzero issue time if contractually required and,
for every period, nonzero bounds with
endTimeafterstartTime. Keep meteorological selection and overlap policy in Stage 7 owners. - Test implications: Add table-driven adapter cases for missing bounds, zero or reversed duration, and one valid period. Assert direct required-source failure without duplicating downstream forecast selection tests.
- Validation: Every accepted required hourly period has a usable half-open time range, and existing valid fixtures still normalize unchanged.
- Related findings: none
- Remediation reference: pending
AUD-015: Weather API warmup retries permanent HTTP failures
- Stage: 6
- Status: candidate
- Severity: low
- Confidence: high
- Category: correctness
- Area:
internal/adapters/weatherapi.Client.warmupandwarmupOnce - Evidence: Source fetching retries only transport/read failures and the
documented
408,429,500,502,503, and504statuses. Warmup uses a separate attempt implementation and retries every error until its budget is exhausted, including permanent400,401,403, and404responses. Focused warmup tests cover successful retry of502and exhausted502, while the non-retryable status test covers only a source request. - Contract at risk: The Weather API retry policy distinguishes transient failures from permanent request/status failures consistently across HTTP attempts.
- Impact: Permanent warmup failures cause redundant requests and delay an actionable error by the full retry schedule. The small default budget bounds the effect, so this is primarily avoidable latency and upstream load.
- Recommendation: Reuse the shared transport attempt classification for warmup while retaining its distinct success rule that a readable 2xx body need not decode as a source envelope.
- Test implications: Add a warmup
404case that makes one request, retain the502retry case, and assert cancellation interrupts its retry delay. Keep source decoding and optional-source policy tests separate. - Validation: Warmup retries only the documented transient classes and fails permanent statuses immediately with endpoint/status context.
- Related findings: none
- Remediation reference: pending
AUD-016: Metric fallback values lose their unit during daypart derivation
- Stage: 7
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/forecast.SummarizeDaypart,numericIndicators, and unitlessRange/TimedValuesummary fields - Evidence: Normalized forecast periods keep temperatures in explicit
Fahrenheit/Celsius fields and winds in explicit mph/km/h fields.
SummarizeDaypartselects the first populated temperature or wind field and stores the raw number in a summary field with no unit.numericIndicatorslikewise selects mph before km/h but compares either result to the mph thresholds of 35 for gusts and 25 for sustained wind. Metric mode is an accepted runtime setting, and focused derivation fixtures populate only Fahrenheit and mph. Consequently, a metric-only gust of 35 km/h is marked hazardous even though it is about 22 mph, while metric temperature and wind summary values are indistinguishable from US customary values to immediate facts consumers. - Contract at risk: Derivation must preserve or normalize measurement units so equivalent meteorological inputs produce equivalent summaries and hazard classifications under every supported units setting.
- Impact: Metric reports can exaggerate wind hazards and carry Celsius or km/h numbers into downstream fields that consumers interpret as Fahrenheit or mph. The defect can materially change planning facts and generated report content.
- Recommendation: Give derived ranges and timed values an explicit unit, or convert metric fallbacks into one documented canonical unit before storing or comparing them. Apply thresholds only in their declared unit and avoid silently preferring one of two populated representations unless their equivalence is validated.
- Test implications: Add paired US/metric cases for temperature, apparent temperature, wind speed, and gust. Assert equivalent canonical summaries and indicators at values immediately below, at, and above the thresholds, including periods with both unit fields populated.
- Validation: Equivalent US and metric inputs produce the same heat, cold, and wind decisions and unambiguous summary values; all immediate derived-fact consumers retain correct units.
- Related findings: none
- Remediation reference: pending
AUD-017: Overnight dayparts omit alerts that begin after midnight
- Stage: 7
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/forecast.BuildDailySummaryandoverlapsWithin - Evidence:
BuildDailySummaryfirst limits alerts to the civil-day period and then derives each daypart's alerts only from that reduced set. Configured daypart windows may cross midnight, such as 22:00-06:00, and hourly period selection correctly includes the next morning. An alert beginning at 01:00 the next day does not overlap the prior civil day, so it is discarded before the overnight window is evaluated even though it overlaps that window. Existing tests separately protect direct alert overlap and overnight hourly grouping, but do not combine the two through daily-summary construction. - Contract at risk: Each daypart summary must be derived from its complete configured half-open period, including the next-day portion of a wrapping window.
- Impact: A report can include next-morning forecast conditions in its overnight daypart while omitting an active alert for exactly those hours, understating overnight risk.
- Recommendation: Compute daypart alert overlaps directly from the source alert run and each daypart period. Keep the top-level daily alert list scoped to the civil day so its separate contract does not widen.
- Test implications: Add an integration case with an overnight daypart and an alert wholly after midnight. Assert that the alert appears clipped in the daypart, does not appear in the prior day's top-level list, and an alert beginning exactly at the daypart end remains excluded.
- Validation: Every wrapping daypart includes all and only alerts overlapping its full period, while daily alert summaries retain civil-day boundaries.
- Related findings:
AUD-002 - Remediation reference: pending
AUD-018: Invalid precipitation percentages flow into derived summaries
- Stage: 7
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/forecast.BuildPrecipTiming,SummarizeDaypart, andweatherdata.ForecastPeriod.ProbabilityOfPrecipitationPercent - Evidence: Both derivation paths accept any non-nil probability without enforcing the percentage domain. Values above 100 can become daypart or timing maxima and open precipitation windows, while negative values can become maxima in otherwise sparse input. The Weather API normalization boundary decodes the field but does not range-check it. Focused tests cover zero, ordinary values, and values immediately around the default 40-percent threshold, but not values below zero or above 100.
- Contract at risk: A field named and consumed as a percentage must remain within 0 through 100 before meteorological comparisons or presentation.
- Impact: Malformed upstream data can produce impossible precipitation facts and misleading timing windows while collection and derivation both report success.
- Recommendation: Enforce the numeric domain at the normalization boundary so every consumer receives valid percentages. Define whether a malformed value makes required hourly input fail or is omitted with an explicit warning; derivation should not silently clamp it because that hides source corruption.
- Test implications: Add normalization and derivation cases for negative, 100, and above-100 values, plus non-finite values if the wire decoder can produce them. Protect the exact threshold and zero semantics already tested.
- Validation: No accepted derived fact contains a probability outside 0-100, and malformed source behavior is deterministic and visible under the chosen source-failure policy.
- Related findings:
AUD-014 - Remediation reference: pending
AUD-019: Ordinary “icy” forecast text does not set the ice indicator
- Stage: 7
- Status: candidate
- Severity: low
- Confidence: high
- Category: correctness
- Area:
internal/forecast.indicatorsForText - Evidence: Ice detection checks for the literal substrings
ice,freezing, andsleet. The common condition adjectiveicycontains none of those byte sequences, so a description such as “Icy roads” does not set the ice indicator. Focused tests exercise broader fixture summaries but provide no table of representative positive and negative condition phrases. - Contract at risk: The documented text indicators identify snow, ice, fog, and wind mentions in normalized forecast descriptions.
- Impact: Reports can omit an ice hazard indicator for plainly icy conditions. Other text or numerical signals may still reveal the condition, which keeps severity low.
- Recommendation: Define a small explicit vocabulary or token-aware matcher
for supported condition forms, including
icy, rather than relying only on incidental substrings. Keep the vocabulary bounded to avoid false positives. - Test implications: Add table-driven phrases for ice/icy/freezing/sleet, mixed case, punctuation, and nearby negative words; retain separate tests for snow, fog, wind, heat, and cold.
- Validation: Representative icy descriptions set the ice indicator without broadening matches to unrelated words.
- Related findings: none
- Remediation reference: pending
AUD-020: Pointer-shaped AFD options pass validation but fail construction
- Stage: 8
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/briefing.ModuleDefinition.ValidateOptions,buildAreaForecastDiscussionModule, andinternal/config.normalizeModuleOptions - Evidence: Option validation explicitly accepts either the declared value
type or a pointer to it. Configuration normalization returns an already-valid
typed value unchanged. The area-forecast-discussion builder, the only
in-scope builder that consumes its options, asserts only the non-pointer
module.AreaForecastDiscussionOptionstype. Therefore&module.AreaForecastDiscussionOptions{...}passes composition and config validation but later fails module construction with an option-type error. The focused pointer acceptance test usesCurrentConditionsOptions, whose builder ignores options, so it does not protect the end-to-end option contract. - Contract at risk: An option shape accepted by registry and configuration validation must be consumable by the corresponding builder without a later type failure.
- Impact: A programmatically constructed report configuration can pass preflight and then fail report preparation solely because it uses the registry's documented accepted pointer form. Loaded YAML normally decodes to the value form, limiting the affected path but making the failure surprising.
- Recommendation: Choose one canonical builder input shape. Prefer normalizing accepted pointers to the declared value type before storing/building, or reject pointers consistently if they are not part of the intended contract.
- Test implications: Exercise the AFD option consumer through composition,
constructed-config normalization, and
BuildModulewith both accepted shapes. Assert identical selected sections, not merely validation success. - Validation: Every option value accepted by
ValidateOptionsreaches the AFD builder in a usable canonical form, and invalid shapes still fail during preflight. - Related findings:
AUD-006 - Remediation reference: pending
AUD-021: Empty weather-story data is published as available
- Stage: 8
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/briefing.buildWeatherStoryModuleand the normalizedweatherdata.WeatherStorysource boundary - Evidence: Weather-story collection accepts a decoded non-null object without
a minimum content invariant. Unlike current conditions, narrative, hourly,
and AFD builders,
buildWeatherStoryModuleperforms no empty-value check; any non-nil pointer, including&weatherdata.WeatherStory{}, emits a module withAvailable: true, blank period labels, and otherwise empty fields. Focused tests cover a populated story and a missing nil source, but not a checked structurally empty object. - Contract at risk: Source-facing module availability must mean that usable source content exists; missing, empty, and available states must not be conflated.
- Impact: Prompt and render consumers can be told a weather story is available when there is no title, description, alternate text, image, or valid period, producing misleading or empty report sections.
- Recommendation: Define the smallest usable weather-story invariant at the normalization boundary and apply optional-source policy when it is not met. Retain a defensive module-level empty check so programmatic fact inputs cannot assert availability without content.
- Test implications: Add a local adapter case for an empty object and a briefing case for an empty normalized story. Protect populated zero-valued booleans/order independently from the content fields used to determine availability.
- Validation: Empty or unusable stories follow the declared missing-source
policy or are omitted, while a usable story emits
Available: truewith its structured fields intact. - Related findings: none
- Remediation reference: pending
AUD-022: Module snapshots accept unsupported schema versions
- Stage: 8
- Status: candidate
- Severity: low
- Confidence: high
- Category: compatibility
- Area:
internal/module.Snapshot.Validate - Evidence: A snapshot declares the exact version
weatherreporter.modules.v1, andNewSnapshotalways assigns it, butSnapshot.Validaterejects only an empty version. A manually decoded, cloned, or programmatically constructed snapshot labeled with an old or future nonempty version passes this validation and can continue to prompt or render consumers. The prompt-input package's analogous validation requires its exact current version. Module tests assert constructor output and missing structural fields but do not exercise an unsupported version. - Contract at risk: A versioned module envelope must reject versions whose field and value semantics the current consumer does not implement.
- Impact: Current production construction uses the right constant, so ordinary reports are unaffected. The exported validation boundary nevertheless gives false compatibility assurance to alternate/test callers and to any future decoded snapshot path.
- Recommendation: Require
SchemaVersion == SnapshotSchemaVersionduring validation and return an error naming the supported version. - Test implications: Add one focused unsupported-version case alongside the current constructor/version and duplicate-output tests.
- Validation: Only the current module snapshot schema passes validation; all normal snapshot construction and downstream consumers remain unchanged.
- 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.derivedDailySummaryValueandDerivedDailySummaryModule.HeatIndexMaxF - Evidence: Daypart derivation aggregates the source's generic
ApparentTemperatureFvalues intoDaypartSummary.ApparentTemperature. The daily briefing builder takes the maximum of that range unconditionally and publishes it asheat_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 morningandearly-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, anddaypartNamed - Evidence: Morning, overnight, afternoon, and evening role lookup uses
case-insensitive
daypartNamed, while the commute/workday loop excludes only names exactly equal to lowercaseovernightorevening. Daypart names are configurable nonblank strings, and neither configuration nor derivation canonicalizes them into semantic roles. A configuredOvernightwindow 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:
scoreOutdoorWindowand 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
-9through-1therefore receive decade0; the absolute remainder then produces phrases such asmid 0sorupper 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 zerowording 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, and9, 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.jsonandspcOutlookBackgroundDefinition - Evidence: The embedded asset labels one field
official_descriptionbut carries no source URL, source date, or revision metadata. The current NOAA Storm Prediction Center outlook reference, 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 onlyNo severe thunderstorms expectedand 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_descriptionis 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
AUD-029: Raw source warnings cross the curated prompt boundary
- Stage: 10
- Status: candidate
- Severity: low
- Confidence: high
- Category: security
- Area:
internal/promptinput.Metadata,promptinput.Package.SourceWarnings, andweatherdata.SourceWarning.Endpoint - Evidence:
promptinput.Buildcopies the complete normalized[]weatherdata.SourceWarninginto the top-level prompt package, and YAML serialization includes each warning'sendpoint. The Weather API adapter populates that field with the source's fixed relative transport path whenever an optional source is missing or malformed. In contrast, the briefing metadata module intentionally projects warnings into a prompt-safe type without endpoint or provenance fields, and its test asserts those omissions. No prompt-input test asserts that raw weather-domain fields or transport paths are absent. - Contract at risk: Only curated prompt-facing values may cross the model boundary; raw weather-domain and transport/provenance fields must not be serialized merely because they are present on an upstream type.
- Impact: An optional-source failure sends an internal relative transport path to Promptkit and the selected model provider and duplicates a safer warning summary already in the briefing. Current adapter paths contain no host, query, or credentials, limiting present disclosure, but future fields added to the raw shared type would serialize without a prompt-boundary review.
- Recommendation: Replace the top-level raw warning field with a prompt-owned safe projection, or remove it in favor of the metadata-module summary. Admit only reviewed source, code, severity, bounded safe message, and completeness fields; never endpoint, query, hash, or response detail.
- Test implications: Build and marshal a package from a warning with a unique endpoint marker, then assert the marker is absent while intended safe warning facts remain. Keep the assertion at the serialized provider-boundary owner.
- Validation: The exact bytes submitted for prompt execution contain no raw transport/provenance fields; ordinary warning meaning and existing deterministic YAML tests remain intact.
- Related findings: none
- Remediation reference: pending
AUD-030: The shared prompt instructs nonexistent briefing paths
- Stage: 10
- Status: candidate
- Severity: high
- Confidence: high
- Category: correctness
- Area:
internal/promptassets/assets/prompts/common/data_package.user.mdandinternal/promptinput.BriefingStanzas - Evidence: The shared user prompt directs every report prompt to read
briefing.derived_daily_summary,briefing.derived_daypart_summaries,briefing.precip_timing, andbriefing.outdoor_windows. The deterministic serializer places all four underbriefing.derived_summaries.<stanza>. It also directs alert relevance checks tobriefing.metadata.alerts, but the prompt-facing metadata module has noalertsfield; relevant counts live inbriefing.applicable_risk_products.alert_digest. Asset tests verify prompt, profile, input-schema, and output-schema identities and successfully inspect the real Promptkit definitions, but do not relate prose field references to a representative serialized package. - Contract at risk: Embedded instructions and the exact curated input schema must evolve as one provider-facing contract.
- Impact: All four normal report prompts can overlook their primary derived summaries or search the wrong alert location, pushing the model toward raw data, unsupported inference, or false statements about alert relevance.
- Recommendation: Update the common prompt to name the actual categorized paths and define one maintainable contract manifest or representative serialized fixture from which semantic path assertions can be made. Avoid a brittle whole-prose snapshot.
- Test implications: Inspect all embedded report prompts and assert the shared
instruction references paths present in a representative real
promptinput.MarshalYAMLoutput, including derived and alert examples. - Validation: Every named input path in the shared instruction resolves in the current serialized package and a category move or prompt-path regression fails one focused compatibility test.
- Related findings: none
- Remediation reference: pending
AUD-031: The shared prompt overstates SPC product locality
- Stage: 10
- Status: candidate
- Severity: high
- Confidence: high
- Category: correctness
- Area:
internal/promptassets/assets/prompts/common/data_package.user.md,briefing.buildSPCConvectiveOutlooks, andbriefing.buildSPCConvectiveDiscussion - Evidence: The shared instruction says all active hazard and risk products
have already been filtered or matched to the forecast location and should be
treated as locally relevant. The SPC outlook module deliberately preserves
all selected overlapping records; only its categorical
risk_digestappliesContainsLocation, rank, type, and overlap filters. SPC discussions are regional text selected by qualifying outlook day/type/rank/overlap, not by point containment. Stage 9 retained this distinction asRET-020and focused builder tests protect it, but the prompt-assets suite does not protect the corresponding instruction semantics. - Contract at risk: Safety-sensitive prompt guidance must distinguish location-matched conclusions from regional context retained for grounded interpretation.
- Impact: A model can present a non-containing SPC outlook record or regional discussion as a hazard at the configured point, overstating severe-weather risk despite the curated digest carrying the correct local conclusion.
- Recommendation: State explicitly that the alert relevant count and SPC categorical risk digest are location-matched, while individual outlook records and discussions require their containment/geographic limitations to be preserved. Remove the blanket locality claim.
- Test implications: Add a focused semantic assertion for the shared instruction and a representative package containing a non-local outlook alongside a location-matched digest. Do not duplicate SPC selection tests.
- Validation: The embedded prompt cannot reasonably instruct a consumer to treat regional or non-containing SPC material as point-local, while still requiring locally relevant digest facts to lead the report.
- Related findings:
AUD-030 - Remediation reference: pending
AUD-032: The exported YAML loader accepts malformed package shapes
- Stage: 10
- Status: candidate
- Severity: low
- Confidence: high
- Category: correctness
- Area:
internal/promptinput.LoadYAMLandValidate - Evidence: Focused temporary probes confirmed that
LoadYAMLaccepts a package with no metadata stanza, ignores a value-map entry absent from briefing order, ignores unknown top-level or report fields, and silently accepts a trailing YAML document.Validatechecks the current schema, report identity, nonempty order, ordered-value presence, duplicates, and known category placement, but not metadata presence or an exact order/value bijection. The decoder neither enablesKnownFields(true)nor performs a second decode expecting EOF. Inbound graph traces find only test callers for this loader, limiting current runtime impact. - Contract at risk: Loading and validating an exported data package should reject ambiguous, incomplete, or forward-unknown representations rather than normalize them silently.
- Impact: Tooling or a future production consumer can accept a package that loses stanzas on remarshal, lacks required provenance, carries unnoticed unsupported fields, or hides a second document, giving a false compatibility signal. The live Build-to-Marshal execution path remains valid.
- Recommendation: Require metadata exactly once, enforce an exact order/value bijection, enable strict known-field decoding for structured fields, and reject all content after the first YAML document.
- Test implications: Add a compact table for missing metadata, orphan value, unknown root/nested fields, and a second document, retaining the current round-trip, duplicate, misplaced, and old-schema cases.
- Validation: Each malformed probe fails with bounded context, one canonical document round-trips deterministically, and no production execution behavior changes.
- Related findings: none
- Remediation reference: pending
AUD-033: Stanza serialization failures lack a focused contract test
- Stage: 10
- Status: candidate
- Severity: low
- Confidence: high
- Category: testing
- Area:
internal/promptinput.BriefingStanzas.MarshalYAMLandyamlNode - Evidence: Stanza values are normalized through JSON before YAML, and the implementation wraps conversion failures with the stanza name. Existing tests cover deterministic output, category placement, round trips, uncategorized stanzas, and schema rejection, but none supplies an unserializable value such as a non-finite float to execute this failure branch. Normal registry outputs currently use fixed serializable shapes, so no present default module triggers it.
- Contract at risk: A serialization failure must remain contextual and must stop execution before any provider call; this behavior should not depend on incidental library diagnostics.
- Impact: A future or changed prompt export can fail without a durable test protecting stanza attribution, making provider-preflight errors harder to diagnose or tempting unsafe fallback serialization. Current default reports are unaffected.
- Recommendation: Add one focused prompt-input test using a deliberately unsupported stanza value and assert a non-nil, stanza-attributed error. The application-level no-provider-work consequence belongs to Stage 14.
- Test implications: Protect the repository context and error category, not the complete encoding-library message; do not duplicate every unsupported Go value shape. Stage 14 confirmed that application tests also do not inject this failure and prove zero executor calls, so add that single consequence assertion at the preparation owner rather than repeating serializer cases.
- Validation: The focused test fails if conversion errors lose stanza context or are swallowed, and deterministic successful serialization remains green.
- Related findings:
AUD-043 - Remediation reference: pending
AUD-034: Prompt-debug credential redaction misses common aliases and URL forms
- Stage: 11
- Status: candidate
- Severity: medium
- Confidence: high
- Category: security
- Area:
internal/promptdebug.safePromptDebugEndpoint,redactPromptDebugParameters, andisPromptDebugSecretKey - Evidence: Debug preparation deliberately persists the resolved provider endpoint and effective extra parameters after key-based redaction. The key normalizer recognizes strings containing credential, secret, password, token, API key, or authorization. A deterministic temporary probe confirmed that common synthetic aliases such as access-key, signature, auth, and cookie retain their marker values in endpoint queries or nested parameters. URL user information and the existing API-key fixture are redacted, but URL path credentials and unrecognized query names are also retained. The probe was removed after reproduction and no sensitive fixture value was recorded.
- Contract at risk: Prompt-debug artifacts may contain content-rich execution detail, but their explicit safe mapping and focused tests promise that provider credentials are not persisted.
- Impact: An operator profile or backend using an unrecognized credential
alias or signed URL can write a live credential into
preparation.json. Files are mode0600beneath a mode0700root, limiting exposure, but the credential becomes durable and subject to backup, retention, or later permission mistakes despite the artifact claiming redaction. - Recommendation: Stop trying to prove arbitrary provider maps and complete URLs safe through a denylist. Persist an allowlisted endpoint projection and allowlisted non-secret execution parameters, or replace all provider-specific extras with names/type summaries. Define how signed paths and queries are omitted rather than expanding an indefinitely incomplete alias list.
- Test implications: Add table-driven synthetic markers across URL user info, paths, common query aliases, case/separator variants, and nested maps/lists; assert that no marker reaches serialized debug bytes while reviewed ordinary parameters remain useful. Do not use real credential-like fixture values.
- Validation: Every supported debug projection is allowlisted, adversarial marker tests find no value leak, and normal summaries remain content-free.
- Related findings: none
- Remediation reference: pending
AUD-035: Prompt-debug symlink checks can be raced before writes
- Stage: 11
- Status: candidate
- Severity: medium
- Confidence: high
- Category: security
- Area:
internal/promptdebug.ensureSecureDirectory,runDirectory, andwriteSecureJSON - Evidence: Directory validation walks path strings with
os.Lstatand rejects every symlink visible at that moment.runDirectorythen joins and creates paths by name, andwriteSecureJSONlater creates a temporary file and renames it through the same pathname. No directory handle pins the validated root or descendants, so a process able to replace a component in a writable ancestor can swap it for a symlink after validation and beforeMkdir,CreateTemp, orRename. Static root/component symlink tests and concurrent missing-ancestor tests pass, but they cannot protect this check/use window. - Contract at risk: Sensitive debug artifacts must remain beneath the explicitly authorized root, and symlink rejection must hold at the file operation rather than only during an earlier observation.
- Impact: Under an explicitly enabled debug run and a replaceable ancestor, a local filesystem attacker can race sensitive rendered prompts or model output into a directory outside the authorized root. Restrictive final modes do not restore path containment after redirection.
- Recommendation: Anchor traversal and creation to opened directory handles
and use no-follow, directory-only operations for every component and final
replacement, with platform-specific handling where necessary. Alternatively
require and document a pre-existing trusted root while still pinning it for
each write; string containment and repeated
Lstatare not sufficient. - Test implications: Add a deterministic filesystem seam or supported-platform race harness that replaces a component between validation and creation and proves no artifact appears outside the root. Retain the cheaper static symlink, mode, collision, and concurrent-distinct-run cases.
- Validation: Debug creation and atomic replacement remain relative to the originally validated root handle across component swaps; attempted redirection fails closed without an external file or partial target.
- Related findings: none
- Remediation reference: pending
AUD-036: The generated-text catalog accepts a known pair for the wrong report
- Stage: 12
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/generatedtext.LookupDefinitionand the generated-text catalog - Evidence: Catalog entries contain only schema ID, template ID, validator,
and render-context builder.
LookupDefinitionaccepts the first matching schema/template pair and copies the caller's report ID into the handler without checking that the pair belongs to that report. A temporary probe changed a copied Daily definition to the knownhourly/hourlypair; lookup returned an Hourly handler with no error. The current registry loop test compares handler IDs with the same definition it supplied, and the report registry test requires only nonempty schema/template IDs, so neither test detects a coherent but wrong known pair. - Contract at risk: Each generated-text report definition must resolve to its one report-specific schema, validator, template, and context builder before provider work begins; the separate Daily, Today, Tomorrow, and Hourly value types preserve that identity even where fields currently match.
- Impact: A registry or constructed-definition regression can route a report through another report's output contract. Depending on the selected pair it can fail only after provider execution during context assembly, or render a semantically wrong report if the downstream inputs remain structurally compatible.
- Recommendation: Include
report.IDin every catalog entry and require the exact report/schema/template triple. Keep unknown schema, unknown template, known-but-mismatched pair, and wrong-report diagnostics distinct and bounded. - Test implications: Assert the exact four report-to-pair mappings from an independent expectation table, and reject every known pair when attached to a different report. Avoid deriving the expected pair from the definition under test.
- Validation: Every current definition resolves once, and swapping any two known pairs fails during lookup before prompt inspection or provider work.
- Related findings: none
- Remediation reference: pending
AUD-037: Typed decoding accepts property names rejected by the schema
- Stage: 12
- Status: candidate
- Severity: low
- Confidence: high
- Category: correctness
- Area:
internal/generatedtext.decodeGeneratedTextand the four generated- text schemas - Evidence: The schemas require the exact lowercase names
summary,forecast_discussion, andprecipitation_timingwhile rejecting additional properties. Go'sencoding/jsonmatches tagged struct fields without regard to ASCII case. A temporary probe showed that Hourly validation acceptsSummaryandForecast_Discussion, then normalizes them to the canonical lowercase names. A real offline Promptkit execution against the embedded schema classified the same response as failed. The exact-name raw-map check happens only forprecipitation_timing, leaving the other two fields inconsistent. - Contract at risk: The schema validator and repository-owned typed validator should accept the same property vocabulary, and typed decoding is documented as rejecting unknown fields without a second JSON Schema engine.
- Impact: The normal Promptkit path currently rejects these aliases first, but direct handler callers or another conforming executor that reports completed validation can accept output the canonical schema forbids. This weakens the typed validator as an independent boundary and makes behavior depend on the executor implementation.
- Recommendation: Inspect top-level raw object keys with exact matching before struct decoding, rejecting case variants, duplicates, missing names, and additions under one explicit policy. Preserve typed decoding for field shapes and semantic normalization.
- Test implications: Add one shared exact-key contract across all four public validators and an embedded-schema integration case proving the two layers agree. Do not repeat every casing variant in each report-specific suite.
- Validation: A corpus of canonical, missing, added, case-varied, duplicate, null, and wrong-type fields produces compatible schema and Go decisions.
- Related findings: none
- Remediation reference: pending
AUD-038: Generated prose and decoder diagnostics have no size boundary
- Stage: 12
- Status: candidate
- Severity: medium
- Confidence: high
- Category: reliability
- Area: the generated-text schemas,
decodeGeneratedText, day-style arrays, and application propagation of validation errors - Evidence: None of the four schemas declares string-length, array-item, or
total-output bounds, and the Go validators impose none before decoding,
trimming, filtering, and normalizing. Embedded profiles intentionally omit
output-token limits. Temporary offline probes showed both Promptkit schema
validation and
ValidateHourlyaccepting and copying a synthetic response with a two-MiB summary. Another probe used a 64-KiB synthetic unknown-field name;encoding/jsoncopied the complete name into a 65,603-byte error.profileExecutionErrorandgeneratedReportErrorwrap validator text verbatim, so an executor that reaches this independent layer can carry that content into ordinary errors. All probes were removed. - Contract at risk: Provider output crosses a bounded, content-safe validation boundary before normalization and rendering; ordinary errors must exclude response-body content and remain safe to summarize or log.
- Impact: A provider, fake, or future executor can force repeated large allocations in validation/normalization and produce an unexpectedly large Markdown artifact from schema-valid prose. Malformed field names can also turn provider-controlled response text into normal diagnostics. Provider context limits and current Promptkit rejection of unknown fields reduce but do not define the repository's accepted size or independent error contract.
- Recommendation: Define reviewed total raw-output, per-string, and day-style paragraph-count/aggregate bounds; express compatible bounds in JSON Schema and enforce the total before decoding. Translate decoder failures into bounded categories that identify canonical fields without echoing arbitrary keys or raw content.
- Test implications: Exercise exact boundary and one-over cases for total bytes, string lengths, paragraph count/aggregate size, and oversized unknown names with synthetic markers. Assert normalized bytes and every ordinary error remain bounded without duplicating all cases per day-style report.
- Validation: Schema and Go limits agree, excessive output fails before normalization/rendering, and no malformed provider value can make normal error text exceed the documented diagnostic bound.
- Related findings: none
- Remediation reference: pending
AUD-039: Generated-text contract tests duplicate policy across owners
- Stage: 12
- Status: candidate
- Severity: low
- Confidence: high
- Category: testing
- Area:
internal/generatedtext/{daily,today,tomorrow,day_style}_test.goand schema assertions ininternal/reporttemplate/reporttemplate_test.go - Evidence: Daily, Today, and Tomorrow each carry nearly line-for-line copies
of normalization and malformed-input tables, while the shared day-style
suite repeats summary, discussion, normalization, empty timing, missing
timing, unknown-field, and retired-field behavior across the same three
validators. Separately,
reporttemplate_test.goimportspromptassetsand repeats four schema-shape assertions, including three duplicated day-style blocks, even thoughreporttemplateowns Markdown assets andpromptassets_test.goalready owns the schema inventory, strict-object shape, required fields, identity, and copy isolation. - Contract at risk: Similar public validators should remain independently protected without copying shared semantics, and schema tests should reside with the asset owner rather than an unrelated template package.
- Impact: One intentional day-style or schema change requires synchronized
edits across several files and packages, creates noisy failures outside the
responsible owner, and can still miss cross-layer discrepancies such as
AUD-037because duplicated examples are not an agreement test. - Recommendation: Keep one table-driven shared day-style behavior suite with a
thin per-exported-validator identity/type case. Move complete schema-shape
ownership to
promptassets; keep only a generated-text integration contract that compares schema acceptance with typed validation where their semantics intentionally overlap. Remove schema assertions fromreporttemplate. - Test implications: Preserve distinct Hourly behavior and each public report identity while deleting repeated semantic cases. A mechanical refactor of a private day-style helper should not require unrelated test edits.
- Validation: The leaner suite still fails for a wrong report dispatch, schema/type disagreement, required-field regression, or normalization change, and report-template tests fail only for template/render behavior.
- Related findings:
AUD-037 - Remediation reference: pending
AUD-040: Render contexts accept conflicting report identities
- Stage: 13
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/generatedtext.BuildHourlyRenderContext, the three day-style context builders, and their module metadata projection - Evidence: Each exported builder selects its report-specific title, context
type, and module surface from the function called, but none checks
briefing.Metadata.ReportIDor reconciles the separately decodedModules.Metadata.ReportID. A temporary probe passed outer metadata labeled Hourly and a snapshot metadata stanza labeled Daily toBuildDailyRenderContext; it returned a Daily context without error, titled it as Daily, and retained the conflicting Daily module identity. Focused context tests supply consistent identities but have no mismatch case. Combined withAUD-036, a Daily definition selecting the known Hourly pair can reach the Hourly context builder with Daily metadata and render rather than fail at this boundary. - Contract at risk: A report-specific render context must be assembled only from metadata and module values for that report, with one authoritative identity before repository-owned Markdown rendering.
- Impact: A registry, preparation, or direct package regression can render the
wrong report shape while preserving conflicting metadata inside the same
context. Current top-level templates do not display
Modules.Metadata, but it is a documented template field and future edits can choose a different identity or timing source without an error exposing the inconsistency. - Recommendation: Make the expected report identity explicit in the common builder path, reject mismatched outer metadata, and when a metadata stanza is present verify its report ID, run ID, prompt ID, period, timezone, and other duplicated canonical metadata against the outer value. Prefer one documented template-facing owner for overlapping metadata rather than two unchecked copies.
- Test implications: Add one shared mismatch table covering all four builders and one outer-versus-module metadata disagreement case. Keep successful report-specific context assertions without duplicating every metadata field per report.
- Validation: Every builder rejects another report's metadata and any present metadata stanza that disagrees with the canonical context input; all four normal prepared contexts still render.
- Related findings:
AUD-036 - Remediation reference: pending
AUD-041: Dynamic prose can impersonate deterministic Markdown sections
- Stage: 13
- Status: candidate
- Severity: medium
- Confidence: high
- Category: security
- Area: all top-level report templates and their interpolation of generated
text and source-derived module strings through
text/template - Evidence:
reporttemplate.Renderusestext/template, which performs no Markdown or HTML escaping, and the templates insert generated summary, forecast-discussion, and precipitation-timing strings directly. The schemas permit newlines and Markdown characters, while typed validation only trims outer whitespace. A temporary probe placed a second-levelAlert Digestheading and fabricated warning bullet in an otherwise valid Daily summary; rendering succeeded and preserved both as active Markdown immediately before the repository-owned Alert Digest position. The same raw interpolation is used for externally sourced alert labels, condition text, daypart display strings, timing phrases, and other module values. - Contract at risk: Generated text fills bounded prose slots only; deterministic facts remain authoritative, and repository-owned templates alone define Markdown structure.
- Impact: Malformed model output, indirect prompt injection through source material, or unexpected upstream Markdown can create headings, bullets, links, raw HTML, or formatting that visually masquerades as deterministic alerts and forecast facts. Schema validation does not distinguish prose from Markdown structure, so a validation-passed provider response can corrupt the trust boundary of the published report.
- Recommendation: Define field-specific Markdown policies at the context or renderer boundary. Preserve intended paragraph breaks while escaping or rejecting block structure, raw HTML, unsafe links, control characters, and inline delimiters where they are not part of the contract; render module labels and values through reviewed inline-safe helpers. Do not rely solely on prompt instructions or downstream Markdown sanitization.
- Test implications: Use synthetic markers across each generated prose slot and representative module label/value positions; assert none can introduce a heading, list item, code block, raw HTML block, or unsafe link while normal punctuation and intended paragraphs remain readable. Include the complete validation-to-render path for at least one report.
- Validation: Parsing rendered output as Markdown shows that only template- owned nodes create report headings and deterministic list structure, and adversarial dynamic strings remain text within their authorized slots.
- Related findings:
AUD-038 - Remediation reference: pending
AUD-042: Today can render a daypart heading with no rows or fallback
- Stage: 13
- Status: candidate
- Severity: low
- Confidence: high
- Category: correctness
- Area:
templates/partials/today_daypart_forecast.md.tmpl - Evidence: The Today partial decides its fallback from whether
.Modules.Daypartsis nonempty, then independently suppresses every row whose summary lacks bothDominantConditionDisplayandDominantCondition. A temporary probe supplied one named daypart with no displayable condition; output contained## Daypart Forecastbut neither a row norNo daypart forecast details are available.Existing tests protect the zero-length fallback and suppression of one empty Evening row beside valid rows, but not the all-filtered case. - Contract at risk: Missing deterministic values should produce an explicit, coherent fallback or omit the section, not leave an empty Markdown heading.
- Impact: When selected Today dayparts exist structurally but all lack usable condition summaries, the published report presents a blank section that implies lost content and gives the operator no explanation.
- Recommendation: Compute whether at least one Today row is displayable before emitting the heading. Render valid rows when present; otherwise render the existing no-details fallback, or consistently omit the complete section if that becomes the documented policy.
- Test implications: Cover zero rows, all-filtered rows, mixed valid/filtered rows, and all-valid rows through the shared partial's observable output.
- Validation: The Daypart Forecast heading is always followed by at least one row or the explicit fallback, and mixed rows retain their existing order.
- Related findings: none
- Remediation reference: pending
AUD-043: Generated-text compatibility is validated after collection
- Stage: 14
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/app.InspectPromptExecution,InspectPromptExecutions,InspectComparisonExecution, andprepareReport - Evidence: Prompt preflight validates the exact prompt ID/version, its one
YAML input, the configured output schema path, the effective profile, and
credentials before weather collection. It never validates the report's
generated-text template or complete catalog binding.
prepareReportcallsgeneratedtext.LookupDefinitiononly after collection and after building report facts, the module snapshot, metadata, the prompt package, and its serialized YAML. A temporary offline probe changed a copied Daily definition to an unknown template while retaining its valid prompt and schema;InspectPromptExecutionsucceeded. The same definition fails catalog lookup only during preparation. Current default registry entries are valid. - Contract at risk: Every configuration-independent report, schema, template, validator, and renderer precondition must fail before external collection or model execution, leaving collected-data validation and serialization for the post-collection preparation boundary.
- Impact: A registry regression or constructed definition with an unknown or mismatched template can pass advertised preflight, perform all remote weather requests, and build the complete prompt package before failing without a model call. This wastes externally visible work and weakens the promise that report-contract defects are configuration preflight failures.
- Recommendation: Resolve and validate the exact report/schema/template
catalog entry as part of pre-collection inspection or report resolution,
after strengthening lookup to enforce the report-specific triple in
AUD-036. Carry the validated immutable handler into preparation so the post-collection path does not perform a weaker or duplicate lookup. - Test implications: At the application owner, inject an invalid template and
known-but-wrong report pair and assert no collector or executor call. Keep
catalog pairing cases in
internal/generatedtext; the app test should protect only ordering and consequences. - Validation: All invalid report/catalog bindings fail before collection in ordinary, batch, and comparison preflight, while each valid definition is resolved once and no model execution occurs on later preparation failures.
- Related findings:
AUD-036 - Remediation reference: pending
AUD-044: Execution provenance is accepted without reconciliation
- Stage: 14
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/app.inspectPromptContract,inspectPromptProfile, andexecutePreparedProfile - Evidence: Inspection verifies returned prompt and profile IDs, input shape,
output shape, and credentials, but accepts an empty prompt hash, backend ID,
and model name. Execution sends the inspected prompt/profile IDs and an
independent data-package copy, then trusts the preparation callback to
replace result profile/backend/model identity. It does not compare the
prepared report definition with the inspection, require the callback, or
reconcile callback and completed
Executionprompt ID/version/hash, profile/backend/model, output contract, validation mode, or schema path. A temporary executor probe returned mutually different inspected, callback, and execution identities plusValidationPassedfor an unrelated mode and schema path;executePreparedProfileaccepted the raw Daily-shaped JSON and rendered Markdown successfully. Existing fakes and the current Promptkit adapter return coherent provenance, so focused tests never exercise a mismatch. - Contract at risk: A validation-passed output must be demonstrably produced by the exact inspected prompt version and selected profile, against the prepared report's exact schema, before repository-owned typed validation and rendering can attribute it to that report.
- Impact: An adapter regression, stale inspection passed by an internal caller, or alternate executor can render and later publish output from a different prompt, profile, backend, model, or schema while the active result records callback-supplied identity. Similar output shapes can make the error silent rather than causing typed validation to fail.
- Recommendation: Define one application-owned provenance validator. Require
complete safe inspection identity, match it to the prepared definition and
request, count exactly one preparation callback, reconcile callback and
completed execution identity/hash fields, and require the expected JSON
Schema validation mode and path before accepting
ValidationPassed. - Test implications: Add one table-driven execution-contract suite that changes one provenance field or omits the callback, plus one coherent happy path. Assert mismatch errors are classified and content-safe, produce no rendered bytes, and retain only verified partial-result fields.
- Validation: Every provenance mismatch fails before generated-text decoding or rendering; coherent Promptkit and fake executions retain the exact inspected prompt hash and selected profile/backend/model in results and debug artifacts.
- Related findings:
AUD-036,AUD-037,AUD-040 - Remediation reference: pending
AUD-045: Single-report publication replaces non-regular destinations
- Stage: 15
- Status: candidate
- Severity: medium
- Confidence: high
- Category: correctness
- Area:
internal/app.validateOutputPathandinternal/fileutil.WriteFileAtomic - Evidence: Destination validation follows the final path with
os.Statand rejects it only when the result is a directory. FIFOs, sockets, devices, and other non-regular objects therefore pass; a symlink to a regular file also passes even though rename replaces the symlink itself rather than its target.WriteFileAtomiccreates a regular temporary file and callsos.Renamewithout reclassifying the target. A temporary Unix probe created a FIFO at an explicit Daily output path, ran the complete offline generation workflow, and confirmed successful publication had replaced the FIFO with a regular Markdown file. Existing app tests protect only a directory collision, and fileutil's rename-error test also uses a directory. - Contract at risk: An operator-selected report destination may replace an absent path or an existing regular report file, but publication must reject filesystem objects whose semantics or ownership are not ordinary report content.
- Impact: A typo or name collision can silently destroy a named pipe, Unix socket entry, device node where permissions permit, or other special object and report success. Accepted symlink behavior is also surprising: the link is removed instead of atomically updating the file it names.
- Recommendation: Use
Lstatto define and enforce an explicit final-component policy before work and immediately before rename. Accept only absent or regular-file destinations; either reject symlinks or document and implement one deliberate link policy without following at validation and replacing at publication. - Test implications: Add a portable table for absent, regular, directory, and symlink targets plus platform-gated FIFO/socket cases. Assert invalid targets fail before prompt inspection or collection and remain unchanged.
- Validation: Every accepted existing target is a regular report file under the documented symlink policy; non-regular objects retain their type and identity with zero collector, executor, or notifier calls.
- Related findings: none
- Remediation reference: pending
AUD-046: Valid long output names fail through the temporary-name pattern
- Stage: 15
- Status: candidate
- Severity: low
- Confidence: high
- Category: correctness
- Area:
internal/fileutil.WriteFileAtomicand explicit single-report output paths - Evidence: The atomic writer gives
os.CreateTempthe pattern.plus the complete destination basename plus a random suffix and.tmp. A destination component can therefore fit the filesystem's name limit while its derived temporary component does not. A temporary offline probe used a valid 248-byte.mdbasename beneath a missing directory on the current filesystem. Resolution, prompt inspection, collection, model execution, and rendering all completed; publication created the parent, then failed withENAMETOOLONGwhile creating the temporary file. A direct write to the exact target succeeded afterward, proving the requested component itself was valid. The failed run also left the newly created empty output directory, showing that failure cleanup covers the temporary file but not parent directories created by the same publication attempt. - Contract at risk: A filesystem-valid operator-selected output name should not become invalid solely because of an internal temporary naming scheme; publication side effects and cleanup should also match the documented output lifecycle.
- Impact: An explicit long output path fails only after all remote and model work, returns no published path, and can leave a directory requiring manual cleanup. Default report names are short, limiting current exposure to custom output paths.
- Recommendation: Use a short fixed temporary prefix independent of the final basename while retaining same-directory rename. Validate any unavoidable platform path constraint before external work, and reconcile directory creation/cleanup semantics without deleting concurrently created content.
- Test implications: Add a filesystem-boundary case whose final component is valid but would overflow the old temp pattern; assert successful replacement, mode, content, and no temporary residue. Add a publication-failure case for the documented missing-directory lifecycle through an injectable stable filesystem boundary rather than relying on a platform error string.
- Validation: Every accepted final basename can create its temporary sibling, and a failed publication leaves neither a partial file nor an app-created empty directory under the documented lifecycle.
- Related findings: none
- Remediation reference: pending
AUD-047: Cancellation is not checked at the publication linearization point
- Stage: 15
- Status: candidate
- Severity: medium
- Confidence: high
- Category: reliability
- Area:
internal/app.publishPromptReport,publicationContextError, andinternal/fileutil.WriteFileAtomic - Evidence: The application checks
ctx.Err()once before calling the atomic writer. The writer then creates missing directories, creates and writes the temporary file, closes it, and finally publishes withos.Rename; it accepts no context and performs no check immediately before rename. Thus cancellation that becomes observable during directory creation or temporary-file I/O is ignored and the destination is still replaced. The linearization point is the successful same-directory rename. Current cancellation and deadline tests arrange cancellation beforepublishPromptReportstarts, so they prove only the earlier check. Unbounded generated Markdown fromAUD-038can widen the unchecked write interval. - Contract at risk: Cancellation observed immediately before publication must preserve the prior operator-owned report; completed rendering does not itself authorize replacement after the run is canceled.
- Impact: A canceled command can replace an existing report and, with notification disabled, return success if cancellation occurs after the one check. With notification enabled it can instead report a later notification failure while retaining a publication the operator expected cancellation to prevent.
- Recommendation: Make the atomic writer context-aware, cleanly write and close its temporary file, then classify and return cancellation/deadline immediately before rename. Keep the application check for fast failure, but make the filesystem boundary own the final pre-commit observation.
- Test implications: Add a deterministic writer seam or controlled atomic-write
test that cancels after temporary-file close but before rename; assert the
old destination and empty
OutputPathremain, no notifier runs, and no temp survives. Retain the current cancellation-before-publication cases. - Validation: Cancellation or deadline made observable at the final pre-rename gate prevents replacement and returns the matching stable category; once rename succeeds, results truthfully report publication regardless of later notification outcome.
- Related findings:
AUD-038 - Remediation reference: pending
Retained Decisions
RET-001: Keep the application package as the explicit composition owner
The package import graph, outbound traces from GenerateDetailed,
RunBatchDetailed, and compareDetailed, and their focused tests were
inspected. internal/app has deliberately broad outbound dependencies because
it sequences project-owned domain, collection, execution, publication, and
notification contracts; those dependencies do not point back into app. Splitting
that fan-out merely to reduce a graph metric would obscure workflow ownership.
Reconsider only if a second composition owner emerges or a coherent workflow
can move behind a narrower contract without duplicating ordering policy.
RET-002: Keep external dependencies behind repository-owned contracts
Production import and data-flow inspection found Promptkit imports only in
internal/adapters/promptkit and Distributor imports only in
internal/adapters/distributor. The Promptkit adapter implements
promptexec.Executor; Weather API collection returns weatherdata.Bundle;
and Distributor results are translated before application and CLI summaries.
This explicit translation prevents dependency types and sensitive diagnostics
from becoming application contracts. Reconsider only when an upstream type is
intentionally adopted as a public repository contract with corresponding
architecture and compatibility changes.
RET-003: Keep publication mechanisms separate by artifact contract
Filesystem-write discovery and call traces were inspected for single reports,
comparison bundles, and secure prompt debugging. fileutil.WriteFileAtomic
owns one-file replacement, comparison.Publish owns guarded transactional
directory replacement and recovery, and promptdebug owns secure explicit
diagnostic files. Their shared use of temporary paths and rename operations is
mechanical similarity, while their authorization, permissions, commit, and
recovery semantics differ materially. Reconsider common abstraction only if
multiple artifact kinds acquire the same complete transaction contract.
RET-004: Keep collection as a narrow application-facing seam
The app.Collector contract, collect.Run, the Weather API adapter boundary,
and focused collection tests were inspected. Although collect.Run is small,
it keeps adapter creation and error context out of orchestration and gives app
tests a dependency-neutral deterministic seam. Reconsider if collection gains
no additional policy and an equally narrow project-owned adapter contract can
replace it without leaking transport construction into app.
RET-005: Keep report definitions explicit and independently reviewable
The four registry definitions were compared across stable ID, prompt and version, template and schema, period resolver, modules, output name, Distributor paths, and run-ID policy. Their similar structure is contract data, while meaningful differences include Daily's required date and dynamic output identity, Today's current local day and Morning membership, Tomorrow's next local day and both batch memberships, and Hourly's rolling elapsed period. Explicit definitions make those differences visible and focused tests assert the exact retained registry surface. Reconsider a shared declarative builder only if the registry grows enough that it can reduce maintenance while preserving independent review of every identity field.
RET-006: Keep command and configuration report lookup policies separate
IDForCommandName accepts only the four exact CLI command names, while
IDForConfigKey deliberately trims space, folds case, and normalizes hyphens
for configuration keys. Tests also reject retired aliases. Combining the two
lookups because their successful mappings are similar would risk broadening
the CLI contract or narrowing configuration compatibility. Reconsider only if
the accepted-input policies intentionally converge or registry metadata can
generate both maps without changing either boundary.
RET-007: Keep civil-day and rolling elapsed-time period policies distinct
Daily, Today, and Tomorrow use CivilDay, whose AddDate boundary correctly
produces 23-hour and 25-hour local days. Hourly instead adds six elapsed hours
to the injected clock, matching its documented rolling-horizon contract.
Batch coverage likewise walks the actual hourly instants within each civil
day, and focused app tests exercise daylight-saving dates. Unifying these
period calculations would conflate calendar and elapsed-time semantics.
Reconsider only if a report's product contract changes which kind of time it
represents.
RET-008: Keep configured output paths declarative during loading
Configuration preserves a nonblank output.directory exactly as supplied and
does not clean, absolutize, inspect, create, expand, or rebase it. App and
publication owners later resolve it against the invocation working directory
and perform destination-specific preflight. This keeps configuration loading
free of working-directory and filesystem lifecycle policy while still rejecting
a meaningless whitespace-only setting. Reconsider only if a new configuration
contract requires an intrinsic path property that can be checked without
assuming an execution destination.
RET-009: Keep inactive optional integrations free of active-field validation
Distributor templates and delivery settings are validated only when notification is enabled, and batch templates only when both Distributor and batch notification are enabled. Likewise, a blank Promptkit local endpoint means the backend is unregistered and its configured default concurrency is dormant; CLI mapping forwards the limit only with an endpoint, while the adapter rejects an active limit without one. This lets complete defaults and examples describe optional integrations without making unused credentials or templates mandatory. Reconsider only if dormant configuration must become an installation-time lint contract distinct from runtime configuration loading.
RET-010: Keep Promptkit profile source precedence in its adapter boundary
Configuration owns mutual exclusion of profile_file and profile_dir, URL
and concurrency shape, and the selected profile ID. The Promptkit adapter owns
opening configured sources, complete-definition precedence, embedded and
built-in fallbacks, and classification of malformed profile errors before
collection. Maintained profile tests exercise that boundary offline. Moving
profile parsing into config would duplicate dependency-specific behavior and
leak Promptkit mechanics. Reconsider only if Weatherreporter adopts a
repository-owned profile format independent of Promptkit.
RET-011: Keep command-specific parsers around one shared flag core
The three action parsers use separate flag.FlagSet values so unsupported
flags fail at the command that owns them: generate alone accepts --out, run
and compare accept --out-dir, compare alone accepts repeated --profile and
--replace, and only Daily/Today generate or compare accept --date.
addCommonFlags shares only config, units, timezone, and prompt-debug options.
This small explicit split keeps the acceptance matrix reviewable without a
general command framework. Reconsider if additional actions make the common
registration or post-parse checks materially larger or divergent.
RET-012: Keep action summaries distinct and bounded by CLI ownership
Generate, batch, and comparison have different public result contracts, so separate summary types and constructors are preferable to a generic result envelope. Generate copies selected fields and a compact notification result; batch preserves ordered report outcomes and independent notification status; comparison locks its field order and converts top-level failures to bounded safe categories. Pre-result failures emit no JSON, while result-bearing failures emit a failed summary before returning an error, and one shared output gate implements quiet mode. Reconsider common abstraction only if multiple actions acquire the same complete summary and failure semantics.
RET-013: Keep source-specific normalization explicit
The eight source methods share transport and missing-policy helpers but retain
visible source semantics: query parameters differ, hourly alone is required,
alerts alone treats explicit null as checked empty data, SPC empty arrays are
also checked data, and issue/update timestamps come from different payload
fields. Collapsing those methods into a generic descriptor would hide the
normalization decisions that need independent review. Reconsider a declarative
table only if it can express every source's availability, empty-value,
timestamp, and validation policy without callbacks that recreate the current
methods indirectly.
RET-014: Keep selected forecast periods structurally intact
Hourly, narrative, and daily selection uses half-open overlap and returns the complete normalized source periods in stable chronological order. It does not fabricate clipped forecast periods at the requested boundary; alert overlap is separately clipped because it explicitly represents an intersection. Focused tests protect inclusion of an already-active hourly period at a rolling horizon and exclusion at exact end boundaries. This preserves source identity, values, and timestamps for downstream reasoning. Reconsider only if a consumer requires synthetic boundary-aligned values and that interpolation or clipping policy is made explicit rather than implied by selection.
RET-015: Keep fact projection shallow under immutable workflow ownership
BuildCollected and CollectedFacts.Bundle allocate independent top-level
provenance and warning slices but retain pointers to normalized weather
products and nested values. Derivation treats those products as immutable, and
prepared-report construction later deep-copies complete inputs before handing
them to consumers. Deep-copying the full weather bundle at every fact boundary
would add repeated allocation without protecting a currently mutable seam.
Reconsider if facts begin mutating normalized products, an external consumer
can retain and mutate them concurrently, or the fact API promises complete
snapshot isolation rather than immutable workflow ownership.
RET-016: Keep module envelopes separate from briefing policy
internal/module owns stable IDs, option and fact vocabulary, outputs, and
ordered snapshots without importing report definitions. internal/briefing
owns the registry that connects those neutral contracts to report support,
fact availability, missing-data policy, builders, and prompt exporters.
Configuration validates through that registry and application composition
builds through the same one, so policy is not duplicated at the neutral
envelope layer. Reconsider only if another independent builder implementation
needs the same policy metadata or the neutral package acquires report-specific
behavior.
RET-017: Keep rich module values and curated prompt exports distinct
Module snapshots retain rich deterministic values for repository-owned render
contexts, while DataPackageValue selects a separately constructed prompt
value when the registry declares an exporter. Current conditions and hourly
forecast use this to omit lowercase, display-label, and precipitation-mention
helpers without weakening template facts; pass-through remains explicit for
modules whose complete structured value is intended for the prompt. Focused
tests compare both representations and prove prompt values are excluded from
snapshot JSON. Reconsider if the rich and prompt schemas converge completely
or a generated projection can preserve equally explicit field-level review.
RET-018: Keep checked-empty alert modules distinct from missing sources
The alert digest uses normalized source provenance and alert presence to emit
separate checked, missing, active-count, and relevant-count facts. Its
registry behavior permits an explicit empty module, so a successful check with
no alerts does not disappear like an unavailable optional source. This
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.
RET-021: Keep prompt category grouping explicit and serialization normalized
The prompt package owns a small fixed category vocabulary and emits metadata, applicable risk products, derived summaries, narrative products, and raw data in one explicit order while preserving module order inside each category. Stanza values pass through JSON normalization before YAML, preventing Go- and YAML-specific value shapes from becoming an accidental provider contract. The highest scoped marshal complexity operates over those fixed categories and service-sized module lists, so a generic reflective serializer would add policy opacity without a realistic cost benefit. Reconsider if the module catalog becomes large or category metadata can be generated from one reviewed manifest without weakening unknown/misplaced-stanza rejection.
RET-022: Keep neutral prompt execution lifecycle values dependency-owned
promptexec defines only repository-owned inspection and execution requests,
completed results, safe categories, validation rejections, and explicit debug
values. The interface carries context to both operations; callbacks run before
provider execution; validation rejection is a completed result, while
operational failure returns no result; errors preserve causes without exposing
their text; and mutable returned bytes/maps are copied. Promptkit production
types remain adapter-local. This is a coherent narrow seam rather than an
anemic duplicate of the dependency API. Reconsider only if a second executor
implementation demonstrates a missing neutral semantic; Promptkit-specific
cancellation and diagnostic translation remain Stage 11.
RET-023: Keep Promptkit source resolution and prepared handles in the adapter
The adapter supplies embedded prompt/schema/profile filesystems and configured profile sources directly to one Promptkit engine, then maps exact inspections and one-use prepared executions into repository-owned values. It does not reparse Promptkit YAML, merge profile fields, duplicate backend capacity, or expose dependency handles. Focused tests cover complete-source precedence, malformed matching definitions, built-in fallback, exact inline input, callback-before-provider ordering, credential checks, cancellation, and all stable error categories. Promptkit v0.5.0 explicitly documents its engine as concurrency-safe and has its own concurrent prepare/run contract test, while Weatherreporter's race-enabled adapter/app suites pass. Reconsider only if the dependency contract changes or Weatherreporter adopts an independent profile format requiring repository-owned resolution.
RET-024: Keep explicit prompt-debug storage separate from normal publication
The debug writer is disabled without an absolute operator root and otherwise
owns versioned per-report/run JSON records, restrictive directory/file modes,
validated path segments, atomic same-file replacement, and distinct comparison
run directories. Application callbacks make a requested preparation-write
failure stop before provider generation, and execution-write failure prevents
report publication. These authorization, sensitivity, and failure semantics
differ materially from ordinary Markdown and comparison publication, so a
generic filesystem writer would obscure the contract. Retain the separate
owner while remediating AUD-034 and AUD-035; reconsider shared mechanics
only after a common primitive can preserve every artifact's security and
transaction boundary.
RET-025: Keep report-specific generated values over shared day-style mechanics
Daily, Today, and Tomorrow currently share the same three fields and one
private validation path, but they remain distinct exported value types and
catalog handlers. This lets context builders reject a value validated for a
different report and leaves each report free to evolve without converting a
temporary structural similarity into a public interchangeability promise.
The private dayStyleFields projection, trimming, nonblank filtering, and
normalization remove mechanical duplication without erasing identity. Retain
that split while fixing catalog identity in AUD-036 and consolidating tests
under AUD-039; reconsider only if the reports intentionally adopt one shared
generated-text contract and migration policy.
RET-026: Keep semantic typed validation after schema validation
Promptkit's embedded JSON Schema validates provider shape, while
internal/generatedtext produces repository-owned typed values, trims prose,
removes blank day-style paragraphs, requires usable summaries/discussions, and
normalizes the result. Those are distinct boundary and domain responsibilities:
schema success alone cannot construct the typed render input, and decoding
alone should not make execution correctness depend on a particular executor.
Retain both layers and make their overlapping vocabulary and limits agree when
remediating AUD-037 and AUD-038; do not replace semantic validation with a
second general-purpose schema engine.
RET-027: Keep report-specific contexts over one generic template map
Hourly, Daily, Today, and Tomorrow contexts expose concrete report metadata,
generated-text types, and module surfaces. A private day-style projection
shares metadata and common module extraction, while separate builders retain
Daily outdoor/planning values, Today current-conditions and planning behavior,
Tomorrow planning, Hourly's rolling-period labels, and distinct ordered
daypart row types. Missing optional stanzas remain typed nil pointers, and
module values cross the context boundary through a JSON-normalized typed copy.
This is useful compile-time review friction: replacing it with a generic map
would make misspelled fields, cross-report leakage, and incidental template
compatibility harder to detect. Retain the typed split while adding the
identity checks in AUD-040.
RET-028: Keep templates as embedded assets with semantic output assertions
Each report's Markdown remains a separately reviewable embedded asset, and the
four shared partials centralize only identical alert, precipitation, or
daypart presentation policy. missingkey=error, template/partial-attributed
parse failures, and template-attributed execution failures make drift
actionable. Focused tests assert meaningful sections, conditions, exact
weather phrases where semantics matter, and relative ordering without a large
golden snapshot of every newline. Generated-text integration tests then render
real typed contexts across the package boundary. This combination protects
report variation and Markdown meaning with less incidental whitespace
coupling than full-output goldens; retain it while adding narrow regression
cases for AUD-041 and AUD-042.
RET-029: Keep one immutable prepared report as the execution boundary
preparedReport is the right shared boundary between collected weather and
profile execution. Preparation constructs facts, the ordered module snapshot,
briefing metadata, curated YAML, and the generated-text handler once. It deep-
copies mutable facts, snapshots, metadata, warnings, and YAML bytes; every
executor receives another byte copy, and every renderer receives newly cloned
typed inputs. Focused mutation tests protect caller and consumer isolation,
while concurrent comparison tests prove that one preparation can feed ordered
profile executions with identical bytes. Retain this explicit value boundary
rather than rebuilding report facts per profile or sharing mutable render
structures; complete catalog validation earlier as required by AUD-043.
RET-030: Keep side-effect-free prompt and profile inspection before collection
Application preflight asks the neutral executor for one exact prompt contract
and the effective safe profile identity before collecting weather. Batch
inspection caches a shared effective profile, comparison preserves requested
profile order and a successfully inspected prefix, credential checks expose no
secret value, and inspection errors are classified without copying dependency
diagnostics into normal text. This is the appropriate owner and stage for
remote-work prerequisites. Retain the inspection seam while expanding it to
cover catalog compatibility and complete, later-reconciled provenance under
AUD-043 and AUD-044.
RET-031: Keep same-directory temporary replacement for one report file
The narrow fileutil.WriteFileAtomic mechanism gives ordinary report
publication the right visibility boundary: it creates a mode-0600 temporary
file beside the selected destination, writes and closes complete Markdown, and
uses one same-directory rename as the linearization point. Failures before
rename leave an existing regular report unchanged, and deferred removal cleans
the temporary name after a rename error. This is simpler and safer than
truncating the destination in place or building a multi-file transaction for a
single artifact. Retain the mechanism while using a bounded temporary name,
enforcing valid target types, and adding the final context gate required by
AUD-045 through AUD-047; do not describe atomic visibility as crash
durability without explicit synchronization.
RET-032: Keep publication truth separate from notification success
publishPromptReport records ReportResult.OutputPath only after the atomic
rename succeeds, then constructs and sends the optional notification from that
exact operator-owned Markdown file. Every earlier failure returns an empty
published path; any request-construction or delivery failure afterward retains
the committed path and notification detail in the active result. The CLI
summary copies those fields and marks the action failed when the returned error
is non-nil, so it can distinguish an unpublished generation failure from a
published report whose delivery failed. Retain this ordering and result model;
batch aggregation policy remains Stage 16.
Open Questions
No Stage 1 open questions or unexplained baseline failures remain.
Stage 2 routed these investigation leads without treating graph metrics or an unusual edge as findings:
internal/configimportsinternal/briefingto use module definitions while validating report-module options. Stages 4, 8, and 23 should determine whether this remains the smallest single-owner validation path or creates avoidable registry coupling.- The graph identifies
internal/cli.Run,internal/app.RunBatchDetailed, andinternal/app.compareDetailedas relatively complex orchestration paths. Their ownership and top-level ordering are coherent; Stages 5, 16, 18, 22, and 23 should assess their local behavior and maintainability rather than inferring a finding from metrics.
Stage 3 routed these investigation leads to their assigned later stages:
LoadLocationaccepts numeric offsets through23:59, while the config reference does not state an offset range. Stage 4 should assess the effective configuration contract and validation ownership before judging that bound.- The report-registry document says focused tests cover run IDs and output
names, but Stage 3 found no behavioral run-ID assertion and only indirect
dynamic output-name coverage.
AUD-003captures the consequential run-ID gap; Stage 24 should reconcile the broader documentation claim with the eventual executable contract. - Prompt, template, schema, and module IDs are internally consistent at the report registry. Stages 8 and 10-13 remain responsible for validating the referenced registries, embedded assets, generated-text schemas, and render compatibility rather than duplicating those audits here.
Stage 4 routed these investigation leads to their assigned later stages:
- Configuration accepts any absolute URL scheme with a host, matching the documented term “absolute URL.” Stages 6, 11, and 16 should assess transport scheme support at the Weather API, Promptkit, and Distributor boundaries before narrowing the configuration contract.
weather_api.base_urlmay be empty in a valid configuration because only collection workflows require it. Stages 5-6 should verify action-specific failure timing and diagnostics rather than making every non-collection configuration require an endpoint.- Report config-key normalization accepts trimmed and case-folded spellings in addition to the documented hyphen/underscore equivalence. Stage 24 should decide whether the canonical reference should describe that compatibility or the executable contract should be narrowed.
- Distributor delivery redaction, runtime template consumption, and notification idempotency remain assigned to Stage 16. Stage 4 accounts only for active configuration and safe identity/path rendering before execution.
Stage 5 routed these investigation leads to their assigned later stages:
- Generate and batch summaries intentionally carry project-owned app error and warning fields, while comparison applies an additional bounded safe-error translation. Stages 6, 11, and 16 should verify that Weather API, Promptkit, and Distributor producers never place sensitive endpoint, credential, or provider detail in the non-comparison fields before Stage 24 judges the full documented output claim.
- Generate and comparison repeat the Daily/Today flag-acceptance and date defaulting policy. They currently agree, and the report registry remains the period owner. Stage 23 should assess whether a shared CLI policy descriptor would reduce drift without moving parsing into the domain registry.
- The static help text includes
--datein usage but omits it from the Options list and describes--quietas suppressing successful output even though it suppresses failed action summaries too.docs/internal/cli.mdalso still says the CLI dispatches only generation and batch actions despite implemented comparison dispatch. Stage 24 should reconcile these documentation/help statements without expanding Stage 5 into the repository-wide documentation audit. - Batch status writes deliberately ignore stderr write failures while JSON encoding errors are returned. Stage 25 should exercise broken-pipe and output writer failures dynamically before deciding whether routine-status I/O must affect the process exit.
Stage 6 routed these investigation leads to their assigned later stages:
facts.BuildCollectedandfacts.Bundlecopy source and warning slices but not nested query maps or warning slices. Prepared-report construction later deep-copies its complete inputs. Stage 7 should judge whether the earlier derivation boundary promises mutation isolation or whether immutable workflow ownership makes the shallow copy intentional.AlertRun.Rawpreserves the complete alert payload in addition to the raw alert items, but graph-augmented use search found no production consumer of the full duplicate. Stage 23 should assess removal with other unused and duplicate internal surfaces; alert-item schema and parsing remain assigned to Stages 7-9.- Source provenance retains the adapter-controlled query parameters in the normalized bundle, while prompt-facing metadata deliberately omits the query map. Stage 10 should confirm that no prompt input or debug artifact widens that boundary, and Stage 24 should judge whether the internal documentation needs an explicit non-secret-query invariant.
- The adapter accepts URL user information, base query parameters, and
fragments because the current absolute-URL check does not constrain them;
endpoint construction drops the fragment and carries base query values into
requests/provenance. The current configuration and examples use none of
these. Stage 24 should clarify the intended base-URL shape after remediation
of
AUD-011, rather than Stage 6 inventing undocumented authentication or query behavior.
Stage 7 routed these investigation leads to their assigned later stages:
DifferenceAtLeast,CrossesAtOrAbove, andCrossesBeloware exported forecast helpers with focused tests but no production callers in the graph. Stage 23 should assess removing this unused comparison surface rather than treating locally correct arithmetic as a derivation defect.- The forecast derivation document describes
BuildPeriodDailySummaries, but no such symbol or equivalent multi-day constructor exists in the implementation. Current facts intentionally build one civil-day summary for Daily, Today, and Tomorrow. Stage 24 should reconcile the named API and actual one-day report contract. - Planning and derived briefing consumers assign Fahrenheit/mph meaning to
the unitless daypart values identified by
AUD-016, and hazard wording may amplify the missed text classification inAUD-019. Stage 9 should account for those downstream effects while avoiding duplicate findings for the same derivation causes.
Stage 8 routed these investigation leads to their assigned later stages:
BuildMetadataand its broaderMetadatatype duplicate part of the metadata module path, but the application retains only a subset for the prompt package and uses the module stanza for briefing content. Stage 23 should assess consolidating this parallel construction without moving prompt package ownership into briefing.- Fact requirement constants, registry declarations, and the two availability switches must evolve together. They currently agree and focused tests cover default compositions, but Stage 23 should assess a definition shape that reduces switch/table drift without hiding missing-data policy.
- Current/hourly rich and prompt structs deliberately differ under
RET-017, yet their field-by-field copy code is sizeable. Stage 23 should evaluate whether small projection helpers or generated mappings reduce maintenance while preserving reviewable prompt curation. - AFD text, alert descriptions/instructions, weather-story content and URLs, and narrative fields currently use pass-through prompt exports. Stage 10 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 inAUD-017, invalid percentages inAUD-018, and incomplete ice classification inAUD-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. titleWorduppercases 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 byAUD-024andAUD-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-028is remediated.
Stage 10 routed these investigation leads to their assigned later stages:
promptinput.SaveandLoadYAMLhave no production callers, while the prompt-input document describes ordinary atomic saves and focused save coverage that do not exist.AUD-001andAUD-032capture the code risks; Stages 23-24 should decide whether to remove the unused API and reconcile the canonical document rather than expanding a dormant persistence workflow.- JSON unmarshalling reconstructs stanza order from map iteration and therefore does not promise deterministic order. There is no exported JSON loader or production consumer, and YAML is the execution format. Stage 23 should assess retiring or explicitly limiting this incidental surface instead of Stage 10 inventing a second ordering contract.
- The neutral interface passes context and makes debug detail explicit, but
provider cancellation, dependency error translation, and secure prompt-debug
publication belong to Stage 11. Application proof that serialization failure
prevents provider work belongs to Stage 14 with
AUD-033as its focused package-level prerequisite.
Stage 11 routed these investigation leads to their assigned later stages:
- Debug writer construction creates or tightens the explicitly requested root before prompt inspection and collection. This is authorized diagnostic state, not an ordinary-workflow violation, but Stage 24 should make the possibility of an empty root after later preflight failure explicit if operators need that lifecycle detail.
marshalDebugParametersand structured-schema capture discard impossible- under-current-Promptkit JSON marshal errors. Promptkit v0.5.0 constrains those values to JSON-compatible copies, so Stage 11 recorded no defect. Stage 23 should avoid generalizing the adapter around that assumption unless the dependency contract changes.- Application-level inspection ordering, exact prompt/profile consistency, and prepared-input reuse remain Stage 14; comparison scheduling and cancellation aggregation remain Stage 18. Stage 11 accounted for adapter context propagation and Promptkit's concurrent-engine contract without duplicating those orchestration findings.
Stage 12 routed these investigation leads to their assigned later stages:
- The application discards the canonical normalized JSON returned beside the typed generated value. This is harmless in the current render path and the package documents the return value, but Stage 23 should decide whether a production consumer justifies retaining that API surface rather than treating non-use alone as a defect.
encoding/jsonalso accepts duplicate object names with last-value wins. The embedded schema validator follows parsed-JSON semantics and no canonical contract currently promises duplicate rejection, so Stage 12 did not invent a uniqueness rule. Stage 25 may include duplicates in its broader adversarial corpus when validating the remediation forAUD-037.- Context construction, template fields, partial execution, and Markdown semantics remain Stage 13. Application proof that catalog and generated-text failures occur before publication remains with Stages 14-18; Stage 12 traced only enough of those callers to establish the raw-output validation boundary.
Stage 13 routed these investigation leads to their assigned later stages:
- Render contexts expose complete
.Collectedand.Derivedvalues plus a decoded.Modules.Metadataeven though current templates use neither and.Reportalready owns display metadata. This is not a current output defect, but Stage 23 should assess narrowing the maintainer surface afterAUD-040establishes one canonical identity and timing source. Renderreparses every top-level template and all four partials for every report. The embedded set is small and measured behavior is deterministic, so Stage 13 did not infer an efficiency defect. Stage 22 should compare a pre-parsed immutable template set only if repository-wide profiling shows rendering cost material.- Current template tests use concise package-local context structs while generated-text tests exercise the real typed contexts. That division gives useful focused and integration coverage, but Stages 20-21 should assess the large fixture surface and repeated report assertions across the whole test portfolio rather than Stage 13 rewriting unrelated cases.
Stage 14 routed these investigation leads to their assigned later stages:
executePreparedProfiledeliberately performs no durable write and ordinary generation checks context only after rendering, immediately before atomic publication. Stage 15 owns the final cancellation, destination-preservation, and partial-result truthfulness consequences; Stage 18 owns comparison-wide cancellation aggregation after concurrently started executions.cloneResolvedcopies the current slice-bearing AFD option explicitly while all other accepted module options are value-only. This is safe for the present registry, but the manual type switch can miss a future reference- bearing option. Stage 23 should assess an ownership-preserving clone contract if module option shapes expand rather than introducing reflection during this audit stage.- Preparation clones typed values through JSON, so impossible-under-current-
shapes clone failures remain wrapped but are not independently injectable.
Stages 20-21 should decide whether existing successful mutation isolation and
the actionable wrapper are sufficient when consolidating application
fixtures;
AUD-033separately owns the reachable stanza serialization gap.
Stage 15 routed these investigation leads to their assigned later stages:
- Existing directory symlinks are deliberately accepted for normal output
directories, but pathname components can change between preflight and rename.
AUD-045owns the final-component policy; Stage 25 should include a deterministic component-swap case when validating remediation rather than Stage 15 claiming containment the current operator-owned-path contract does not define. - The writer provides atomic visibility but does not call
Syncon the file or parent directory. No canonical document promises power-loss durability, so Stage 15 did not invent that guarantee. Stage 24 should keep “atomic” wording clear enough that operators do not read it as a persistence acknowledgment. WriteJSONAtomicremains outside the ordinary report path and is reached in production only through unused persistence helpers.AUD-001already owns that surface; Stage 23 should remove or justify it without mixing legacy JSON persistence into the single-Markdown publication contract.
Stage Log
Stage 1: Establish The Baseline And Audit Ledger
- Status: Complete.
- Scope reviewed: repository identity, worktree state, Go module and workspace context, graph identity, tracked package/source/test/fixture/asset/document inventory, and required baseline validation.
- Exclusions: none beyond the stage boundary; no production code, tests, dependencies, examples, or canonical documents were changed.
- Result: the audit is tied to exact commit
e7c7262; the expected-baseline difference is explained; every required baseline command passes; and all later stages remain pending. - Findings: none.
- Retained decisions: none.
- Open questions: none.
Stage 2: Audit Architecture And Dependency Direction
- Status: Complete.
- Scope reviewed:
cmd/weatherreporter, production import boundaries for all 24 Go packages, principal call and data-flow paths, external adapter containment, project-owned interfaces, publication boundaries, filesystem writes, ADR 0001, focused orchestration tests, and the architecture policy. - Exclusions: Detailed report/time, configuration, CLI, weather, domain, module, prompt, rendering, comparison, and notification correctness remains assigned to Stages 3-18. Cross-cutting complexity, deduplication, and full documentation coherence remain assigned to Stages 22-24.
Boundary Accounting
| Normative boundary or invariant | Implementation owner and evidence | Disposition |
|---|---|---|
| Binary entry and CLI ownership | cmd/weatherreporter.main calls cli.Run; internal/cli owns action parsing, config loading, executor construction, and bounded result output. Graph traces place app calls below action resolution. |
Matches policy. |
| Configuration ownership | internal/config owns defaults, YAML loading, normalization, secrets, and validation. CLI loads config and passes effective values down; app does not parse config files. |
Matches policy; the config-to-briefing validation edge is routed to Stages 4, 8, and 23. |
| Application orchestration | GenerateDetailed, RunBatchDetailed, and compareDetailed sequence preflight, collection, preparation, execution, publication, and notification through repository-owned values and interfaces. No lower package imports app. |
Matches policy; retained as RET-001. |
| Report and domain ownership | report, timeutil, weatherdata, forecast, facts, module, briefing, promptinput, generatedtext, and reporttemplate form one-way deterministic dependencies below app. |
Matches policy at package level; local rules remain for Stages 3 and 7-13. |
| Prompt execution boundary | promptexec.Executor is dependency-neutral. CLI constructs the Promptkit adapter once per action, app consumes only promptexec requests/results, and the prepared-report data flow supplies curated serialized module packages. |
Matches policy; retained as RET-002. |
| Weather API boundary | App calls its Collector interface; the production implementation delegates through collect.Run to adapters/weatherapi.FetchBundle, which returns normalized weatherdata.Bundle. Production HTTP imports occur only in external adapters. |
Matches policy; retained as RET-004. |
| Distributor boundary | App owns notification timing and repository request/result types; distributorNotifier translates them to adapter-local types and the adapter alone imports the Distributor dependency. Comparison exposes no notifier path. |
Matches policy. |
| Prompt preflight and curated inputs | Generate inspects the exact prompt/profile before collectWeather; batch inspects all candidates before its one collection; comparison inspects prompt and all profiles before collection, then calls prepareReport once. Data-flow traces reach promptinput.Build and MarshalYAML, not raw bundle serialization into Promptkit. |
Matches policy at workflow level; detailed checks remain for Stages 10, 11, 14, and 18. |
| Single output and notification order | publishPromptReport checks context, calls fileutil.WriteFileAtomic, records the path, and only then calls notifyReport. Focused tests cover pre-publication preservation and notification ordering. |
Matches policy; publication split retained as RET-003. |
| Batch output and notification order | RunBatchDetailed validates every planned path before sequential execution, suppresses item notification, continues across item failures, and calls notifyBatch only after the loop. Focused tests cover preflight, partial failure, and notification-after-publication. |
Matches policy at workflow level; detailed review remains for Stage 16. |
| Comparison publication | App preflights before external work and again before publication. internal/comparison owns bundle validation, guarded replacement, staging, commit, restoration, and cleanup results. The comparison request has no notifier and the trace does not reach Distributor. |
Matches policy at workflow level; detailed review remains for Stages 17-18. |
| Stateless execution | Normal traces retain preparation and execution values in memory. Production filesystem writes are limited to selected Markdown publication, selected comparison bundles, explicit prompt-debug capture, and the unreachable helpers in AUD-001; no cache, workspace, history, receipt, or resume owner appears in the package graph. |
Matches normal-runtime policy; AUD-001 records the unused persistence surface. |
| Dependency cycles and direction | The compiler-derived direct-import inventory is acyclic. Entry packages point inward, adapters do not import app/CLI, domain packages do not import orchestration, and no external dependency type appears outside its production adapter. | Matches policy. |
Commands And Evidence
- Refreshed graph project
home-eric-Workspace-weatherreporterin moderate mode. The graph's branch identity remains production baselinee7c7262; Stage 1's intervening commit changes only this excluded audit document. - Used graph architecture views for structure, dependencies, entry points, hotspots, boundaries, layers, and clusters; queried the graph schema and direct import edges.
- Used graph search, snippets, inbound/outbound call traces, and data-flow
traces for
main,cli.Run,GenerateDetailed,RunBatchDetailed,compareDetailed,prepareReport,executePreparedProfile,collect.Run, all three external adapters,publishPromptReport,comparison.PlanDestination,comparison.Publish, and Distributor notification. - Used compiler-derived
go listdirect imports to separate production edges from test-only graph edges and confirm the build has no import cycle. - Used graph-augmented code search to inventory external dependency imports, HTTP ownership, and production filesystem writes. A bounded text search confirmed the unused persistence helpers have no documentation or hidden non-code callers.
- Ran
go test ./internal/app ./internal/cli ./internal/collect ./internal/comparison ./internal/adapters/... ./internal/fileutil ./internal/promptdebug ./internal/promptexec; all focused packages passed. - Findings:
AUD-001. - Retained decisions:
RET-001,RET-002,RET-003, andRET-004. - Open questions: the two leads recorded above are routed to their assigned later stages.
Stage 3: Audit Report Identity And Time Foundations
- Status: Complete.
- Scope reviewed: all production and focused-test code in
internal/reportandinternal/timeutil; report and timezone contracts indocs/cli.md,docs/config.md, anddocs/internal/report-registry.md; and the focusedinternal/appandinternal/forecastcallers that consume batch membership, run IDs, civil periods, clock windows, and hourly coverage. - Exclusions: Effective configuration validation and CLI date parsing remain assigned to Stages 4-5. Forecast derivation beyond the time boundary remains assigned to Stage 7. Module, prompt, schema, and template compatibility remains assigned to Stages 8 and 10-13. Batch notification and Distributor idempotency behavior remains assigned to Stage 16, and documentation-wide coherence remains assigned to Stage 24.
Contract Accounting
| Contract or risk | Implementation and caller evidence | Disposition |
|---|---|---|
| Stable report identities | The default registry contains exactly Daily, Today, Tomorrow, and Hourly. Each definition carries the documented prompt 2.0.0, matching template/schema identity, modules, output name, and Distributor paths; focused registry tests assert the retained surface. |
Matches the registry contract; explicit definitions retained as RET-005. Asset compatibility remains for later owner stages. |
| Alias resolution | Command lookup accepts exact current command names. Config lookup separately normalizes whitespace, case, and hyphens. Both reject retired aliases in focused tests. | Matches the distinct boundary contracts; separation retained as RET-006. |
| Batch membership and ordering | Morning explicitly plans Today then Tomorrow before eligible Daily dates; Evening explicitly plans Tomorrow before eligible Daily dates. Focused app tests cover ordering, flags, multiple Daily dates, and no eligible dates. | Matches CLI and registry contracts. Detailed partial-success and notification semantics remain for Stage 16. |
| Report period policy | Daily requires an explicit date; Today uses an explicit date or current local date; Tomorrow selects the next local civil day; Hourly spans six elapsed hours from the injected clock. Resolve defaults a missing location to UTC and a zero clock to the system clock. |
Matches documented selection policy; calendar and elapsed policies retained separately as RET-007. |
| Civil-day and hourly DST behavior | CivilDay uses local midnight plus one calendar day, and batch hourly coverage walks actual instants, correctly accommodating 23-hour and 25-hour dates. Focused batch tests exercise DST civil days. |
Sufficient for the reviewed policy. |
| Configured clock windows | ClockWindow adds elapsed clock durations to local midnight. The only production path reaches forecast.ResolveDayparts, daily summaries, and facts. Fixed-zone tests do not exercise either DST transition. |
Incorrect on DST dates; recorded as AUD-002. |
| Date and timezone parsing | ParseLocalDate uses strict YYYY-MM-DD parsing in the selected location. LoadLocation covers IANA names, America/ fallback, documented aliases, fixed US abbreviations, and signed offsets, returning contextual errors for invalid input. |
Implementation is coherent for Stage 3. Effective config validation and CLI error presentation remain for Stages 4-5. |
| Clock control | All report resolvers accept an injected Now; only zero-valued input falls back to time.Now. Focused report and batch tests use fixed clocks. |
Sufficient for deterministic period selection. |
| Daily output and run identity | Daily output names derive from the resolved valid-start date, and Daily run IDs append that date so same-clock multi-date products remain distinct. The downstream batch-notification map rejects duplicate IDs. | Implementation is coherent, but the identity invariant lacks a focused behavior test; recorded as AUD-003. |
| Half-open period operations | Period.Valid, Contains, Overlaps, and Intersection consistently use [start,end) semantics, and focused tests protect overlap boundaries. |
Matches the internal period contract. |
Commands And Evidence
- Used graph search, source snippets, and inbound/outbound traces for the four
report definitions, registry construction and lookup, command/config mapping,
batch mapping and planning,
Resolve, every period resolver,CivilDay,ClockWindow,ParseLocalDate,LoadLocation, period overlap operations, Daily output naming, run-ID construction,ResolveDayparts, batch hourly coverage, andplannedReportsByRunID. - Compared the implementation with
docs/cli.md,docs/config.md, anddocs/internal/report-registry.md, then inspected the focused report, time, forecast, and app tests. Bounded text searches were used for string and test assertion discovery not represented by the code graph. - Reproduced the clock-window defect with
America/Chicagotransition dates: 17 elapsed hours after midnight yields 18:00 on 2026-03-08 and 16:00 on 2026-11-01; six elapsed hours yields 07:00 and 05:00 respectively. - Ran
go test ./internal/report ./internal/timeutil ./internal/forecast ./internal/app; all focused packages passed. - Findings:
AUD-002andAUD-003. - Retained decisions:
RET-005,RET-006, andRET-007. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 4: Audit Configuration, Secrets, And Validation
- Status: Complete.
- Scope reviewed: all production and focused-test code in
internal/config; the configuration-to-executor mapping ininternal/cli; Promptkit adapter configuration and focused tests; all three maintained examples;docs/config.md; and configuration-related architecture, operations, documentation, and testing rules. - Exclusions: CLI parsing and action-specific diagnostics remain assigned to Stage 5. Weather API transport behavior remains assigned to Stage 6. Promptkit source loading and sensitive execution diagnostics remain assigned to Stage 11. Output publication preflight remains assigned to Stages 15 and 17, Distributor delivery remains assigned to Stage 16, and repository-wide documentation coherence remains assigned to Stage 24.
Precedence And Validation Map
| Contract or risk | Implementation and consumer evidence | Disposition |
|---|---|---|
| Discovery and precedence | Load starts with Defaults, merges the explicit or default YAML path, tolerates only an absent implicit default file, applies nonempty units/timezone overrides, normalizes report modules, loads configured secrets, and validates. Representative CLI coverage proves explicit units/timezone beat file values while unrelated file values remain. |
Matches the documented defaults → file → CLI order. Environment variables do not override config fields. |
| YAML schema and migrations | mergeFile uses KnownFields(true); custom notification, report, Distributor-path, and module decoders reject their own unknown fields. Retired scriptorium, recent-change, and workspace keys have focused migration/rejection tests. |
Sufficient for known struct fields and retired top-level contracts. Unsupported keys inside the semantic missing-source map are AUD-004. |
| Defaults and partial merges | Exact documented defaults are built once in Defaults; YAML decoding merges partial nested structs over them, and focused tests cover default literals, omitted optional sections, batch partial configuration, and both maintained config examples. |
Matches the canonical reference. Exact-default assertions protect user-visible contracts rather than private constants. |
| Output directory | Config accepts empty, relative, and absolute values, preserves them byte-for-byte, and rejects only nonempty whitespace. Graph traces place path resolution, inspection, creation, and publication below app/filesystem owners. | Matches policy; retained as RET-008. |
| Weather and daypart validation | Config checks absolute base URLs when present, positive timeout, nonnegative precision, nonempty units/timezone, resolvable timezone, JSON format, nonempty dayparts, names, and parseable clocks. Collection separately requires a base URL for collection actions. | Matches the documented split. The Stage 3 DST defect remains AUD-002. |
| Missing-source policy | Policy values and nonblank keys are validated, and the adapter consumes source-specific values by source name with a default fallback. Required hourly failures bypass optional-source policy. | Unsupported and ineffective keys pass silently; AUD-004. |
| Report/module overrides | Loaded aliases, duplicate normalized reports, module identities/order, compatibility, duplicate modules/stanzas, typed options, explicit empty lists, and Distributor path templates are validated through report/module registries. | Loaded YAML behavior is well covered. Exported constructed values cannot establish private presence state; AUD-006. Detailed module semantics remain for Stages 8-9. |
| Promptkit settings | Config validates mutually exclusive profile sources, positive timeout, optional absolute local endpoint, and nonnegative concurrency. CLI maps a dormant endpoint-less limit to the adapter zero value; adapter construction owns file/directory loading, backend registration, fallbacks, and safe error classification. | Matches the documented inactive/active split; retained as RET-009 and RET-010. |
| Secret directory | Empty disables loading. A configured directory accepts only directly contained regular files with environment-variable basenames, rejects symlinks/directories/nonregular/unreadable entries, removes one LF or CRLF, overwrites the named environment value, and reports only directory/file names and operation context. | Values are absent from tested diagnostics, but failure application is not atomic; AUD-007. |
| Distributor configuration | Disabled notification skips active-field checks. Enabled notification validates endpoint, token variable name, timeout, failure policy, allowed template variables, batch activation, and report path safety/uniqueness. Templates render from repository-owned metadata rather than environment values. | Mostly sufficient. Two single-report rendered identities omit the nonblank invariant; AUD-005. |
| Filesystem and URL timing | Config validates intrinsic string/URL shape. Output destinations are inspected by app/publication owners, Promptkit sources by its adapter, and secret entries by the config loader. No audit command contacted an external service. | Matches ownership boundaries for Stage 4; transport-specific scheme support is routed to later stages. |
| Constructed configuration | Validate, report override accessors, and focused tests attempt to provide loaded/constructed consistency without mutating caller-owned option values. |
General scalar validation is consistent, but report override presence is not externally constructible; AUD-006. |
| Maintained examples | Both YAML configuration examples load through the strict config suite. The endpoint-only weather-light profile is inspected through the real Promptkit adapter with embedded assets and no network or credential. All examples contain synthetic endpoints and no secrets. |
Matches documentation and testing policy. |
Commands And Evidence
- Used graph search, source snippets, call traces, and graph-augmented test
searches for defaults,
Load, YAML merging and migration rejection,Validate, secret loading, report/module/path traversal, notification renderers, CLI executor mapping, Promptkit adapter construction, Weather API missing-source policy consumption, and app notification consumers. - Compared executable fields, defaults, precedence, templates, paths, profile
settings, source keys, and secret behavior with
docs/config.md, maintained examples, and the configuration-related operations and architecture rules. - Ran the production CLI with
examples/minimal-config.yml. It reached offline profile credential preflight and returnedmissing_credentialbefore weather collection, confirming that the endpoint-less local concurrency default is intentionally dormant rather than rejected by adapter construction. - Ran
go test ./internal/config ./internal/adapters/promptkit ./internal/cli; all focused packages passed. - Findings:
AUD-004,AUD-005,AUD-006, andAUD-007. - Retained decisions:
RET-008,RET-009, andRET-010. - Open questions: the four leads recorded above are routed to their assigned later stages.
Stage 5: Audit CLI Parsing, Wiring, And Output Contracts
- Status: Complete.
- Scope reviewed:
cmd/weatherreporter, all production and representative tests ininternal/cli,docs/cli.md,docs/internal/cli.md, immediate config/report/time/executor mapping, and app request/result/error boundaries. Comparison parsing and CLI translation were reviewed, while detailed comparison execution remains excluded. - Exclusions: Weather collection, report generation, publication, notification, and comparison execution correctness remain assigned to Stages 6-18. Cross-cutting parser deduplication, test portfolio durability, documentation coherence, and dynamic robustness remain assigned to Stages 21 and 23-25.
Command And Exit Accounting
| Command or risk | Parsing, result, and exit evidence | Disposition |
|---|---|---|
| Root help and version | No arguments, -h, and --help print static help without config/provider setup. Exact --version prints the injected or build version; extra version arguments fail. Main prints any returned error to stderr and exits 1; nil returns normally. |
Matches the documented root contract. Help wording drift is routed to Stage 24. |
| Generate dispatch | Requires one of four exact report names. Command-specific flag sets accept only documented flags; Daily requires a date, Today defaults or parses one, and Tomorrow/Hourly reject --date. Config and explicit output/working-directory values map into one app request and one executor. |
Functional mapping matches, but date validation occurs after executor construction; AUD-009. Assembled result/exit coverage is AUD-010. |
| Run dispatch | Requires exact Morning or Evening batch identity, accepts --out-dir rather than --out, resolves only explicit relative overrides against the absolute invocation directory, preserves configured fallback, and constructs one executor/request. |
Matches the parser/wiring contract. Representative assembled result coverage is incomplete in AUD-010; detailed batch behavior remains Stage 16. |
| Compare dispatch | Requires a valid report and two distinct nonblank ordered profiles before config/executor work; applies the same report date eligibility; clears the configured default profile; maps exact bundle destination/replace/debug options; constructs one executor. | Matches the CLI contract. Focused tests cover parser, construction frequency, pre-execution failure, success/partial/cleanup summaries, quiet behavior, and safe errors. Detailed execution remains Stage 18. |
| Common flags and ownership | addCommonFlags registers config, units, timezone, and prompt debug only. Each action separately registers its output, quiet, date, profile, and replacement flags. Report and batch name lookup delegates to internal/report; config precedence delegates to internal/config. |
Intentional explicitness retained as RET-011; repeated date policy is routed to Stage 23. |
| Validation ordering | Unknown commands/reports, unsupported flags, extra args, and comparison profile shape fail before configuration or executor creation. Generate performs Daily presence and Daily/Today parsing after config and executor creation. | Partly incorrect; AUD-009. |
| Output paths | CLI captures/validates one absolute working directory, preserves configured fallbacks, and resolves only nonempty relative explicit output overrides against that directory. App owns default naming and publication. | Matches CLI/config documentation and architecture ownership. |
| Stdout, stderr, and quiet | Result-bearing actions emit indented JSON to stdout. Run emits compact ordered item/notification/batch lines to stderr first. Quiet returns before both routine channels but the returned error still reaches main stderr. Pre-result failures emit no structured output. | Matches the documented separation; summary design retained as RET-012. Broken status-writer behavior is routed to Stage 25. |
| Generate summaries | Selected report, prompt, time, profile/model, warnings, validation, output, debug, notification, and error fields are copied; status is failed exactly when a result accompanies an error. | Shape matches documentation, but only the constructor is tested; assembled behavior is AUD-010. Producer redaction remains for Stages 6, 11, and 16. |
| Batch summaries and exit | Report counters remain independent of notification failure. Any failed report or failed notification makes summary status/error failed, emits status lines unless quiet, and returns typed BatchError so main exits nonzero. |
Mapping is coherent and notification failure is assembled-tested. Broader representative translation coverage is AUD-010. |
| Comparison summaries | Field order is fixed by the explicit DTO; profile order is retained; unpublished artifact fields are omitted; errors are mapped to bounded aggregate, cancellation, deadline, Promptkit, destination, cleanup, or application categories. | Matches the documented safe CLI contract with strong focused coverage. |
| Cancellation | Runner.Run forwards its context to every app action, and app tests protect cancellation behavior. Production main supplies an uncancelable background context and has no signal bridge. |
Programmatic propagation is correct; executable cancellation is AUD-008. |
| Test ownership | Parser/resolver tests own flag acceptance, paths, configuration mapping, and one-executor construction; summary tests own selected fields; comparison command tests own assembled translation. | Comparison is sufficient. Non-comparison assembled coverage is insufficient in AUD-010; lower workflow details correctly remain in app tests. |
Commands And Evidence
- Used graph inventory, source snippets, inbound/outbound traces, and graph-augmented searches for root dispatch, every action parser/resolver, common flag registration, working/output path resolution, executor mapping, summary/status construction, output routing, error classification, app cancellation consumers, and process signal handling.
- Compared executable help, accepted flags, summary fields, error/status rules,
and action wiring with
docs/cli.md,docs/internal/cli.md, and the configuration, architecture, documentation, and testing contracts. - Ran
go test -cover ./internal/cli; the suite passed at 75.8% statement coverage. Coverage was used only to guide test inspection, not as a finding. - Ran
go run ./cmd/weatherreporter --help,go run ./cmd/weatherreporter --version, andgo run ./cmd/weatherreporter generate daily; help and development-version paths succeeded, while missing Daily date returned the documented pre-result error and nonzero status without contacting an external service. - Findings:
AUD-008,AUD-009, andAUD-010. - Retained decisions:
RET-011andRET-012. - Open questions: the four leads recorded above are routed to their assigned later stages.
Stage 6: Audit Weather Data Acquisition And Collection
- Status: Complete.
- Scope reviewed: all production code and focused tests in
internal/adapters/weatherapi,internal/weatherdata, andinternal/collect; all eight adapter fixtures; the Weather API integration, normalized weather-data, and collection internal documents; and immediate app/prompt-facing consumers needed to account for provenance, warnings, and error exposure. - Exclusions: Meteorological selection, alert interpretation, forecast/fact derivation, and aliasing policy remain assigned to Stage 7. Prompt-facing curation remains assigned to Stages 8-10, app orchestration to Stages 14-16, cross-cutting cleanup to Stage 23, documentation reconciliation to Stage 24, and adversarial dynamic checks to Stage 25.
Source And Failure Accounting
| Source or transport risk | Required, empty, malformed, and provenance behavior | Disposition |
|---|---|---|
| Warmup | Calls /conditions/current first with format, units, and precision; requires a readable 2xx body; closes it; and stops before source requests on failure. Context cancels requests and retry waits. |
Endpoint and cancellation behavior match. Permanent statuses are retried contrary to the shared policy in AUD-015; body safety/limits are AUD-012 and AUD-013. |
| Observations | Optional. Missing/null and malformed data use configured policy. A successful value records observation timestamp as issue time plus endpoint, query, fetch time, and compact-data hash. | Matches the source contract. |
| Current conditions | Optional. Missing/null and malformed data use configured policy; successful normalized fields and provenance are retained. | Matches the source contract. |
| Hourly forecast | Required regardless of optional-source policy. Missing/null, decode failure, and an empty period list fail collection; format, units, precision, and timezone are sent. | Availability mapping matches, but periods with zero/reversed time bounds pass; AUD-014. |
| Narrative forecast | Optional. Missing/null and malformed data use configured policy; issue/update timestamps and forecast periods are normalized. An empty period list remains checked data. | Matches the documented source contract. Semantic forecast use remains Stage 7. |
| Active alerts | Optional. An absent member is missing, explicit null is checked empty data with a hash and no warning, and malformed non-null data uses configured policy. Alert items remain raw payloads at this boundary. |
Matches the explicit alerts exception. Full duplicate raw payload is routed to Stage 23. |
| Forecast discussion | Optional. Missing/null and malformed data use configured policy; issue/update times, key messages, and short/long sections are normalized. | Matches the source contract. |
| Weather story | Optional. Uses format only; missing/null and malformed data use configured policy; start/update provenance is retained when available. | Matches the source contract. |
| SPC convective outlooks | Optional. Uses format/timezone without units; missing/null uses configured policy; non-null empty outlook/discussion lists are checked data; issue time prefers issuedAt and falls back to asOf; GeoJSON remains raw. |
Matches the explicit SPC empty-data and provenance contract. |
| Optional-source policy | error aborts with no partial bundle, warn records the same stable warning in source and bundle, and none records a missing source without a warning. Transport/status/envelope failures remain direct request errors. |
Matches the documented division. Unsupported policy keys remain the Stage 4 finding AUD-004. |
| Endpoint construction | Joins every fixed endpoint to a base path prefix and applies only the required format/units/precision/timezone matrix. | Query/path behavior matches for HTTP(S); unsupported absolute schemes survive until runtime in AUD-011. Base URL query/user-info shape is routed to Stage 24. |
| HTTP attempt lifecycle | Requests carry context and configured client timeout; response bodies close after bounded reads; source attempts retry transport/read failures and only the documented transient statuses. | Mostly coherent. Unsafe response text and silent truncation are AUD-012/AUD-013; warmup classification divergence is AUD-015. |
| Normalization and collection ownership | weatherdata contains no HTTP/config/filesystem behavior. The adapter translates wire envelopes into project types. collect.Run constructs one adapter, forwards context, and distinguishes setup from fetch errors. |
Matches architecture; the narrow collection seam remains retained under RET-004. Source-specific normalization remains explicit under RET-013. |
| Test assets and ownership | Adapter tests use httptest.Server for HTTP/query/status/retry/cancellation/policy behavior and eight small checked-in JSON fixtures for translation. weatherdata owns a focused GeoJSON round trip; collect owns three real local composition/error cases. Fixtures are synthetic, credential-free, and total under 5 KiB. |
Ownership is distinct and offline. Missing high-risk cases are attached to AUD-011 through AUD-015, not inferred from coverage alone. |
Commands And Evidence
- Used graph architecture, symbol search, source snippets, inbound traces, and
graph-augmented use searches for adapter construction, all eight source
methods, HTTP attempts and retries, body lifecycle, endpoint/query assembly,
missing/malformed policy, hashes and timestamps, normalized source consumers,
collect.Run, and propagation toward app/CLI boundaries. - Compared implementation and fixtures with
docs/integrations/weatherapi.md,docs/internal/weather-data.md,docs/internal/collect.md, the architecture policy, and the testing policy. Bounded text inspection was used for documentation, JSON fixtures, literal credential markers, and known test assertions outside graph discovery. - Ran
go test -coverprofile=/tmp/weatherreporter-stage6-cover.out ./internal/adapters/weatherapi ./internal/weatherdata ./internal/collect; all focused packages passed. Adapter statement coverage was 84.9% and collection coverage 100%; coverage was used only to guide branch inspection. - Findings:
AUD-011,AUD-012,AUD-013,AUD-014, andAUD-015. - Retained decisions: existing
RET-004was revalidated andRET-013records explicit source normalization. - Open questions: the four leads recorded above are routed to their assigned later stages.
Stage 7: Audit Forecast And Fact Derivation
- Status: Complete.
- Scope reviewed: all production code and focused tests in
internal/forecastandinternal/facts; the forecast derivation and facts internal documents; normalized forecast types; and immediate briefing/app consumers needed to determine unit meaning, mutation ownership, and whether derived facts participate in normal workflows. - Exclusions: Detailed source-facing, derived, planning, formatting, and SPC briefing behavior remains assigned to Stages 8-9. Cross-cutting test portfolio and cleanup remain assigned to Stages 19-23, documentation reconciliation to Stage 24, and adversarial dynamic checks to Stage 25.
Derivation Rule And Edge-Case Accounting
| Derivation rule or risk | Boundary, ordering, and test evidence | Disposition |
|---|---|---|
| Valid-period selection | Hourly, narrative, and daily runs use half-open overlap, retain complete source periods, and stable-sort by start time. Nil runs return no facts. Focused tests cover unsorted input, partial overlap, and exact-boundary exclusion. | Matches the source-period contract; complete-period selection is retained as RET-014. Structurally invalid required periods remain AUD-014. |
| Civil-day summaries and dayparts | Daily summaries require hourly data, resolve configured local windows, and select each daypart independently. Empty dayparts remain explicit summaries; overnight hourly selection crosses midnight. | Ordinary and empty behavior matches. DST window construction remains AUD-002; after-midnight alert selection is incorrect in AUD-017. |
| Temperature and wind aggregation | Min/max and peak selection is deterministic for populated values; numeric heat/cold thresholds distinguish Fahrenheit and Celsius. Wind and all unitless summary fields fall back between explicit unit fields without conversion. | Incorrect for metric fallback values; AUD-016. Paired unit-system regressions are absent. |
| Condition indicators | Text matching is case-insensitive; numerical heat, cold, and wind signals merge monotonically across periods. Dominant-condition ties resolve lexically and notable conditions sort. | Deterministic, but common “icy” text is missed; AUD-019. Broader briefing interpretation remains Stage 9. |
| Precipitation timing | Input is copied and stable-sorted. Exact threshold inclusion, dry input, gaps, maxima, text aggregation, thunder mentions, and intentionally open final windows have focused tests. | Window mechanics match. The percentage domain is unenforced in AUD-018; the open final window is an intentional horizon signal. |
| Alert parsing and overlap | Supported time-field fallbacks, malformed omission, instruction whitespace, half-open clipping, ordering, and direct boundary cases have focused tests. | Direct behavior matches. Daily prefiltering loses alerts in wrapping dayparts; AUD-017. |
| Collected facts and mutation | Collection projects every normalized product and copies top-level sources/warnings. Bundle reconstruction repeats those top-level copies; derivation reads shared nested values without mutation. | Fits immutable workflow ownership and is retained as RET-015; no speculative deep-copy requirement was added. |
| Report-family derived facts | Every report selects valid-period hourly, narrative, daily, alert, precipitation, and SPC facts. Daily/Today/Tomorrow additionally build one daily/daypart summary; Hourly does not; unknown reports and invalid periods fail. | Matches the implemented report-family contract with focused cases. The stale multi-day documentation name is routed to Stage 24. |
| SPC outlooks and discussions | Only valid overlapping outlook periods are selected. Output ordering uses day, type, severity rank, validity, label, and ID; discussions are retained only for selected days and sorted deterministically. Nil and checked-empty runs remain empty facts. | Matches documented checked-empty and deterministic ordering behavior with focused tests. Detailed briefing meaning remains Stage 9. |
| Repeated work and realistic size | One derived build performs several linear selections, then each configured daypart scans the hourly run and its alert candidates; selected precipitation and SPC slices are sorted. With the normal four dayparts and service-sized runs of a few hundred periods, this is only low-thousands of simple overlap checks. | No Stage 7 performance finding. Reassess in Stage 22 only if horizons, daypart counts, or alert volumes become materially larger or derivation is repeated per module. |
| Test ownership | forecast owns selection, summaries, thresholds, precipitation, parsing, overlap, ordering, and fixture integration. facts owns projection, report-family derivation, SPC filtering/order, and failure cases. timeutil owns the underlying period rules. |
Ownership is coherent. Missing high-risk unit, overnight-alert, percentage-domain, and icy-text cases are attached to AUD-016 through AUD-019, not inferred from coverage alone. |
Commands And Evidence
- Used graph architecture, symbol search, source snippets, call traces, and graph queries for every scoped derivation function, report-family consumers, unit-bearing normalized fields, comparison helpers, loops, sorting, and immediate unit interpretations. Text search was limited to documents, literals, fixtures, and focused assertions where graph discovery was not applicable.
- Compared implementation and tests with
docs/internal/forecast-derivation.md,docs/internal/facts.md, and the architecture, documentation, and testing policies. Existing Stage 3 and Stage 6 findings were treated as dependencies rather than duplicated. - Ran
go test -coverprofile=/tmp/weatherreporter-stage7-cover.out ./internal/forecast ./internal/facts ./internal/timeutil; all focused packages passed. Statement coverage was 89.7% for forecast, 73.4% for facts, and 54.5% for timeutil; coverage guided branch inspection but was not itself a finding. - Findings:
AUD-016,AUD-017,AUD-018, andAUD-019. - Retained decisions:
RET-014andRET-015. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 8: Audit Module Contracts, Registry, And Source-Facing Briefing Modules
- Status: Complete.
- Scope reviewed:
internal/module; briefing registry and package contracts; metadata, current conditions, narrative forecast, hourly forecast, alert digest, area forecast discussion, and weather story builders and their directly related tests; report default composition; configuration module normalization/validation; and the relevant module, briefing, and prompt-input internal documentation. - Exclusions: Derived daily/daypart, precipitation, outdoor/planning, detailed formatting, and SPC builders remain assigned to Stage 9. Complete prompt serialization and asset contracts remain Stage 10, application preparation remains Stage 14, cross-cutting cleanup remains Stage 23, and documentation coherence remains Stage 24.
Registry And Source-Module Accounting
| Contract or module | Registry, transformation, and test evidence | Disposition |
|---|---|---|
| Neutral module envelope | Stable IDs and fact/option vocabulary live in internal/module; snapshots preserve caller order, omit runtime prompt values from JSON, reject missing identities and duplicates, and support typed rich-stanza lookup. |
Separation is retained as RET-016. Exact version compatibility is not enforced; AUD-022. |
| Registry initialization | Default definitions have unique IDs/stanzas, builders, supported reports, typed defaults, missing-data behavior, requirements, and prompt-export policy. Constructor tests cover duplicates, absent builders, and unsupported warn; default report compositions validate. |
Default metadata is coherent. Unknown custom missing-data values fail only at build time; low-impact constructor hardening is left for Stage 23 unless the registry becomes extensible. |
| Configuration and options | Configuration normalizes raw YAML into registry-declared option types, validates compositions through the same registry, and preserves report order. Only AFD has nonempty default selections and consumes a section list. | IDs/defaults/order agree. Accepted pointer/value option shapes diverge at the AFD builder; AUD-020. |
| BuildModule policy | Lookup, report support, option shape, fact availability, omit/error/empty behavior, output identity, and prompt export execute in one boundary. Exporter failures receive module/stanza context and outputs are identity-checked before export. | Coherent for defaults. The branching is policy-driven rather than per-module switch testing; builders are exercised through public registry behavior. |
| Metadata | Emits report identity, variant, prompt, generation/period context, configured units/timezone/location, and bounded source-warning summaries without endpoint/hash provenance or alert duplication. It remains available with empty source metadata. | Matches the documented prompt-safe module contract with focused field/omission tests. Parallel non-module metadata construction is routed to Stage 23. |
| Current conditions | Optional source; rounds explicit unit fields, retains day/condition/humidity, derives compass labels, omits a wholly empty value, and uses a curated prompt projection without lowercase/display helpers. | Matches source and prompt-export contracts with focused rich/prompt tests. |
| Narrative forecast | Daily/Today/Tomorrow only; requires both source presence and selected derived periods, maps only valid-period values, copies unit-bearing fields, and produces local friendly bounds and compass wind labels. Missing or empty selections omit the module. | Matches support, selection, transformation, and omission contracts with focused tests. Invalid normalized percentages remain AUD-018. |
| Hourly forecast | All reports; requires source presence and selected periods, maps the complete selected weather fields, derives labels/compass direction, and marks precipitation at the exact 20-percent threshold. Its prompt export omits rich-only labels and mention helpers. | Matches support, boundary, rich-value, and curated-export contracts with focused tests. Invalid period/percentage inputs remain AUD-014/AUD-018. |
| Alert digest | All reports with explicit empty behavior; distinguishes checked-empty and source-missing states, counts raw active and relevant overlaps separately, preserves derived order, and formats full alert periods with guidance. | Matches availability and value contracts; distinction retained as RET-018. Overnight relevance remains the upstream AUD-017. |
| Area forecast discussion | Optional for all reports; empty section lists mean all sections, report defaults select long-term for Daily and key messages/short-term for Hourly, unsupported names fail, and selected slices are copied. | Section policy and defaults agree, but pointer-shaped accepted options fail construction; AUD-020. |
| Weather story | Optional for all reports; copies story content, flags, URL, update time, and friendly valid-period labels. | Populated and missing behavior is tested. Structurally empty data is incorrectly emitted as available; AUD-021. Complete prompt curation remains Stage 10. |
| Mutation and ordering | Snapshot outputs copy the outer slice; source modules copy scalar pointers and AFD key-message slices. Prepared-report construction later deep-copies complete inputs. Registry and report order flow unchanged into snapshots. | No shared-mutation finding under immutable preparation ownership. Rich/prompt separation is retained as RET-017. |
| Complexity drivers | BuildModule centralizes validation/missing/export branching; fact availability uses parallel requirement switches; default definitions form a long policy table; current/hourly prompt projections copy many fields. Builders otherwise perform small linear maps over service-sized periods/alerts. |
No Stage 8 performance finding. Table/switch drift, parallel metadata, and projection duplication are routed to Stage 23 with concrete owners. |
| Test ownership | Module tests own snapshot structure/order and rich-versus-prompt selection. Briefing registry tests own default composition, support, options, missing behavior, output/export validation, and assembled snapshots. Source-module tests own observable transformations and omissions; config tests own YAML normalization. | Mostly sufficient and behavioral. Missing pointer-consumption, empty-story, and unsupported-schema cases are attached to AUD-020 through AUD-022. |
Commands And Evidence
- Used graph architecture, symbol search, source snippets, inbound traces, and graph queries for snapshot consumers, registry/config validation, all scoped builders and exporters, source fields, default report compositions, tests, shared-state copying, and complexity drivers. Text inspection was limited to scoped documentation and targeted source/test bodies.
- Compared implementation and focused tests with
docs/internal/module.md, relevant portions ofdocs/internal/briefing.mdanddocs/internal/prompt-input.md, and the architecture, documentation, and testing policies. - Ran
go test -coverprofile=/tmp/weatherreporter-stage8-cover.out ./internal/module ./internal/briefing ./internal/config ./internal/report; all focused packages passed. Statement coverage was 86.5% for module, 82.5% for briefing, 86.1% for config, and 48.8% for report; coverage guided risk inspection but was not itself a finding. - Findings:
AUD-020,AUD-021, andAUD-022. - Retained decisions:
RET-016,RET-017, andRET-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.
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, andAUD-028. - Retained decisions:
RET-019andRET-020. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 10: Audit Prompt Inputs, Assets, And Neutral Execution Contracts
- Status: Complete.
- Scope reviewed: all production and focused-test code in
internal/promptinput,internal/promptassets, andinternal/promptexec; every embedded prompt, Promptkit profile, input schema, and output schema; prompt-input and generated-text internal documents; the Promptkit integration contract; and immediate briefing/application consumers needed to establish the provider-boundary data flow. - Exclusions: Promptkit adaptation, provider cancellation/error translation, and secure debug files remain Stage 11; output schemas and generated-text semantics remain Stage 12; application preparation/provider preflight remains Stage 14; cross-cutting API cleanup and documentation coherence remain Stages 23-24; and adversarial repository-wide checks remain Stage 25.
Prompt Boundary And Execution Accounting
| Contract or risk | Serialization, asset, caller, and test evidence | Disposition |
|---|---|---|
| Curated work-package construction | Application preparation reaches promptinput.Build and MarshalYAML from ordered module DataPackageValue exports, not normalized bundle serialization. Report identity, local date, period, and module categories are explicit. |
Module values follow the curated path, but the top-level raw warning copy violates it in AUD-029. |
| Category order and normalization | One fixed table groups known stanzas as metadata, applicable risk, derived, narrative, and raw data; module order is retained within groups. Values normalize through JSON before YAML, and repeated marshals are byte-identical in focused tests. | Coherent and retained as RET-021; missing failure-branch coverage is AUD-033. |
| Package validation and loading | Current schema, report/run metadata, ordered stanza presence, duplicate order entries, known category membership, and misplaced stanzas are rejected. A temporary table-driven probe exercised additional malformed shapes. | Incomplete for required metadata, orphan values, unknown struct fields, and trailing documents; AUD-032. |
| Embedded prompt identities | Exactly four report definitions bind the registry's prompt ID/version, embedded profile, weatherreporter.data_package.v4 input schema, and matching generated-text output schema. Real offline Promptkit inspection succeeds for each. |
Identity is coherent; common prose references nonexistent serialized paths in AUD-030. |
| Safety-sensitive prompt semantics | The shared prompt prioritizes risk, forbids invention, and asks regional discussion limitations to be preserved. | Its blanket assertion that all retained products are location matched contradicts SPC module semantics; AUD-031. |
| Embedded profiles and schemas | Exactly three profiles carry the intended backend/model/timeouts/reasoning/tier settings; tests reject unsafe/incidental profile settings and retired runtime strings. Embedded schema bytes are independently copied. | Matches the scoped identity and immutability contracts. Output-schema field meaning remains Stage 12. |
| Inspection and execution lifecycle | Dependency-neutral requests carry context and exact prompt/profile identity. Execution runs validation callbacks before provider work and distinguishes completed validation rejection from operational failure without a result. | Coherent and retained as RET-022; adapter realization remains Stage 11. |
| Safe errors, debug, and result isolation | All stable error categories are covered; safe errors preserve causes while omitting cause text; messages and invalid UTF-8 diagnostics are bounded; debug is opt-in; returned output/debug bytes and maps are copied. | Sufficient at the neutral contract boundary. Dependency diagnostics remain Stage 11. |
| Complexity and realistic input size | The highest scoped production path is stanza YAML marshaling, with two shallow loops over five fixed categories and the report's service-sized module list. Asset lookup and neutral result construction are constant or small linear work. | No Stage 10 performance finding; explicit grouping remains cheaper to reason about than generic reflection. |
| Test ownership | Prompt-input tests own construction, validation, grouping, ordering, category placement, and YAML round trips; prompt-assets tests own embedded inventories/identities and real inspection; prompt-exec tests own lifecycle, category, error, debug, bound, and copy semantics. | Mostly behavioral and nonduplicated. Missing provider-boundary warning/path/locality and serialization/load cases are attached to AUD-029 through AUD-033. |
Commands And Evidence
- Used graph architecture, symbol and augmented-code search, snippets,
inbound/outbound traces, data-flow traces, and complexity queries for
Build,Validate,MarshalYAML,LoadYAML, stanza grouping/normalization, prompt asset lookup, execution request/result construction, and application preparation callers. Bounded text inspection covered embedded Markdown, YAML, and JSON assets and their focused assertions. - Compared implementation and tests with
docs/internal/prompt-input.md,docs/internal/generatedtext.md,docs/internal/module.md,docs/integrations/promptkit.md, and the architecture, documentation, and testing policies. Every embedded prompt/profile/schema reference was inventoried and cross-checked against the report registry. - Ran a temporary focused malformed-package probe proving acceptance of missing metadata, orphan stanza values, unknown fields, and a trailing YAML document, then removed the probe without retaining test or product changes.
- Ran
go test -coverprofile=/tmp/weatherreporter-stage10-cover.out ./internal/promptinput ./internal/promptassets ./internal/promptexec; all focused packages passed. Coverage guided branch inspection but was not itself treated as a finding. Rango test ./...,go vet ./..., andgit diff --check; all passed. - Findings:
AUD-029,AUD-030,AUD-031,AUD-032, andAUD-033. - Retained decisions:
RET-021andRET-022. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 11: Audit Promptkit Adaptation And Secure Prompt Debugging
- Status: Complete.
- Scope reviewed: all production and focused-test code in
internal/adapters/promptkitandinternal/promptdebug; Promptkit integration and adapter internals; relevant operations, architecture, documentation, and testing rules; Promptkit v0.5.0's local public concurrency/prepared-execution contract; and immediate app/CLI consumers needed to trace credential checks, debug authorization, callback failure, and result projection. - Exclusions: Generated-text schema semantics remain Stage 12; application prompt inspection/preparation order remains Stage 14; comparison execution scheduling remains Stage 18; suite-wide test hygiene and cross-cutting cleanup remain Stages 19-23; documentation-wide coherence remains Stage 24; and broader adversarial diagnostics remain Stage 25.
Adapter And Debug-Security Accounting
| Contract or risk | Adapter, filesystem, caller, and test evidence | Disposition |
|---|---|---|
| Dependency containment and configuration | Promptkit production imports occur only in its adapter. CLI-owned settings map to profile directory/file, local endpoint/capacity, and timeout; mutually exclusive sources and inactive local capacity fail construction. | Matches the architecture boundary. Profile resolution remains dependency-owned under RET-023. |
| Profile source precedence | Explicit in-memory definitions, configured file/directory, embedded fallback, and built-ins provide complete definitions in the documented order. Absence falls through; a malformed matching configured definition fails. | Coherent with offline focused tests and retained as part of RET-023. |
| Exact inspection and credential preflight | Prompt inspection returns exact ID/version/hash, one input, and output contract. Profile inspection returns only logical ID, backend/model, direct-key requirement, and environment-variable name; app checks direct keys and nonblank environment values before collection. | Safe values and missing-credential categories match. Environment names are identifiers, not secret values. |
| Prepared inline execution | The adapter copies the YAML bytes into one inline artifact, prepares once, invokes the callback before generation, and runs the same opaque one-use handle. Callback failure prevents the provider call and deferred discard clears unused private state. | Matches the integration contract with exact-provenance and callback tests. |
| Cancellation and concurrency | Preparation and execution receive caller context independently; canceled/deadline causes map before generic generation errors. Promptkit documents Engine as concurrency-safe with capacity owned per engine, and comparison uses one adapter concurrently without wrapper state. |
Coherent; race-enabled adapter/app tests pass. No redundant application-wide limit or adapter mutex is warranted. |
| Error classification and normal redaction | Promptkit configuration, prompt/profile, credential, artifact/render, capacity, generation, validation, request, cancellation, and deadline errors map to bounded repository categories. Underlying causes remain discoverable but their content does not enter Error(), summaries, or logs. |
Sufficient at this boundary; focused marker and category tests protect normal output. |
| Debug authorization and isolation | Empty root disables all filesystem access; an enabled root must be absolute and not /. Report/date/run segments reject separators and traversal, comparison derives distinct deterministic run segments, and content-rich values appear only when capture is requested. |
Matches explicit opt-in and per-run isolation. Separate ownership retained as RET-024. |
| Debug credential projection | User information, fragments, known secret query keys, and known nested parameter keys are removed or redacted before preparation JSON is written. | The denylist misses common aliases, signed/path credentials, and arbitrary provider vocabulary; AUD-034. |
| Directory and symlink containment | Static symlinks in the root or any visible descendant component are rejected; directory modes are tightened to 0700, and unsafe references fail before a file write. |
Path checks are separated from later name-based creation/replacement and can be raced; AUD-035. |
| File collision, modes, and partial writes | JSON is marshaled before creation; same-name artifacts use a mode-0600 temporary in the final directory and atomic rename; non-regular existing targets are rejected; distinct concurrent run directories and shared missing ancestors are tested. |
Coherent for non-adversarial paths. A failed requested callback stops generation, and a failed execution write stops publication. |
| Complexity and test ownership | Adapter mapping/classification is straight-line or small linear copying. Debug recursion follows bounded JSON-compatible parameter trees; directory traversal is linear in path components; realistic artifact sizes are dominated by intentionally captured prompt/output content. Adapter tests own dependency translation; debug tests own filesystem/security behavior; app tests own callback consequences. | No Stage 11 efficiency or duplication finding. Redaction and race gaps are attached to their narrow owners rather than inferred from coverage. |
Commands And Evidence
- Used graph architecture, symbol and augmented-code search, snippets, inbound/data-flow traces, and hotspot inspection for adapter construction, exact inspection, preparation/execution, classification, debug writes, path validation, redaction, application callbacks, and CLI debug-root flow. Text inspection was limited to scoped documents, tests, string-based security policy, and the locally installed Promptkit v0.5.0 contract.
- Confirmed Promptkit's public engine concurrency guarantee, one-use prepared handle state, independent preparation/execution contexts, credential-redacted details, and upstream concurrent engine contract test without contacting a provider.
- Ran a temporary deterministic redaction probe using only synthetic markers; it proved that common endpoint-query and nested-parameter aliases survive serialization, then was removed without retaining source/test changes.
- Ran
go test -race -coverprofile=/tmp/weatherreporter-stage11-cover.out ./internal/adapters/promptkit ./internal/promptdebug ./internal/app; all focused packages passed with 90.0%, 73.6%, and 76.0% statement coverage respectively. Coverage guided branch inspection but was not itself a finding. Rango test ./...,go vet ./..., andgit diff --check; all passed. - Findings:
AUD-034andAUD-035. - Retained decisions:
RET-023andRET-024. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 12: Audit Generated-Text Validation And Catalog Contracts
- Status: Complete.
- Scope reviewed: all production and focused validation/catalog tests in
internal/generatedtextexceptrender_context.goand its focused tests; all four embedded generated-text schemas and theirpromptassetsowner; schema assertions currently placed ininternal/reporttemplate; the generated-text and Promptkit integration contracts; and the immediate Promptkit/application path needed to establish schema validation, raw-output dispatch, and error propagation. - Exclusions: Render-context assembly, template/partial semantics, and Markdown rendering remain Stage 13; application preparation and preflight ordering remain Stage 14; publication consequences remain Stages 15-18; suite-wide durability and refactoring remain Stages 19-23 except for duplication explicitly assigned to this stage; documentation-wide coherence remains Stage 24; and repository-wide adversarial checks remain Stage 25.
Generated-Text Contract Accounting
| Contract or risk | Schema, decoder, catalog, caller, and test evidence | Disposition |
|---|---|---|
| Raw output to typed value | Promptkit validates raw provider JSON against the report definition's embedded schema and reports passed/failed status. After a pass, app dispatches the same raw bytes through the prepared handler; the report validator strictly decodes shape, trims prose, removes blank day-style paragraphs, applies semantic required-field rules, and returns a typed value plus canonical JSON before context construction. | The path and ownership are explicit. Retain both validation layers under RET-026; their exact-name disagreement is AUD-037. |
| Required and empty fields | Every schema requires summary, forecast discussion, and precipitation timing and rejects additional properties. Go requires nonblank trimmed summary/discussion; day-style discussion retains one or more nonblank paragraphs; precipitation timing must be present and string-valued but may normalize to empty. Null and wrong structural types fail. | Coherent aside from case-insensitive aliases. The schema intentionally permits lexical blanks that repository semantic validation rejects. |
| JSON document strictness | Typed decoding rejects malformed JSON, unknown canonical additions, wrong field types, and multiple top-level values. It accepts surrounding whitespace. Duplicate names follow Go/parsed-JSON last-value semantics because no stronger canonical rule exists. | Sufficient for documented document shape, with exact-key enforcement missing in AUD-037. Duplicate policy is routed to Stage 25 remediation validation rather than declared retroactively. |
| Array and size behavior | Day-style arrays require one schema item and one nonblank normalized paragraph but have no item-count or aggregate bound. No schema or Go string/total-output bound exists, and decoder unknown-field errors quote arbitrary names. | Unbounded accepted prose and diagnostics are AUD-038. |
| Report-specific types and shared mechanics | Daily, Today, and Tomorrow are distinct types and validators over one private field projection and validation helper; Hourly deliberately uses a single discussion string. Typed context builders can therefore distinguish reports even when day-style fields match. | Appropriate separation retained as RET-025; tests duplicate rather than merely protect that choice in AUD-039. |
| Catalog compatibility | Four entries bind schema/template pairs to validators and builders; current registry iteration resolves all entries, schema/template lookup returns nonempty assets, and unknown IDs or mismatched known pairs fail. | Entries omit report identity, so a complete known pair can be assigned to the wrong report; AUD-036. |
| Asset ownership and immutability | promptassets embeds and returns independent copies of all four schema byte slices. generatedtext.Handler.Schema adds report context; reporttemplate production code owns templates and does not own schemas. |
Production ownership and copy isolation are coherent. Schema tests placed in reporttemplate duplicate the asset owner's assertions; AUD-039. |
| Error safety | Semantic required-field errors and catalog errors contain stable report/schema/template identifiers. JSON library errors are wrapped with report kind and preserve causes. | Arbitrary unknown-field names are emitted without a diagnostic bound and can be wrapped into normal application errors; part of AUD-038. No raw value is otherwise deliberately included. |
| Complexity and realistic cost | Scoped validators are straight-line except for one pass over discussion paragraphs; catalog lookup scans four fixed entries. Normal work is linear in provider output and paragraph count. | Algorithms are proportionate, but absent input/array limits make their allocation and normalization cost attacker/provider-controlled; AUD-038. |
| Test ownership and durability | Focused tests cover all four dispatches, canonical normalization, malformed/type/multiple-value cases, required semantics, retired fields, catalog lookup, schema inventory/identity, and independent schema bytes. | Exact report pairing, schema/Go agreement, and bounds are unprotected. Day-style semantics and schema shapes are redundantly asserted across packages; AUD-036 through AUD-039. |
Commands And Evidence
- Used graph architecture, symbol and augmented-code search, inbound/data-flow tracing, snippets, and complexity queries for catalog lookup, schema access, typed decoding/normalization, report validators, application execution, and their production/test callers. Bounded text inspection covered the four JSON schemas, scoped tests, generated-text/Promptkit documentation, and policy.
- Compared each schema's exact required fields and property shapes with Daily, Today, Tomorrow, and Hourly structs and semantic validators. Traced schema validation status through Promptkit adaptation into app's handler dispatch and verified schema byte-copy isolation and fixed catalog scan cost.
- Ran temporary offline probes showing that a Daily definition accepts the known Hourly pair, Go accepts case-varied summary/discussion keys while real embedded-schema validation rejects them, both layers accept a two-MiB valid summary, and a 64-KiB synthetic unknown key appears in full in the decoder error. The probes were removed without retaining source or test changes.
- Ran
go test -coverprofile=/tmp/weatherreporter-stage12-cover.out ./internal/generatedtext ./internal/promptassets ./internal/reporttemplate; all packages passed with 81.3%, 73.7%, and 86.2% statement coverage respectively. Coverage guided branch inspection but was not itself treated as a finding. Rango test ./...,go vet ./..., andgit diff --check; all passed. - Findings:
AUD-036,AUD-037,AUD-038, andAUD-039. - Retained decisions:
RET-025andRET-026. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 13: Audit Render Contexts, Templates, And Markdown Rendering
- Status: Complete.
- Scope reviewed:
internal/generatedtext/render_context.goand all focused context tests; all production code, tests, four top-level templates, and four partials ininternal/reporttemplate; the generated-text and report-template internal documents; the complete maintainer template guide; and the narrow catalog/application caller path needed to establish report identity, template pairing, and in-memory render behavior. - Exclusions: Generated-text JSON decoding and schema semantics remain Stage 12 except where their validated strings enter Markdown; application preparation, immutable copies, and execution ordering remain Stage 14; publication and notification consequences remain Stages 15-18; portfolio- wide test and complexity work remains Stages 19-23; documentation-wide coherence remains Stage 24; and broader adversarial rendering remains Stage 25.
Context And Markdown Accounting
| Contract or risk | Context, template, partial, caller, and test evidence | Disposition |
|---|---|---|
| Report metadata and identity | Hourly builds location and rolling-period labels; day-style reports derive civil-date/day-name labels and preserve distinct titles. Timezones, generated times, and periods are validated before construction. | Builders do not validate the report ID or reconcile the separately decoded metadata stanza; AUD-040. Error branches for invalid time metadata also lack focused tests, to be covered with the identity remediation rather than as a second finding. |
| Module-to-context mapping | Snapshot stanzas are selected by canonical stanza name, JSON-normalized through module.StanzaValue, and projected into typed optional pointers. Common day-style extraction is followed by report-specific Daily, Today, or Tomorrow planning fields; Hourly has its own surface. Extraction errors name the failing module. |
Coherent under valid snapshots and retained as RET-027. Snapshot schema-version permissiveness remains the upstream AUD-022; duplicate stanzas are rejected by module.NewSnapshot. |
| Deterministic daypart ordering | Typed row slices follow DerivedFacts.DaypartSummaries, try canonical and date-qualified keys, suppress already selected keys, then append remaining module keys lexically. Templates range only the ordered slices, not the source map. |
Byte-stable and semantically protected for configured and remaining rows. Upstream key collisions remain AUD-024; no new ordering defect. |
| Generated prose slots | Summary and forecast discussion occupy explicit top-level prose positions. Precipitation prose renders only when deterministic precipitation windows exist, preventing that slot from creating a dry-period timing section by itself. | Slot selection is coherent, but raw Markdown lets any prose slot create unauthorized report structure; AUD-041. Unbounded prose remains AUD-038. |
| Deterministic sections | Alert/SPC, current conditions, hourly/daypart forecast, precipitation windows, and their numeric values come from typed module fields. Alert instructions/descriptions and unused planning/narrative modules do not silently enter current output. Enhanced-or-higher SPC policy is centralized in helpers. | Current field ownership is explicit. Dynamic module strings share the Markdown-escaping defect in AUD-041; Today's all-filtered daypart case is AUD-042. |
| Missing values and report variation | Optional module pointers are guarded. Hourly provides explicit current/hourly fallbacks; day-style reports provide daypart fallbacks; Today alone includes current conditions and deliberately suppresses conditionless rows. Daily/Tomorrow share one richer daypart partial. | Intentional variation is preserved, except Today's row filter can leave a bare heading; AUD-042. |
| Template and partial lookup | Four fixed IDs resolve independent embedded source strings. Render applies missingkey=error, parses the chosen top-level asset and every shared partial, and attributes lookup, top-level parse, partial read/parse, and execution failures. All partials being parsed for every report makes a broken shared asset fail closed. |
Coherent and retained as RET-028. Schema ownership remains in promptassets; catalog report pairing remains AUD-036 and misplaced schema tests remain AUD-039. |
| Escaping and formatting | Repository literals define headings, bullets, ordering, and spacing. Templates use text/template; dynamic strings are not recursively evaluated as template actions. |
There is no Markdown/HTML structural escaping for provider or source-derived strings; AUD-041. |
| Output determinism | Embedded assets, typed slices, fixed partial order, and absence of current map iteration make repeated rendering of an unchanged context byte-identical. A temporary repeat-render probe confirmed identical output. | Sufficient. Context aliasing/immutability before rendering belongs to Stage 14. |
| Test review value | Tests cover real context construction for all reports, rich/omitted modules, module decoding errors, ordering, partial thresholds, conditional sections, fallback rows, open-ended precipitation, missing context, actionable unknown IDs, and semantically important output order. They use focused fragments rather than full-output goldens. | Good semantic review value under RET-028. Identity mismatch, Markdown structure, and all-filtered Today rows are missing and attach directly to AUD-040 through AUD-042; cross-suite fixture cost remains Stages 20-21. |
| Complexity and cost | Builders are straight-line projections plus linear stanza/daypart passes; remaining daypart keys sort deterministically. Template helpers scan small alert/SPC slices, and rendering parses eight embedded assets per call. | Proportionate at current service-sized inputs. Output-size amplification remains AUD-038; cross-cutting render performance remains Stage 22. |
Commands And Evidence
- Used graph architecture, symbol and augmented-code search, snippets, inbound traces, and complexity queries for all context builders, module extraction, daypart ordering, template lookup/rendering, partial helpers, and application callers. Text inspection covered every scoped Go file, template, partial, focused test, and canonical template/internal document.
- Cross-checked every current template field against its typed context producer, confirmed no template ranges over a map, inventoried optional guards and fallbacks, compared the Today/Daily/Tomorrow variations, and traced normalized snapshot values and validated generated prose into repository-owned Markdown.
- Ran temporary offline probes showing that a Daily builder accepts outer Hourly metadata alongside a Daily metadata stanza, a schema-valid generated summary can inject an Alert Digest heading and fabricated warning bullet, repeated rendering is byte-identical, and an all-filtered Today daypart slice produces a bare heading. The probes were removed without retaining source or test changes.
- Ran focused coverage for
./internal/generatedtextand./internal/reporttemplate; both passed with 81.3% and 86.2% statement coverage respectively. Coverage identified untested metadata failure paths but was not itself treated as a finding. Rango test ./...,go vet ./...,go run ./cmd/weatherreporter --help, andgit diff --check; all passed. - Findings:
AUD-040,AUD-041, andAUD-042. - Retained decisions:
RET-027andRET-028. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 14: Audit Application Preparation And Prompt Preflight
- Status: Complete.
- Scope reviewed: all production code and focused tests in
internal/app/prepared_report.go,prompt_inspection.go,prompt_generate.go, andprofile_execution.go; the narrow ordinary, batch, and comparison callers needed to establish collection order, preparation count, cancellation, and partial-result consequences; and the prepared-report and application-orchestration internal contracts. - Exclusions: Single-report destination resolution and atomic publication remain Stage 15; batch continuation and notification remain Stage 16; comparison identity/publication and scheduling remain Stages 17-18; portfolio-wide test consolidation and application refactoring remain Stages 20-23; documentation-wide reconciliation remains Stage 24; and broader adversarial execution remains Stage 25.
Preparation And Execution Accounting
| Contract or risk | Ordering, copy, execution, and test evidence | Disposition |
|---|---|---|
| Pre-collection validation | Ordinary and comparison flows resolve report and destination before optional debug initialization, then inspect prompt/profile and credentials before collectWeather; batch inspects all needed prompt/profile pairs before its one collection. Exact prompt ID/version, one required YAML input, JSON Schema output path, selected profile ID, and environment credential availability fail before collection. |
The generated-text template/catalog binding is absent from this preflight and is checked late; AUD-043. Output publication preflight remains with the assigned later stages. |
| Prompt/profile inspection | Inspection is side-effect-free, caches one effective batch profile, preserves explicit comparison order, and returns a successfully inspected comparison prefix on profile failure. Unclassified dependency failures become content-safe configuration errors. | Coherent and retained as RET-030, except that safe provenance can be incomplete and is not later reconciled; AUD-044. |
| Post-collection preparation | Preparation requires a bundle, builds facts once, builds one ordered module snapshot and metadata value, constructs and deterministically serializes the curated package, resolves a generated-text handler, and wraps every failure with its operation. No model or normal publication occurs on a preparation error. | The construction boundary is explicit. Catalog lookup order is AUD-043; missing serialization/no-executor regression remains AUD-033. |
| Immutable prepared state | Facts, snapshots, metadata, source warnings, resolved slice-bearing fields, and YAML bytes are copied before retention. Executor requests receive byte copies and rendering receives fresh typed clones. Focused tests mutate the source bundle and returned render values; concurrent comparison tests observe equal package bytes for every profile. | Demonstrably isolated for current types and retained as RET-029. The manual future option-shape risk is routed to Stage 23, not treated as a current alias defect. |
| Single-build reuse | Ordinary generation prepares once for its one selected profile. Comparison collects and prepares once, then passes the same immutable logical package through independent copies to every profile; it does not recompute facts or modules per provider. | Matches the prepared-report contract and avoids cross-profile derivation drift. |
| Executor request and cancellation | Execution receives the inspected prompt ID/version, selected profile ID, caller context, independent YAML bytes, and debug capture only when explicitly enabled. Classified provider errors retain their category; otherwise they become generation errors. The reusable executor/render core performs no durable writes. | Request shape and context propagation are coherent. Missing provenance checks are AUD-044; post-render publication cancellation and comparison aggregation remain Stages 15 and 18. |
| Debug callbacks | Preparation debug is written only through the explicit writer. A requested callback write failure is marked local and returned directly, preventing provider work under the executor contract; execution-debug failures stop before rendering/publication. Normal results retain only safe identities and an artifact path. | Matches Stage 11's secure-debug ownership. Callback invocation and identity are nevertheless trusted rather than verified; part of AUD-044. |
| Generated text and rendering | Only completed passed validation proceeds; failed becomes validation rejection and incomplete states become operational validation errors. The prepared handler independently validates raw JSON, clones render inputs, builds a typed context, and renders in memory. |
Correct layer order under coherent provenance. Schema/mode and execution identity are not tied to the inspected contract; AUD-044. Existing schema, size, context, and Markdown findings remain AUD-036 through AUD-042. |
| Errors and partial results | Preparation and execution wrappers preserve actionable operation names. Ordinary generation copies source warnings after preparation and records resolved profile identity, validation status, and debug path before returning execution errors. Comparison outcomes independently retain ordered safe execution status. | Coherent for reachable callers. Durable-path truthfulness and aggregation policy remain explicitly assigned to Stages 15-18. |
| Complexity and test ownership | The preparation path is a guarded straight-line pipeline over service-sized typed values; its dominant cost is deliberate deep copy and serialization. Execution is another guarded pipeline with no loop. Focused suites own inspection, mutation isolation, debug callbacks, validation states, rendering without publication, and concurrent shared-package reuse. | No Stage 14 performance finding. Missing invalid-catalog ordering, provenance mismatch, and serialization consequence tests attach directly to AUD-043, AUD-044, and AUD-033. |
Commands And Evidence
- Used graph symbol and augmented-code search, exact source snippets, inbound/data-flow traces, and scoped complexity metadata for preparation, inspection, ordinary generation, profile execution, collection, and comparison reuse. Bounded text inspection covered the four scoped production files, focused tests, task-specific internal documents, audit plan, and all repository policies.
- Enumerated every pre-collection failure (report/output/debug setup, executor, exact prompt shape, profile selection, and credentials), post-collection preparation failure (bundle, facts, modules, package construction, serialization, catalog, and immutable copies), and post-model boundary (completed schema status, typed validation, context construction, rendering, debug callback, cancellation handoff, and partial outcome).
- Ran temporary offline probes proving that prompt preflight accepts an unknown generated-text template plus blank safe provenance, and that mutually inconsistent callback/execution identities and schema-validation provenance still render valid Daily-shaped output. The probes were removed without retaining source or test changes and contacted no provider.
- Ran
go test -race -coverprofile=/tmp/weatherreporter-stage14-cover.out ./internal/app; the focused package passed with 76.0% statement coverage. Coverage guided missing-branch inspection but was not itself treated as a finding. Rango test ./...,go vet ./...,go run ./cmd/weatherreporter --help, andgit diff --check; all passed. - Findings:
AUD-043andAUD-044; application consequences also extend the existing test findingAUD-033. - Retained decisions:
RET-029andRET-030. - Open questions: the three leads recorded above are routed to their assigned later stages.
Stage 15: Audit Single-Report Generation And Atomic Output
- Status: Complete.
- Scope reviewed: the complete single-report path in
internal/app/app.go, single-report destination logic ininternal/app/output.go, publication inprompt_generate.go, all production code and tests ininternal/fileutil, generation/output tests, the application-orchestration and operations contracts, and the narrow CLI result projection needed to trace committed paths and errors without re-auditing parsing. - Exclusions: Batch output planning, continuation, and notification remain Stage 16; comparison destination identity and transactional bundle publication remain Stage 17; comparison cancellation remains Stage 18; suite-wide durability and refactoring remain Stages 19-23; documentation-wide coherence remains Stage 24; and adversarial pathname races remain Stage 25.
Single-Report Publication Accounting
| Contract or risk | Destination, filesystem, result, and test evidence | Disposition |
|---|---|---|
| Stage order and destination precedence | Report resolution precedes output resolution; an explicit file wins over the configured directory, which wins over the captured absolute working directory. Existing configured non-directories fail before prompt inspection or collection, while missing directories are deferred until publication. | Coherent for ordinary paths. Catalog preflight remains AUD-043; CLI executor-before-date ordering remains AUD-009. |
| Path normalization and target type | Relative paths are anchored to the captured working directory, absolute paths are cleaned, roots and directories are rejected, and result paths are absolute. Final validation otherwise accepts every non-directory object and follows symlinks. | Destructive special-file and ambiguous symlink behavior is AUD-045. |
| Atomic write and linearization | The writer creates a temporary file in the destination directory, writes and closes complete bytes, then uses same-directory os.Rename. Successful rename is the single visibility/commit point. Existing regular output is untouched on all earlier application failures and writer failures before rename. |
Mechanism retained as RET-031. Temporary naming can reject a valid final component in AUD-046; crash durability is not claimed. |
| File and directory modes | os.CreateTemp creates new report files as mode 0600; MkdirAll requests 0755 for missing operator-selected parents and leaves existing modes unchanged. Replacement takes the temporary file's restrictive mode. |
Safe for report content. Focused tests do not assert the final mode, but the standard-library primitive directly defines it and no conflicting user contract exists; no coverage-only finding. |
| Temporary and failure cleanup | A deferred remove covers write, close, and rename failures after temporary creation; the rename-error test proves no matching temp remains. Parent directories created before a later create/write failure are not rolled back. | Temp cleanup is coherent. The valid-long-name failure and directory residue are AUD-046; broad directory rollback would need concurrency-safe ownership. |
| Cancellation | Cancellation/deadline before publication entry returns the stable category, leaves OutputPath empty, preserves previous bytes, and skips notification; focused app tests cover both. |
The check precedes all temporary I/O rather than the rename boundary, so later-observable cancellation can still commit; AUD-047. |
| Pre-publication preservation | Prompt inspection, collection, generation, schema/typed validation, render, debug-write, destination, cancellation, and atomic-write failures occur before OutputPath is set. Tests preserve an existing file across generation, render, cancellation, deadline, and directory-collision failures; fileutil tests cover rename error and temp cleanup. |
Sufficient for regular files aside from the final cancellation gap. Stage 14 owns preparation/execution provenance and Stage 16 owns multi-output consequences. |
| Post-publication semantics | Immediately after rename, the result records the absolute path. Optional notification is then built from and sent with that exact file. Notification failure returns a failed action while retaining the committed path and Markdown; disabled notification returns success without a notification object. | Coherent and retained as RET-032. Single-report identity rendering gaps remain AUD-005; Distributor adapter/batch policy remain Stage 16. |
| Errors and CLI result truth | Cancellation is wrapped with report/run/operation context and preserves its category. Filesystem errors carry the exact affected directory, temp operation, or final path; notification errors carry report/run/output context. CLI projection copies active safe fields, committed path, notification, and returned error, marking any result-bearing error failed. | Actionable and truthful at the reviewed boundary. Assembled non-comparison CLI status/exit tests remain the existing AUD-010. |
| Complexity and test ownership | Resolution and publication are short guarded pipelines; file publication is constant-pass work over rendered bytes and has no service-sized loop beyond the write itself. App tests own workflow preservation/result ordering; fileutil tests own atomic replacement and temporary cleanup. | No Stage 15 complexity finding. Missing special-target, long-name, and final cancellation cases attach directly to AUD-045 through AUD-047. |
Commands And Evidence
- Used graph symbol and augmented-code search, exact snippets, inbound/outbound
traces, and scoped complexity/coverage data for
GenerateDetailed, output resolution and validation, publication/cancellation, atomic writing, notification, and CLI result projection. Bounded text inspection covered all scoped production/tests, task-specific documents, policies, and the exact Stage 15 boundary. - Traced every consequential result through the CLI projection: pre-result resolution failure, resolved but unpublished destination/preflight, collection/generation/render/cancellation/write failure, successful commit, and committed output followed by notification failure. CLI parsing and aggregate batch behavior were not re-audited.
- Ran temporary offline filesystem probes showing that complete generation replaces a FIFO with a regular Markdown file and that a filesystem-valid 248-byte basename fails after model work because the derived temp component is too long, leaves its new parent directory, and remains directly writable. The probes were removed without retaining source/test changes.
- Ran focused coverage for
./internal/appand./internal/fileutil; both passed with 76.0% and 68.4% statement coverage respectively. Coverage guided failure-branch inspection but was not itself treated as a finding. Rango test ./...,go vet ./...,go run ./cmd/weatherreporter --help, andgit diff --check; all passed. - Findings:
AUD-045,AUD-046, andAUD-047. - Retained decisions:
RET-031andRET-032. - Open questions: the three leads recorded above are routed to their assigned later stages.