diff --git a/docs/roadmap/audit.md b/docs/roadmap/audit.md index 5705d99..4c65ef5 100644 --- a/docs/roadmap/audit.md +++ b/docs/roadmap/audit.md @@ -1,6 +1,6 @@ # Repository Audit Ledger -Status: In progress; Stages 1-21 complete. +Status: In progress; Stages 1-23 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 @@ -177,6 +177,25 @@ semantics; dense configuration/domain tables, realistic filesystem tests, small external-boundary fakes, and representative app/CLI workflows earn their cost. No complete-output golden snapshots or oversized fixture framework was found. +Stage 22 found one medium-severity efficiency defect because every Weather API +collection discards a current-conditions warmup response, fetches that source +again, and serializes all eight otherwise independent source requests. It also +found one low-severity replacement cost because an existing comparison bundle +is fully recognized four times, causing `4N+16` complete reads for `N` +successful reports. Service-sized derivation, fixed registries, template +parsing, fixed batch sequencing, and ordinary profile fan-out are otherwise +proportionate to their current workloads. +Stage 23 found seven low-severity structural issues: daypart-key normalization and +Distributor template parsing each have two policy implementations; report +metadata and render inputs retain parallel authorities and unused template +surfaces; fact requirements span string constants, registry declarations, and +availability switches; generate and comparison repeat report-date policy at +four sites; several internal compatibility surfaces have no production +consumer; and one duplicate capitalization path corrupts non-ASCII daypart +labels. Report-specific generated types/builders, explicit prompt allowlist +projections, package-local safety copies and path diagnostics, config-to- +briefing validation, top-level workflow orchestration, and the current narrow +resolved-value clone remain intentionally explicit. Subsystem conclusions and final disposition remain pending the later stages. ## Baseline Metadata @@ -326,7 +345,7 @@ inventory commands, graph index refresh, and graph architecture inspection. | 20 | Audit test risk coverage and ownership | Complete | | 21 | Audit test durability, duplication, and maintenance cost | Complete | | 22 | Audit cross-cutting efficiency and complexity | Complete | -| 23 | Audit cross-cutting refactoring and deduplication opportunities | Pending | +| 23 | Audit cross-cutting refactoring and deduplication opportunities | Complete | | 24 | Audit documentation coherence and executable contracts | Pending | | 25 | Run dynamic robustness and final diagnostic validation | Pending | | 26 | Verify, consolidate, and triage findings | Pending | @@ -360,7 +379,7 @@ with evidence about meaningful risks, test ownership, gaps, and duplication. | Test risk coverage and ownership | Repository-wide suite | 20 | Partly insufficient. Consequential contracts have narrow owners and representative cross-boundary workflows; every identified missing regression is already attached to the production or test-gap finding for the defect it would catch. Coverage diagnostics found no additional unowned critical branch. Accepted omissions are thin entry-point/delegation wrappers, defensive accessors, and dormant helpers already routed under `AUD-001`; broad duplication and durability remain Stage 21. | | Test durability, duplication, and maintenance cost | Repository-wide suite | 21 | Partly insufficient. Most tests assert stable behavior with direct fixtures and appropriately narrow fakes. `AUD-039`, `AUD-061`, and `AUD-063` identify duplicated or implementation-coupled assertions; `AUD-062` identifies avoidable production waits; `AUD-064` identifies incomplete failure cleanup. Each consolidation retains a named behavioral owner. | | Cross-cutting efficiency | Graph metrics, workflow traces, and focused latency/allocation diagnostics | 22 | Partly insufficient. Fixed-size registries, service-sized derivation, template parsing, batch composition, and ordinary profile fan-out are proportionate. `AUD-065` records one redundant Weather API request plus serial independent source fetches; `AUD-066` records repeated full comparison-bundle reads during replacement. | -| Cross-cutting maintainability | Graph metrics plus focused structural review | 23 | Pending | +| Cross-cutting maintainability | Graph similarity, change history, caller traces, and focused structural review | 23 | Partly insufficient. `AUD-067` through `AUD-073` identify duplicated canonicalization/template/date/requirement policy, parallel metadata authority, dormant internal surfaces, and unsafe duplicate capitalization. Important report, prompt-curation, safety-copy, validation, and orchestration similarities remain explicit under `RET-053` through `RET-058`. | | Documentation and executable-contract coherence | Canonical documents, code, schemas, templates, examples | 24 | Pending | | Dynamic robustness and diagnostic checks | Repository-wide deterministic checks | 25 | Pending | @@ -2897,6 +2916,298 @@ with evidence about meaningful risks, test ownership, gaps, and duplication. - Related findings: `AUD-038`, `AUD-053`, `AUD-055` - Remediation reference: pending +### AUD-067: Daypart key canonicalization has two independent owners + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: duplication +- Area: `internal/briefing.normalizedKey` and + `internal/generatedtext.normalizedDaypartKey` +- Evidence: Graph fingerprint comparison found two exact 17-line production + implementations: trim and lowercase the name, preserve Unicode letters and + digits, collapse every other run to one underscore, and trim edge + underscores. Briefing introduced its copy in `7b760a0`; generated-text added + a separately named copy later in `ddda424`. Briefing uses the key to store + daypart module values, while generated-text recomputes it from derived facts + to recover configured order. The same identity is also the collision domain + whose missing validation causes `AUD-024`. +- Contract at risk: One accepted daypart name must have one canonical identity + from configuration through module storage and render ordering. +- Impact: A future Unicode, punctuation, casing, or collision-policy change + must update two packages identically. Drift can leave a module entry present + under one key but unrecognized by ordered rendering under the other, causing + misplaced or omitted daypart output even after collision validation is + added. +- Recommendation: Give canonical daypart identity to the existing daypart + domain owner, preferably beside `forecast.DaypartDefinition`, and make + configuration collision checks, briefing keys, and generated-text ordering + call that one helper. Keep display labels separate from canonical roles as + required by `AUD-025`; do not introduce a generic string-normalization + package. +- Test implications: One focused domain table should own blank edges, + punctuation runs, casing, Unicode, and collision equivalence. Config should + test rejection consequences, while briefing/generated-text retain one + composition case each rather than repeating the normalization table. +- Validation: Every accepted name produces the same key at validation, + snapshot, prompt, and render boundaries; current default keys and rendered + ordering remain unchanged. +- Related findings: `AUD-024`, `AUD-025` +- Remediation reference: pending + +### AUD-068: Distributor single and batch templates duplicate one parser + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: duplication +- Area: `internal/config.renderDistributorTemplate` and + `renderDistributorBatchTemplate` +- Evidence: The two 27-line functions are fingerprint-identical except for + their value type and variable-to-value callback. Both independently own the + brace scanner and the exact unclosed, empty, unknown, and unopened-variable + diagnostics. Batch support copied the second grammar in `32060bd`; the file + has seven historical changes, and config tests repeat malformed/unknown + template cases for the two families. Typed public wrappers and allowed + variable sets are legitimately distinct, but syntax is not. +- Contract at risk: Single-report and batch notification templates use one + interpolation grammar and must reject malformed syntax consistently. +- Impact: Any escaping, validation, diagnostic, or parser correction requires + synchronized edits and duplicated tests. Drift would make configuration + accept a template for one notification mode and reject the same syntax for + the other without a domain reason. +- Recommendation: Extract one private parser that accepts the diagnostic name + and a typed-family resolver callback returning a value and whether the + variable is allowed. Retain distinct value structs, allowed-variable sets, + nonempty-result rules, and public rendering functions; no general template + framework or new package is warranted. +- Test implications: Give the shared parser one malformed-syntax table. Keep + small single/batch tests for their variable vocabularies, value mappings, + nonempty requirements, report-path validation, and public error context. +- Validation: Every current valid template renders identically, current + malformed inputs retain their actionable field names, and a parser mutation + is detected once rather than by copied syntax suites. +- Related findings: none +- Remediation reference: pending + +### AUD-069: Preparation retains parallel metadata authorities and unused render surfaces + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: architecture +- Area: `internal/briefing.BuildMetadata`, `buildMetadataModule`, + `internal/app.preparedReport`, and + `internal/generatedtext/*RenderContext`/`*TemplateModules` +- Evidence: `BuildMetadata` and `buildMetadataModule` independently project the + same run ID, report ID, variant, prompt ID, generation time, units, timezone, + valid period, location, and source-warning facts. Preparation retains the + broader `briefing.Metadata` beside a snapshot containing `MetadataModule`, + then projects the former again into `promptinput.Metadata`. Render contexts + receive the outer metadata for display identity while also exposing the + decoded module metadata. All four contexts additionally expose complete + `Collected` and `Derived` values. Repository template search found no use of + `.Collected`, `.Derived`, or `.Modules.Metadata`; the duplicated identity + paths already permit the conflicts in `AUD-040`. The broad metadata path + predates the later module and render-context paths, rather than resulting + from one shared projection. +- Contract at risk: Prompt metadata, metadata stanza content, and displayed + report timing must derive from one prepared identity, while templates should + receive only fields they are authorized to render. +- Impact: A metadata addition or correction can require parallel builders, + application projection, snapshot fixtures, and context fixtures. More + importantly, callers can supply conflicting outer and stanza identities and + templates gain accidental access to raw deterministic facts that current + assets deliberately do not use. +- Recommendation: After resolving `AUD-040`, establish one smallest prepared + metadata value and derive prompt-package metadata, the metadata stanza, and + report display context from it through named projections. Remove unused + `Collected`/`Derived` fields and the unused template-module metadata field; + pass derived facts privately only while constructing ordered daypart views. + Keep prompt-package ownership in `promptinput` and module-value ownership in + `briefing` rather than replacing them with one cross-layer mega-struct. +- Test implications: Preserve one preparation test proving all three outputs + share identity and source-warning values, module tests for prompt-safe stanza + content, generated-text tests for report display fields and daypart order, + and template tests for unchanged Markdown. Delete fixture data that protects + only unreachable template fields. +- Validation: Current prompt YAML and rendered Markdown remain semantically or + byte equivalent as contractually required, conflicting identity inputs are + impossible or rejected, and template contexts contain no unused complete + fact bundle. +- Related findings: `AUD-029`, `AUD-040`, `AUD-063` +- Remediation reference: pending + +### AUD-070: Fact requirement policy spans constants, declarations, and switches + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: architecture +- Area: `internal/module.FactRequirement`, + `internal/briefing.defaultModuleDefinitions`, `collectedFactAvailable`, and + `derivedFactAvailable` +- Evidence: Adding a module fact prerequisite requires a string constant in + `module`, use in one or more registry definitions, and a matching branch in + one of two availability switches in `briefing`. `FactRequirement` is a + string, and `NewModuleRegistry` validates neither known requirement names nor + collected-versus-derived placement; an unknown value is accepted and later + silently treated as unavailable. `modules.go` has changed 21 times as source, + derived, planning, SPC, and prompt-export modules were added, demonstrating + continuing evolution of this three-part policy. Focused default-composition + tests currently keep built-ins aligned but do not make one representation + authoritative. +- Contract at risk: Every declared prerequisite must have one known + availability predicate, and registry construction should reject rather than + defer unknown policy vocabulary. +- Impact: A new or renamed requirement can compile and validate into a registry + yet make its module always missing at runtime. The separate collected and + derived slices also allow a valid constant in the wrong category, producing + the same misleading absence without identifying the declaration error. +- Recommendation: Move requirement descriptors to `briefing`, where facts are + visible, and define each name, fact category, and availability predicate + once. Let definitions refer to those descriptors or validate identifiers + through one registry-owned map during `NewModuleRegistry`. Keep each + module's actual requirement list and missing-data behavior explicit in the + definition table. +- Test implications: One descriptor table should prove every identifier has a + predicate and correct category; constructor tests should reject unknown or + misplaced requirements. Retain behavior tests for materially distinct + missing-source policies, not one availability test per constant. +- Validation: Every default definition constructs, unknown/miscategorized + requirements fail at registry creation, and existing omit/empty/error results + remain unchanged for all reports. +- Related findings: none +- Remediation reference: pending + +### AUD-071: Generate and comparison repeat report-date policy at four sites + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: duplication +- Area: `internal/cli.parseGenerateFlags`, `parseComparisonFlags`, + `Runner.resolveGenerateAction`, and `Runner.resolveComparisonAction` +- Evidence: Both flag parsers independently decide that Daily and Today accept + `--date`. Both action resolvers then independently require it for Daily, + default it to the local current date for Today, parse it in the configured + timezone, and leave Tomorrow/Hourly without it. Generate acquired Today + policy in `4a02389`; comparison copied the action policy later in `b489c56`. + `root.go` has 36 historical changes, and + `TestResolveComparisonActionMatchesReportDatePolicies` exists specifically to + detect disagreement after the fact. Action-specific request construction and + diagnostics differ, but report/date capability does not. +- Contract at risk: The same report accepts, requires, defaults, and parses the + same date semantics regardless of whether one profile or several profiles + execute it. +- Impact: Adding a report variant or changing date semantics requires four + coordinated branches plus agreement tests. A missed site can expose a flag + that later fails, reject a valid comparison only, or choose different civil + dates for otherwise equivalent actions. +- Recommendation: Add one private CLI report-date policy descriptor and one + resolver that returns the effective local date from report kind, supplied + text, timezone, and clock. Let each parser consult `AcceptsDate` and each + action wrap missing-date diagnostics with its command name. Keep flag parsing + and request construction in CLI rather than moving them into the report + registry. +- Test implications: Give the shared policy a four-report table for acceptance, + required/default/forbidden behavior, invalid dates, and timezone-relative + Today defaults. Retain one thin generate and comparison wiring case each. +- Validation: Generate and compare preserve current accepted flags, errors, and + resolved dates for all four reports, and no duplicated report-kind switch + remains in their date path. +- Related findings: `AUD-009` +- Remediation reference: pending + +### AUD-072: Dormant internal compatibility surfaces have no workflow owner + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: architecture +- Area: `internal/promptinput.LoadYAML`, + `internal/forecast.{DifferenceAtLeast,CrossesAtOrAbove,CrossesBelow}`, + `internal/weatherdata.AlertRun.Raw`, and the normalized-JSON return from + `internal/generatedtext` validators +- Evidence: Production caller traces find no caller for `LoadYAML`; its + round-trip tests are the only consumer, while normal execution builds a + package and marshals YAML directly. The three exported forecast threshold + helpers are called only by `TestThresholdHelpers`. The Weather API adapter + copies every full alerts response into `AlertRun.Raw`, but no production or + test code reads that field; individual raw alert items and source hashes own + the needed payload/provenance. All four generated-text validators marshal and + return canonical normalized JSON, but the sole production handler caller + assigns that result to `_`; normalization behavior is otherwise consumed + through the typed value used for rendering. The persistence half of the same + obsolete prompt-input surface is already `AUD-001`, and hardening the unused + loader alone is `AUD-032`. +- Contract at risk: Internal package APIs and stored values should describe the + active stateless pipeline, not imply compatibility or persistence contracts + with no workflow owner. +- Impact: These surfaces retain tests, error branches, allocations, JSON shape, + and future design constraints without protecting current output. They can + invite a new caller to rely on accidental semantics such as YAML round-trip + loading, threshold helpers with no domain owner, a duplicate whole alert + envelope, or canonical JSON that the renderer never observes. +- Recommendation: Retire `LoadYAML` with `promptinput.Save`; remove the unused + threshold helpers and their representation-only test; remove `AlertRun.Raw` + and adapter assignments; and simplify the internal validator/handler result + to the typed value plus error. If a concrete tooling consumer is identified + before remediation, document and test its narrow contract instead of + retaining all surfaces speculatively. +- Test implications: Preserve Build/Validate/MarshalYAML execution tests, + forecast behavior that uses real threshold decisions, alert item parsing and + provenance hashes, typed normalization semantics, schema agreement, and all + normal generate/batch/comparison workflows. Remove only tests whose sole + subject is the retired surface; `AUD-039` still owns duplicated validator + semantics. +- Validation: Graph and text searches show no remaining caller or field + reference, prompt YAML and rendered reports remain unchanged, alert source + hashes still cover accepted payloads, and the repository suite passes. +- Related findings: `AUD-001`, `AUD-032`, `AUD-038`, `AUD-039` +- Remediation reference: pending + +### AUD-073: Duplicate capitalization helpers disagree on Unicode safety + +- Stage: 23 +- Status: candidate +- Severity: low +- Confidence: high +- Category: duplication +- Area: `internal/briefing.titleWord` and `sentenceCase` +- Evidence: `sentenceCase` trims, converts the string to runes, and uppercases + the first rune. `titleWord` instead uppercases `value[:1]` and appends + `value[1:]`, splitting any multibyte first rune. Its four production callers + place configured daypart names into derived summaries, workday concerns, + outdoor planning notes, and late-day watch prose. Configuration accepts + Unicode nonblank daypart names, so a name beginning with `é`, `ø`, or another + multibyte letter reaches this path and produces replacement/invalid text. + Existing tests use ASCII default names only. +- Contract at risk: Accepted Unicode display labels must remain valid and + readable when interpolated into deterministic briefing prose. +- Impact: A valid localized or operator-chosen daypart name can be corrupted in + several prompt-facing planning fields. Two helpers that express the same + first-character presentation policy also invite future call sites to choose + the unsafe implementation accidentally. +- Recommendation: Replace both with one private rune-safe first-letter helper, + choosing trimming explicitly at each call boundary if their whitespace + contracts differ. Keep canonical identity normalization separate under + `AUD-067`; this helper is display-only and must not assign semantic roles. +- Test implications: Add one small table for empty, ASCII, multibyte lowercase, + already-uppercase, and leading-space behavior. Retain one assembled planning + assertion proving a Unicode daypart label survives in prompt-facing prose. +- Validation: All current ASCII output is unchanged, every accepted Unicode + label remains valid UTF-8 with its first rune capitalized, and only one + capitalization implementation remains. +- Related findings: `AUD-024`, `AUD-025`, `AUD-067` +- Remediation reference: pending + ## Retained Decisions ### RET-001: Keep the application package as the explicit composition owner @@ -3515,6 +3826,77 @@ observable clone contention, or a larger dynamic batch as triggers for a bounded worker design rather than inferring a defect from goroutine or graph counts alone. +### RET-053: Keep report-specific generated types and thin builders explicit + +Fingerprint analysis found exact or near-exact Daily/Tomorrow context builders, +four catalog type-dispatch adapters, three planning-module wrappers, and +parallel report definitions. Their similarity follows four intentionally +distinct report contracts: each public generated type, schema/template pair, +planning stanza, report ID, and typed context must remain independently +selectable and type-checked. Existing private day-style helpers already share +the mechanics that have one policy owner. Retain the thin variants rather than +introducing reflection, a generic report mega-type, or callbacks that obscure +which planning fields each report supports. `AUD-039` and `AUD-063` own test +duplication without erasing these production distinctions. + +### RET-054: Keep rich-to-prompt projections as explicit allowlists + +Current conditions, hourly periods, and derived dayparts copy many fields from +rich module values into narrower prompt structs. That field-by-field code is +deliberate curation: omission of template-only helpers, source detail, and +unneeded values is part of the prompt security and authority boundary under +`RET-017`. Code generation, reflection, embedding, or serialization through +the rich struct would make accidental prompt expansion harder to review. +Retain named projection functions and focused omission tests. Extract only +small semantic subprojections when multiple prompt types genuinely share the +same allowlist and change history. + +### RET-055: Keep tiny safety copies and path checks package-local + +Exact fingerprints found three ten-line string-map copy helpers in Promptkit, +prompt-debug, and neutral execution packages, plus similar absolute-path +checks in app and comparison. The copies sit at separate mutation/security +boundaries and their names state which owner is taking a copy. The path checks +attach different diagnostics and feed substantially different publication +policies. A generic utility package would save little code while coupling +unrelated contracts. Retain these local helpers; share only if a common type or +policy owner emerges, not because the mechanics happen to match. + +### RET-056: Keep configuration validation against the briefing registry + +`internal/config` imports `internal/briefing` to normalize typed module options +and validate report compositions against the same definitions used at runtime. +This edge prevents configuration from copying module IDs, option shapes, +supported-report rules, and stanza policy. Moving a second descriptor table +into config would create the duplication Stage 23 is meant to remove, while a +new abstraction package would split the current owner without another +consumer. Retain the dependency; narrow the requirement vocabulary within +briefing under `AUD-070` without moving runtime facts into config. + +### RET-057: Keep workflow orchestration visibly distinct + +Generate, fixed batch, and comparison share phases such as resolution, +inspection, collection, preparation, execution, and publication, but the +ordering and failure contracts differ materially. Batch validates all outputs, +continues independent report failures, publishes sequentially, and gates one +notification. Comparison validates explicit profiles, prepares once, executes +concurrently, and publishes one partial-capable transaction without +notification. Extracting a common workflow framework would hide those state +machines. Retain top-level guarded pipelines and consider only phase-local +helpers after `AUD-051`, `AUD-058`, `AUD-065`, and `AUD-066` settle their +behavior. + +### RET-058: Keep the resolved-value clone explicit for current option shapes + +`cloneResolved` copies report slices and the only reference-bearing module +option, AFD sections. Every other accepted option is presently value-only, and +mutation-isolation tests cover the complete prepared boundary. Reflection or +JSON cloning would add runtime failure modes and obscure ownership to solve a +hypothetical future shape. Retain the explicit switch now. If a second +reference-bearing option is introduced, give module options an owner-defined +clone contract and make the registry/construction test prove every registered +shape participates. + ## Open Questions No Stage 1 open questions or unexplained baseline failures remain. @@ -3909,6 +4291,25 @@ Stage 22 routed these investigation leads to their assigned later stages: Stage 26 may consider a limit or worker bound only if operational evidence establishes a larger supported workload. +Stage 23 routed these investigation leads to their assigned later stages: + +- Stage 24 should reconcile docs that present `promptinput.Save`/`LoadYAML`, + normalized generated JSON, complete render-context facts, or template module + metadata as maintained surfaces if Stage 26 selects the retirement/narrowing + direction in `AUD-069` and `AUD-072`. Current-state documentation should not + be changed before remediation. +- Stage 25 should include a Unicode-leading daypart label in its broader + parser/configuration corpus and preserve the existing destination-race cases; + it should validate the findings rather than implement shared helpers. +- Stage 26 should consolidate `AUD-001`, `AUD-032`, and `AUD-072` into one + coherent dormant-surface decision; `AUD-024`, `AUD-025`, `AUD-067`, and + `AUD-073` into one daypart identity/display sequence; and `AUD-040` with + `AUD-069` so canonical metadata ownership precedes context narrowing. +- Refactoring for `AUD-065` should keep source-local collection results and one + deterministic merge point; refactoring for `AUD-066` should keep initial + rejection and commit-time authorization in their current safety owners. + Stage 26 must sequence those behavior fixes before extracting helpers. + ## Stage Log ### Stage 1: Establish The Baseline And Audit Ledger @@ -5136,3 +5537,65 @@ Stage 22 routed these investigation leads to their assigned later stages: - Retained decisions: `RET-050` through `RET-052`. - Open questions: the four leads recorded above are routed to their assigned later stages. + +### Stage 23: Audit Cross-Cutting Refactoring And Deduplication Opportunities + +- Status: Complete. +- Scope reviewed: graph exact-fingerprint and semantic-similarity candidates; + repeated report/date policies, mappings, validation, path handling, result + and error projections, report and module variants, workflow stages, template + parsing, canonicalization, value copying, prompt curation, generated-text + APIs, render contexts, registry construction, fact requirements, dormant + compatibility surfaces, test ownership already established in Stage 21, and + production/test-file change history. +- Exclusions: no structural remediation was implemented; documentation + coherence remains Stage 24; adversarial/dynamic validation remains Stage 25; + consolidation, priority, and remediation sequencing remain Stages 26-27. No + production code or tests were changed. + +#### Refactoring And Duplication Accounting + +| Candidate | Shared behavior, drift/change evidence, and required stable boundary | Disposition | +| --- | --- | --- | +| Daypart canonical keys | Briefing and generated-text contain exact 17-line normalizers introduced in different commits. Both must map accepted configuration names to the same snapshot/render key; collisions are already consequential under `AUD-024`. | One domain owner under `AUD-067`; preserve display labels and semantic roles separately. | +| Distributor template grammar | Single and batch rendering contain exact 27-line brace parsers and duplicated malformed-input tests. Batch copied the grammar later; typed values and variable sets differ, syntax does not. | One private parser callback under `AUD-068`; retain typed public wrappers and family policies. | +| Prepared/report metadata | Two briefing builders repeat ten identity/context fields; app retains outer metadata plus a metadata stanza and projects prompt metadata again. Templates use neither complete facts nor module metadata exposed by all contexts. Existing conflict is `AUD-040`. | Establish one prepared identity and narrow projections under `AUD-069`; preserve prompt YAML and Markdown contracts. | +| Fact requirements | Names live in `module`, lists in briefing definitions, and availability in two switches; constructor accepts unknown or miscategorized strings. `modules.go` has 21 revisions across continued module growth. | Define descriptor and predicate once under `AUD-070`; keep per-module lists/missing policy explicit. | +| CLI report dates | Generate and comparison each repeat flag eligibility plus required/default/parse logic. Policies arrived in different commits, and a cross-action test now guards agreement in a 36-change file. | One private CLI descriptor/resolver under `AUD-071`; preserve action-specific errors and request types. | +| Dormant compatibility surfaces | YAML loading, three forecast threshold exports, complete alert envelopes, and canonical normalized generated JSON have no production consumer; some retain tests or runtime copies. | Retire together with related `AUD-001`/`AUD-032` work under `AUD-072`, unless Stage 26 identifies a concrete owner. | +| Capitalization | Rune-safe `sentenceCase` and byte-slicing `titleWord` express the same presentation mechanic; the latter has four prompt-facing callers and corrupts accepted multibyte labels. | One display-only rune-safe helper under `AUD-073`; canonical identity remains `AUD-067`. | +| Report variants | Daily/Tomorrow context builders are fingerprint-identical and catalog/planning wrappers are similar, but distinct types, schema/template pairs, IDs, and planning fields are intentional contracts. | Retain thin explicit variants under `RET-053`; existing private day-style helpers own shared mechanics. | +| Prompt projections | Rich current/hourly/daypart values require lengthy field copies into narrower prompt types. Omitted fields are an authority allowlist, not incidental mapping repetition. | Retain reviewable projections under `RET-054`; no reflection, embedding, or generated mega-mapper. | +| Maps and absolute paths | Three packages locally copy string maps; app/comparison perform similar absolute-clean checks. Copies protect separate mutation boundaries, while path errors and downstream policy differ. | Retain tiny local helpers under `RET-055`; no generic utility package. | +| Config and briefing registry | Config calls the runtime briefing registry for module IDs, option types, supported reports, and composition validation. | Retain under `RET-056`; duplicating a config descriptor would increase drift. | +| Workflow and result translation | Generate, batch, and comparison share phase names but differ in preflight scope, continuation, concurrency, transaction, notification, partial results, and safe CLI projection. | Retain visible state machines and boundary-specific result/error projections under `RET-057`; extract only local phases after open correctness findings settle. | +| Prepared clone | `cloneResolved` manually copies slices and the sole reference-bearing option; current mutation tests cover it. | Retain under `RET-058`; introduce an option-owned clone contract only when another reference-bearing shape appears. | +| Test fixtures and duplicate assertions | Stage 21 already identified the concrete cross-owner and implementation-coupled cases in `AUD-039`, `AUD-061`, and `AUD-063`; remaining local fixtures/tables protect distinct contracts. | Do not create a shared fixture framework or duplicate Stage 21 findings. | + +#### Commands And Evidence + +- Enumerated all indexed function nodes and grouped exact fingerprints, then + manually separated tests, thin report variants, local safety helpers, and + genuine shared policy. Used semantic graph search, symbol snippets, and + inbound traces for metadata, requirements, projections, date handling, + workflows, and dormant APIs. +- Used targeted text search where assets and field references are outside graph + coverage. Confirmed no template references `.Collected`, `.Derived`, or + `.Modules.Metadata`; no production caller consumes validator normalized JSON, + `AlertRun.Raw`, the three forecast thresholds, or `LoadYAML`. +- Reviewed history rather than inferring burden from line similarity alone. + `root.go` has 36 changes, `briefing/modules.go` 21, and + `config/notify_templates.go` seven. The two daypart normalizers and the two + CLI action policies were introduced in distinct commits; batch notification + copied the existing template grammar when that feature arrived. +- Reconciled every earlier Stage 23 lead: unused persistence/loader surfaces + join `AUD-072`; metadata and render-context breadth become `AUD-069`; + requirements become `AUD-070`; date policy becomes `AUD-071`; Unicode and + daypart identity become `AUD-067`/`AUD-073`; projections, config coupling, + workflow metrics, and the current clone remain retained decisions. +- Ran `go test ./...`, `go vet ./...`, + `go run ./cmd/weatherreporter --help`, and `git diff --check`; all passed. +- Findings: `AUD-067` through `AUD-073`. +- Retained decisions: `RET-053` through `RET-058`. +- Open questions: the four leads recorded above are routed to their assigned + later stages.