Split configuration into wire models and a resolved domain model - #351
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire models and a factory in Buildvana.Core.Configuration now compose the configuration file, the command line, and the built-in defaults into Buildvana.Runtime's BuildvanaConfig, exactly once per run. Every default lives on the domain records as a property initializer; the GitHub token is read on demand through GetToken(), never stored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The schema now requires source and apiKeyEnv whenever a NuGet feed is stated, and name and email whenever git.identity is stated, so a half-written section fails at configuration load (BV1104) rather than at push or commit time. The domain counterparts become non-nullable required members, the prerelease-to-release feed fallback moves into the configuration factory, and the API key is read through the new GetApiKey() extension method, mirroring GetToken(). The schema also annotates each setting's built-in default value, taken from a fresh BuildvanaConfig, except release.configuration, whose default is the resolved dotnet.configuration and thus dynamic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The committer identity for release commits now resolves as the configured git.identity, then the CI bot identity supplied by the server adapter, then whatever the repository Git configuration states, and the release fails before building anything when none of the three exists. The bot identity used to lose to the repository configuration; it now outranks it, so release commits are attributed deterministically rather than to whatever a previous CI step left behind. UnknownServerAdapter answers the CI-bot question honestly with null instead of failing the build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #351 +/- ##
==========================================
+ Coverage 74.64% 75.15% +0.51%
==========================================
Files 165 170 +5
Lines 4586 4713 +127
Branches 788 802 +14
==========================================
+ Hits 3423 3542 +119
- Misses 1014 1019 +5
- Partials 149 152 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
rdeago
left a comment
There was a problem hiding this comment.
Review of the wire/domain configuration split. The architecture does what #350 asked for — one factory owning precedence, defaults on the domain records, strictly-BCL Runtime preserved — and the migration of the deleted validation/defaulting logic was audited member-by-member: defaults all moved faithfully, the multiple-config-file guard moved with its coverage, the prerelease→release feed fallback is intact, and the deleted test scenarios are re-covered by the new factory/provider tests. Conventions all check out too (BOMs, extension-block template, no non-BCL references in Runtime, env-var test parallelism rationale).
Two correctness clusters need fixing in this PR, plus smaller items — all anchored inline:
1. Forwarded -c/--configuration (two comments + CHANGELOG). The advertised bv pack -- -c Debug fails at the Restore step: the forwarded -c is folded into Restore.Args and dotnet restore rejects it with MSB1001 (verified empirically). The breakage predates this PR, but this PR documents the syntax as working and bakes it into a test. Separately, parsing the forwarded stream breaks the documented "passed through verbatim, never parsed by bv" contract: a forwarded token that merely looks like -c (e.g. as another option's value) is captured or, in trailing position, aborts the run with a bv error. Both point at one decision: if bv owns -c/--configuration in the forwarded stream, strip them after promotion and amend the CHANGELOG contract bullet; if it doesn't, don't read them.
2. Blank-vs-absent gap in the new load-time guarantee (four comments). required checks presence and "type": "string" accepts "", so the "a half-written section fails at configuration load" guarantee holds only for absent members. Blank values regress diagnostics the old code had: "" feed source/apiKeyEnv fail cryptically at push time (the old actionable "has no source" guards were deleted with their tests), blank git.identity members are persisted into .git/config and crash with an NRE (or fail post-build for whitespace), and a blank configuration dies as a Guard ArgumentException. One rule at one layer fixes the family — generator-emitted minLength: 1 for required strings, or factory-side stated-but-blank rejection — plus a one-line IsNullOrWhiteSpace consistency fix for tokenEnv.
Smaller items: a silent failure mode in the schema generator's defaults matching (name mismatch drops a section's defaults; make the miss throw), the duplicated BuildvanaRuntimeException→BuildFailedException translation block (share a tiny Tool-side helper; the Runtime-accessor routing itself is right), a judgment call on threading config.DotNet.Configuration through five commands including two that ignore it, and line-length violations on modified lines in four places.
Also examined and deliberately not flagged, as they turned out to be intentional design: the settings classes still parsing flags whose resolved values come from the overrides parser (documented attribute-carrier mechanism), DotNetConfig.All (hooks-contract surface per the completeness principle), the factory's per-call new of defaults records (documented single-source-of-defaults idiom), and the schema generator's hand-rolled name resolution (no serializer disagreement is reachable with these models, and JsonTypeInfo wouldn't remove the reflection dependency).
The parser already promoted a `-c`/`--configuration` stated after `--` into bv's own view of the build configuration, but left the tokens in the forwarded stream, which the factory folds into every pipeline command's arguments — and `dotnet restore` rejects `-c` with MSB1001, so the advertised `bv pack -- -c Debug` failed at the Restore step before the override could matter. bv now owns the two option names wherever they appear in the forwarded stream: reading consumes the tokens, only the stripped remainder reaches `dotnet`, and bv injects the resolved configuration itself in the form each command accepts. This is the only shape that can work: no single forwarded spelling suits all four pipeline commands (`-c` dies at restore, `-p:` is not understood by MTP-mode `dotnet test`). The changelog's `-c` story is consolidated into one bullet stating the final state, replacing a claim that the forwarded flag previously decided the build (it never did; the same command line already died at Restore), and the verbatim-pass-through bullet gains its one exception. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`required` checks presence and a string-typed schema accepts empty, so the "a half-written section fails at configuration load" guarantee held only for absent members: a blank feed source or apiKeyEnv failed cryptically at push time, blank git.identity members were persisted into .git/config and crashed the release afterwards, and a blank configuration died as an internal-parameter ArgumentException. One rule closes the family: a blank or all-whitespace string is never a value, enforced where each input enters. - Required strings: the generator emits `minLength: 1` and `pattern: "\S"` (presence alone is not a value; the pattern closes the whitespace-only hole that minLength leaves), and the validator learns both keywords, reporting BV1106/BV1107 with file and line like any other schema error. At most one string error is reported per value, so a blank member states one mistake once. - Optional strings: the factory folds blank to null, so the member counts as not stated and the next precedence tier applies. This generalizes the emptyChangelog rule to dotnet/release.configuration, versioning.prereleaseTag, and github.tokenEnv, whose previous Length-based check missed whitespace-only names. - Command line: CliOptionReader rejects a blank or whitespace value (inline or space-separated) exactly like a missing one, so overrides can never carry blank. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApplyDefaults conflated two cases behind one null: the defaults type has no property matching the schema member, and the default is null. The first is a modeling error — matching is by JSON name alone, so a rename or a lost [JsonPropertyName] on either side of a model pair silently dropped a whole section of defaults, with the CI schema check prescribing --update-and-commit as the remedy, baking the loss in. The miss now throws, naming the defaults type, the JSON name, and the opt-out. BuildvanaJsonConfig.Schema, wire-only by design and therefore legitimately without a domain counterpart, carries [JsonSchemaNoDefault] as of this commit. A null default value remains a skip, as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The try/catch turning BuildvanaRuntimeException into BuildFailedException existed byte-identical around GetApiKey() in DotNetService and GetToken() in the GitHubServerAdapter constructor. Routing secret access through the Runtime accessors is the intended design (one code path shared with hooks); the translation wrap is the shareable part, so it moves into RuntimeAccess.Translate, ready for the third secret accessor that will inevitably arrive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five commands injected BuildvanaConfig solely to hand config.DotNet.Configuration to the pipeline - Clean and Restore included, whose steps ignore the value. BuildPipeline now takes the resolved configuration itself and defaults a null parameter to dotnet.configuration, so a caller with nothing to say no longer repeats the resolved value at a step documented to ignore it. Only bv release states a configuration explicitly (release.configuration), which it needs anyway to locate build artifacts. The trade-off is deliberate: this reinstates the optional parameter the branch had removed, judged less misleading than requiring a value that two of five call sites cannot use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ReSharper suppression claimed Forwarded is read in the BuildPipeline constructor; it is read by CommandLineOverridesParser. The member docs also described the tokens as forwarded verbatim, which overstates since bv now consumes the configuration option out of the stream before forwarding the rest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wraps the Build/Test/PackSolutionAsync declarations (over the 120-character declaration threshold), splits the no-feed failure message and two XML documentation lines that exceeded the general 140-character limit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generate(Type, ...) and ApplyDefaults exceeded the 120-character declaration threshold. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The identical 170-character CA1812 suppression exceeded the general 140-character limit in DefaultsSchemaSample, DefaultsSchemaSection, RequiredSample, and their fourth twin GeneratorSample. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All 14 threads addressed and resolved — nine commits, one per finding, shared-fix threads sharing one:
One small out-of-scope fix on the way, recorded in the PR description's Additional changes: a1c6190 corrects Gate is green ( @rdeago please take another look. |
|
Re-reviewed the nine response commits against every thread. All 14 findings are correctly resolved, and the new diff introduces no new problems. Verified in particular:
Codecov (patch and project) and CodeQL are green on |
Checklist of related issues / discussions
Proposed changes
BuildvanaConfigmodeled the configuration file; nothing modeled the settings, so every consumer resolved defaults and precedence privately, across the five idioms catalogued in #350. This PR gives resolution a home, as designed there:Buildvana.Core.Configuration):*JsonConfigrecords faithful to the file, all members nullable (null= not stated in the file), plusCommandLineOverridesand the file discovery/loading code moved out ofBuildvana.Runtime. The provider types are renamed to match (BuildvanaJsonConfigProviderand friends).Buildvana.Runtime): the same record names, now the single resolved answer. Every default is a property initializer on the record that owns it; a nullable member is a domain option whosenullhas exactly one documented meaning, never "unspecified". The assembly keeps no JSON-file, schema, or command-line knowledge and stays strictly BCL.BuildvanaConfigFactory.Create(json, commandLine)): the only place precedence is applied — scalars flag → file → default;dotnetargs appendedall→ per-command → forwarded after--; env layered by key;release.configurationfalling back to the resolveddotnet.configuration; the prerelease feed falling back to the release feed. Both producers compose through it:bv(DI, with a realCommandLineOverrides) andComputeVersion(task host,null).DotNetServiceseam: the factory resolves each command's final args and environment; the service contributes only its base and trailing arguments. TheDotNetSettingsfamily dissolves, and forwarded arguments become a command-line contribution to the same resolution instead of a fourth path for the build configuration: the-c/--configurationtokens bv promotes out of the forwarded stream are also consumed by it, sobv pack -- -c Debugresolves and buildsDebugwithoutdotnet restore(which rejects-c) ever seeing the flag.RuntimeInfogains a requiredConfigurationmember carrying the domain model, snapshotted into each args file as it is written;ConfigFileis re-documented as the source-file pointer for hooks that edit the file itself;HookArgs.LoadConfig()andBuildvanaConfig.Load()are gone.GetToken()/GetApiKey()extension methods resolve values on demand and throwBuildvanaRuntimeException, translated toBuildFailedExceptionat bv's call sites. A test asserts no resolved secret ever appears in serialized hook args. One deviation from §7: extension methods, not extension properties — a getter that reads process state and throws on absence would betray property semantics, while the structural cannot-be-serialized guarantee is identical.defaultemission, annotated from a domain-model instance, so the schema documents exactly what an omitted setting resolves to (requiredneeded no generator code — System.Text.Json already emits it for C#requiredmembers, now locked by a test).nuget.feeds.*requiressource+apiKeyEnvandgit.identityrequiresname+email, so a half-written section fails at configuration load and the domain records carry non-nullable members. Blank is never a value: required strings carryminLength/patternconstraints enforced at load (BV1106/BV1107, with file and line), optional strings treat a stated blank as unstated, and a blank CLI option value is rejected like a missing one.schemas/buildvana.schema.jsonregenerated.git.identityis live:bv releaseresolves its committer identity as configured identity → CI bot identity → repository Git config → fail. The bot now outranks whatever a CI checkout left in the repository's config (recorded as a behavior change in the changelog); a configured identity outranks both.dotnet run .claude/tools/inspect.cs --gateexits zero.Docs (
Hooks.md,DirectoryStructure.md,EnvironmentVariables.md),PublicAPI.Unshipped.txt, the changelog (including the amendments §Risks called for), and this repository's ownbuildvana.jsoncare updated per the acceptance criteria.Additional changes
EnvVarHelper.Require's message reworded to the standard adopted forEnvironmentVariables.GetRequired("Required environment variable X is missing or empty."), so the one remaining non-configuration env read (GITHUB_OUTPUT) fails with the same words as everything else.UnknownServerAdapter.CIBotIdentitynow answersnull— a local build has an honest answer to that question — instead of failing the build; the class doc, already false forPushUsername/PushPassword, corrected to match.CHANGELOG.md: dropped the stale claim that the computed version is reported atnotice:level (the task reports it at detail level).docs/Hooks.md: the hook-args logging level corrected to trace (the doc said detail; the code says trace).docs/ConfigurationFiles.md→docs/SdkConfigurationFiles.md, with a newdocs/BuildvanaToolConfiguration.mdTODO stub forbuildvana.json; every reference re-pointed to whichever of the two it actually meant.buildvana.jsonc: four worked-example comments re-synced to the schema descriptions the file's intro promises they mirror.Buildvana.slnx: stale file entries fixed (.globalconfig,docs/Diagnostics.md,THIRD-PARTY-NOTICESwithout extension,NuGet.configcasing) and the real documentation and configuration file sets listed.CommandParameters.Forwarded: the ReSharper-suppression justification named the wrong reader (theBuildPipelineconstructor; it isCommandLineOverridesParser), and the member docs overstated "verbatim" forwarding now that bv consumes the configuration tokens out of the stream; both corrected.Types of changes
This pull request introduces the following types of changes:
docsdirectory) update.gitattributes,.gitignore)"Other" covers the restructuring itself:
Buildvana.Runtime's public surface changes substantially (types moved out, loaders removed, extension methods and a required member added), which the "Refactor" box excludes by its no-public-API-changes parenthetical.Breaking changes
This pull request introduces breaking changes:
Buildvana.Runtimehas never shipped stable, and the identity-precedence change is recorded as a behavior change on the preview line; nothing stable breaks.Checklist
docsdirectory) only:🤖 Generated with Claude Code