1326 lines
92 KiB
Markdown
1326 lines
92 KiB
Markdown
# Repository Audit Ledger
|
|
|
|
Status: In progress; Stages 1-6 complete.
|
|
|
|
This temporary roadmap document is the evidence ledger for the staged audit
|
|
defined by [the audit plan](audit-plan.md). It records audit evidence and
|
|
status, not implemented product behavior. Current contracts remain with the
|
|
canonical owners identified by the
|
|
[documentation policy](../policy/documentation.md).
|
|
|
|
## 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. 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.json`
|
|
- `internal/adapters/weatherapi/testdata/convective_outlooks.json`
|
|
- `internal/adapters/weatherapi/testdata/current.json`
|
|
- `internal/adapters/weatherapi/testdata/discussion.json`
|
|
- `internal/adapters/weatherapi/testdata/hourly.json`
|
|
- `internal/adapters/weatherapi/testdata/narrative.json`
|
|
- `internal/adapters/weatherapi/testdata/observations.json`
|
|
- `internal/adapters/weatherapi/testdata/weather_story.json`
|
|
- `internal/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`, and `docs/templates.md`;
|
|
- policies: `docs/policy/architecture.md`,
|
|
`docs/policy/documentation.md`, and `docs/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 under `docs/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 | Pending |
|
|
| 8 | Audit module contracts, registry, and source-facing briefing modules | Pending |
|
|
| 9 | Audit derived, planning, formatting, and SPC briefing modules | Pending |
|
|
| 10 | Audit prompt inputs, assets, and neutral execution contracts | Pending |
|
|
| 11 | Audit Promptkit adaptation and secure prompt debugging | Pending |
|
|
| 12 | Audit generated-text validation and catalog contracts | Pending |
|
|
| 13 | Audit render contexts, templates, and Markdown rendering | Pending |
|
|
| 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 | Pending |
|
|
| Module and briefing contracts | `internal/module`, `internal/briefing` | 8-9 | Pending |
|
|
| Prompt inputs, embedded assets, and execution contracts | `internal/promptinput`, `internal/promptassets`, `internal/promptexec` | 10 | Pending |
|
|
| Promptkit boundary and sensitive debug output | `internal/adapters/promptkit`, `internal/promptdebug` | 11 | Pending |
|
|
| Generated-text validation | `internal/generatedtext` | 12 | Pending |
|
|
| Render contexts and templates | `internal/generatedtext`, `internal/reporttemplate` | 13 | Pending |
|
|
| Prompt preflight and prepared inputs | `internal/app` | 14 | Pending |
|
|
| Single-report publication and preservation | `internal/app`, `internal/fileutil` | 15 | Pending |
|
|
| 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`, and
|
|
`internal/fileutil.WriteJSONAtomic`
|
|
- Evidence: Graph inbound traces show `promptinput.Save` has no callers,
|
|
`weatherapi.SaveBundle` is called only by `TestSaveBundle`, and
|
|
`fileutil.WriteJSONAtomic` is called only by `SaveBundle` and 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.Save` and `SaveBundle` write
|
|
intermediate prompt input or normalized weather data to arbitrary paths,
|
|
while normal publication uses `fileutil.WriteFileAtomic` for selected
|
|
Markdown and `comparison.Publish` for 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
|
|
`WriteJSONAtomic` if it then has no production use, and delete or consolidate
|
|
tests that protect only those retired APIs.
|
|
- Test implications: `TestSaveBundle` and `TestWriteJSONAtomic` protect unused
|
|
persistence mechanisms; `promptinput.Save` has no focused test. Preserve
|
|
tests for `WriteFileAtomic`, 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.ClockWindow` and
|
|
`internal/forecast.ResolveDayparts`
|
|
- Evidence: `ClockWindow` obtains the local civil-day start and creates its
|
|
boundaries with elapsed-duration additions such as
|
|
`day.Start.Add(17 * time.Hour)`. In `America/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 is
|
|
`forecast.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:00` and overnight
|
|
windows, rather than adding elapsed hours to midnight.
|
|
- Test implications: Add table-driven `America/Chicago` coverage 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 `timeutil` and `forecast` suites 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 `GeneratedAt` is 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. Downstream
|
|
`plannedReportsByRunID` rejects 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
|
|
`plannedReportsByRunID` receives 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`, and
|
|
`internal/adapters/weatherapi.policyFor`
|
|
- Evidence: The configuration contract lists seven optional source keys and
|
|
states that hourly forecast data is always required. `Validate` checks only
|
|
that each map key is nonblank and its policy is one of `error`, `warn`, or
|
|
`none`; 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 as `alert: error`, an arbitrary key, or an
|
|
ineffective `hourly: none` loads successfully. Focused config tests cover an
|
|
invalid policy value but no unsupported key.
|
|
- Contract at risk: `missing_source.sources` accepts 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`, or `none`
|
|
rule 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/config` one authoritative supported-source
|
|
set and reject every other key, including `hourly`. 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 `Validate` or `LoadFile`, 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`, and
|
|
`RenderDistributorIdempotencyKey`
|
|
- Evidence: Enabled single-report notification validation rejects an empty
|
|
template and unknown variables, and `RenderDistributorPipelineID` rejects 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`, and
|
|
`Config.ReportDistributorPathOverrides`
|
|
- Evidence: YAML unmarshalling sets private `deterministicModulesSet` and
|
|
`pathTemplatesSet` flags to distinguish omission from an explicit list.
|
|
Both public override accessors skip exported `DeterministicModules` and
|
|
`PathTemplates` values unless the corresponding private flag is true. Code
|
|
outside package `config` therefore cannot construct an equivalent override:
|
|
setting a nonempty exported slice in `Config.Reports` is silently ignored,
|
|
and `Validate` skips its contents as well. Tests named for loaded/constructed
|
|
consistency reside inside package `config` and 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.Config` can
|
|
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.Load` and `loadSecrets`
|
|
- Evidence: `Load` calls `loadSecrets` before `Validate`. The secret loader
|
|
validates and applies directory entries one at a time with `os.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.main` and CLI cancellation propagation
|
|
- Evidence: `main` calls `cli.Run` with `context.Background()`. Repository code
|
|
search found no `signal.Notify`, `signal.NotifyContext`, `os.Interrupt`, or
|
|
termination-signal handling. `Runner.Run` correctly 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: `SIGINT` or `SIGTERM` terminates 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.NotifyContext` for
|
|
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 `--date` or 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 production
|
|
`generate daily` diagnostic 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-007` even 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 `timeutil` parser 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, and
|
|
`cmd/weatherreporter`
|
|
- Evidence: The CLI suite directly tests generate summary field copying and
|
|
shared output helpers, but has no assembled `Runner.Run` generate success,
|
|
result-bearing failure, quiet failure, or pre-result failure test. Generate
|
|
calls `app.GenerateDetailed` directly, 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.Validate` and
|
|
`internal/adapters/weatherapi.New`
|
|
- Evidence: Both boundaries accept any parsed absolute URL with a scheme and
|
|
host. The adapter always constructs an `http.Request` and executes it with
|
|
`http.Client`, so a configured URL such as `ftp://weather.example.test`
|
|
passes 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 `http` and `https` as 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.warmupOnce` and
|
|
`fetchHTTPOnce`
|
|
- 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`, and `collect.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.warmupOnce` and
|
|
`fetchHTTPOnce`
|
|
- 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.fetchHourly` and
|
|
`internal/weatherdata.ForecastRun`
|
|
- Evidence: Required hourly validation rejects a missing/null payload, a JSON
|
|
type error, and a zero-length `periods` slice. Go decoding leaves absent
|
|
`issuedAt`, `startTime`, and `endTime` fields as zero `time.Time` values, 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 explicit `null` rather 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 `endTime` after `startTime`. 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.warmup` and `warmupOnce`
|
|
- Evidence: Source fetching retries only transport/read failures and the
|
|
documented `408`, `429`, `500`, `502`, `503`, and `504` statuses. Warmup uses
|
|
a separate attempt implementation and retries every error until its budget
|
|
is exhausted, including permanent `400`, `401`, `403`, and `404` responses.
|
|
Focused warmup tests cover successful retry of `502` and exhausted `502`,
|
|
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 `404` case that makes one request, retain the
|
|
`502` retry 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
|
|
|
|
## 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.
|
|
|
|
## 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/config` imports `internal/briefing` to 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`, and
|
|
`internal/app.compareDetailed` as 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:
|
|
|
|
- `LoadLocation` accepts numeric offsets through `23: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-003` captures 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_url` may 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 `--date` in usage but omits it from the Options
|
|
list and describes `--quiet` as suppressing successful output even though it
|
|
suppresses failed action summaries too. `docs/internal/cli.md` also 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.BuildCollected` and `facts.Bundle` copy 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.Raw` preserves 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 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-weatherreporter` in moderate
|
|
mode. The graph's branch identity remains production baseline `e7c7262`;
|
|
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 list` direct 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`, and `RET-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/report`
|
|
and `internal/timeutil`; report and timezone contracts in `docs/cli.md`,
|
|
`docs/config.md`, and `docs/internal/report-registry.md`; and the focused
|
|
`internal/app` and `internal/forecast` callers 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, and `plannedReportsByRunID`.
|
|
- Compared the implementation with `docs/cli.md`, `docs/config.md`, and
|
|
`docs/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/Chicago` transition 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-002` and `AUD-003`.
|
|
- Retained decisions: `RET-005`, `RET-006`, and `RET-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 in `internal/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 returned `missing_credential` before 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`, and `AUD-007`.
|
|
- Retained decisions: `RET-008`, `RET-009`, and `RET-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 in `internal/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`, and
|
|
`go 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`, and `AUD-010`.
|
|
- Retained decisions: `RET-011` and `RET-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`, and
|
|
`internal/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`, and `AUD-015`.
|
|
- Retained decisions: existing `RET-004` was revalidated and `RET-013` records
|
|
explicit source normalization.
|
|
- Open questions: the four leads recorded above are routed to their assigned
|
|
later stages.
|