From 674bd066f6d0e670fdf0cc316c961992273fcad6 Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Mon, 19 Oct 2026 20:30:50 +0200 Subject: [PATCH 1/2] build: declare the UTF-8 BOM charset for C# files Line endings are left to core.autocrlf: the repository stores most .cs files with LF, and declaring crlf would make editors rewrite files checked out with LF. --- .editorconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.editorconfig b/.editorconfig index 46da7f34..fd4400a1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,6 +25,11 @@ generated_code = true # C# files [*.cs] +# Encoding (*) +# No end_of_line: the repository stores most .cs files with LF and relies on core.autocrlf; declaring crlf +# makes editors rewrite every line of a file checked out with LF. +charset = utf-8-bom + # New line preferences csharp_new_line_before_open_brace = all csharp_new_line_before_else = true From 58ae1de285af5c80370821579d04061cbe3918d2 Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Mon, 19 Oct 2026 20:30:50 +0200 Subject: [PATCH 2/2] docs: replace convention documents with AGENTS.md rules and ADRs Remove code-conventions.md and documentation-guidelines.md. AGENTS.md states the rule precedence and the documentation layout. ADR 0001 records releases without update channels, ADR 0002 the shared MSI UpgradeCode. --- AGENTS.md | 30 ++++++++++- .../0001-releases-without-update-channels.md | 45 ++++++++++++++++ docs/adr/0002-shared-msi-upgrade-code.md | 53 +++++++++++++++++++ docs/design/code-conventions.md | 36 ------------- docs/documentation-guidelines.md | 42 --------------- 5 files changed, 126 insertions(+), 80 deletions(-) create mode 100644 docs/adr/0001-releases-without-update-channels.md create mode 100644 docs/adr/0002-shared-msi-upgrade-code.md delete mode 100644 docs/design/code-conventions.md delete mode 100644 docs/documentation-guidelines.md diff --git a/AGENTS.md b/AGENTS.md index 25a89627..1e029e10 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,10 +26,36 @@ configuration passed to the frontend. internals of `Bravo` through `InternalsVisibleTo`. The build stamps the version from git history, so a clone or a CI checkout must be complete: with a shallow clone the build fails or produces a wrong version. +## Code + +Which rule applies, in order of precedence: + +1. `.editorconfig` and the analyzers, for everything they cover. Editors apply `.editorconfig`, agent file tools + do not: write `.cs` files as UTF-8 with BOM, keep the line endings of a file you edit, and use CRLF in a new + one. A different encoding or line ending shows the whole file as changed. +2. The Microsoft [.NET design guidelines](https://learn.microsoft.com/dotnet/standard/design-guidelines/) for + names and API design in code you add or change, also where the surrounding code differs. +3. Code you do not otherwise change is not changed for conformance alone. Align a type when a pull request + changes it for another reason, with the rename in its own commit. +4. Contracts that installed clients and other tools depend on win over all of the above. + +## Documentation + +| Location | Content | +| --- | --- | +| `docs/design/` | Current behavior that the code cannot state: invariants, ownership, external constraints, each with the consequence of breaking it | +| `docs/adr/` | One decision per file, with its reasons and rejected alternatives | + +Planned work and its status are not kept in this repository. + +Update a document in the same pull request that changes what it describes, and delete statements that no +longer hold. Do not add or change a rule in this file, or a decision in `docs/adr/`, unless the maintainer +asked for it. + ## Read before you write | Document | Read it before | | --- | --- | -| [docs/design/code-conventions.md](docs/design/code-conventions.md) | creating or editing any `.cs` file. Encoding and design rules apply from its first line, and a wrong encoding rewrites the whole file. | | [docs/design/versioning.md](docs/design/versioning.md) | touching `version.json`, the build number, or any code that compares versions. | -| [docs/documentation-guidelines.md](docs/documentation-guidelines.md) | writing or updating anything under `docs/`. | +| [ADR 0001](docs/adr/0001-releases-without-update-channels.md) | touching the update check, the update feed, or the release of a version. | +| [ADR 0002](docs/adr/0002-shared-msi-upgrade-code.md) | changing `UpgradeCode`, `MajorUpgrade` or the product version in `installer/wix`. | diff --git a/docs/adr/0001-releases-without-update-channels.md b/docs/adr/0001-releases-without-update-channels.md new file mode 100644 index 00000000..2fa7b768 --- /dev/null +++ b/docs/adr/0001-releases-without-update-channels.md @@ -0,0 +1,45 @@ +# 0001. Releases without update channels + +- Status: Accepted +- Date: 2026-10-19 + +## Context + +The update check reads one of two feeds, selected by the user or by the `UpdateChannel` policy: +`Stable` reads `bravo-public/currentversion.json`, `Dev` reads `bravo-internal/currentversion.json`, both on +`bravorelease.blob.core.windows.net`. The selector is shown to every user, `Dev` serves untested internal +builds, and the channel spreads over settings, policy templates, the API, the frontend and every translation. + +Releases are published on GitHub, and a preview carries its prerelease tag in `version.json` +([versioning](../design/versioning.md)). A preview and the release that follows it share `Major.Minor.Patch` +and differ only in the fourth field of the file version. + +## Decision + +- Every release is published on GitHub, as a prerelease or as a stable release. +- The update check reads `bravo-public/currentversion.json` only. The feed lists stable releases only; + previews have no feed entry and are downloaded from the GitHub release page. +- The feed `version` is the four-field numeric file version of the release, for example `1.1.0.25`. +- The channel is removed from the application: the selector, the user setting, the `UpdateChannel` policy, the + API parameter and the strings. +- `bravo-internal/currentversion.json` is updated once, to the first release without channels, and then left + unchanged. + +## Rejected alternatives + +- A separate preview feed, or an opt-in to preview notifications: it reintroduces the channel under another + name, for a preview audience that downloads builds by hand. +- Reading releases from the GitHub API instead of the feed: installed clients read the feed regardless. + +## Consequences + +- Installed clients on `Dev` read only `bravo-internal`: its single update moves them to a release that reads + `bravo-public`. +- Both feed URLs are compiled into installed clients and stay available while those clients exist. +- A three-field `version` parses with an undefined fourth field and compares lower than any build of the same + `Major.Minor.Patch`, so a preview user is never notified of the release. A non-numeric `version` makes every + installed client fail in `Version.Parse`. +- A `1.0.x` release is no longer possible: installed `1.0` builds carry dated file versions such as + `1.0.9517.31194`, which compare higher than `1.0.14` in the feed and in the MSI. +- An `UpdateChannel` value set by an administrator is ignored; the release notes say so. +- Existing `usersettings.json` files keep reading correctly: the serializer ignores the removed property. diff --git a/docs/adr/0002-shared-msi-upgrade-code.md b/docs/adr/0002-shared-msi-upgrade-code.md new file mode 100644 index 00000000..37e32feb --- /dev/null +++ b/docs/adr/0002-shared-msi-upgrade-code.md @@ -0,0 +1,53 @@ +# 0002. One UpgradeCode for the per-user and the per-machine MSI + +- Status: Accepted +- Date: 2026-10-19 + +## Context + +Bravo ships two MSI packages from the same build: [Bravo.wxs](../../installer/wix/src/Bravo/Bravo.wxs), +installed per machine, and [Bravo-perUser.wxs](../../installer/wix/src/Bravo/Bravo-perUser.wxs), installed +per user. Both declare the same `UpgradeCode`, take the MSI `ProductVersion` from the file version +([versioning](../design/versioning.md)), and author `MajorUpgrade` with `AllowSameVersionUpgrades="yes"` and +`AllowDowngrades="no"`. + +Windows Installer behavior that constrains the choice, from the Microsoft documentation: + +| Behavior | Source | +| --- | --- | +| A major upgrade is never installed across installation contexts: a per-user product is upgraded only by a per-user installation, a per-machine product only by a per-machine one | [Major Upgrades](https://learn.microsoft.com/windows/win32/msi/major-upgrades) | +| Only the first three fields of `ProductVersion` are compared; the fourth is ignored | [ProductVersion](https://learn.microsoft.com/windows/win32/msi/productversion) | +| `FindRelatedProducts` runs at the first installation only, not in maintenance mode or uninstallation | [FindRelatedProducts](https://learn.microsoft.com/windows/win32/msi/findrelatedproducts-action) | +| One `UpgradeCode` can cover the versions and languages of a single product; each stand-alone product has its own | [Using an UpgradeCode](https://learn.microsoft.com/windows/win32/msi/using-an-upgradecode) | +| A dual-purpose package can install in either context; the context of the first installation is used for all later updates, repairs and removal | [Single Package Authoring](https://learn.microsoft.com/windows/win32/msi/single-package-authoring) | + +`AllowSameVersionUpgrades="yes"` makes WiX set `msidbUpgradeAttributesVersionMaxInclusive`, so an installed +product with the same three fields is treated as an older version and removed +([WiX MajorUpgrade](https://docs.firegiant.com/wix/schema/wxs/majorupgrade/)). Without it, two packages that +differ only in the fourth field install side by side as two products. The inclusive maximum triggers the ICE61 +warning ([ICE61](https://learn.microsoft.com/windows/win32/msi/ice61)), which the +[packaging pipeline](../../.azure/pipelines/build-bravo.yaml) suppresses. + +## Decision + +Keep one `UpgradeCode` for both packages. They are one product in two installation contexts, like a +dual-purpose package. + +## Rejected alternatives + +- A separate `UpgradeCode` per package: the two contexts never upgrade each other in either case, so nothing + changes for users. The new package would no longer find installations made with the current code, and would + install beside them, unless its `Upgrade` table kept the current code permanently. + +## Consequences + +- Each package upgrades installations of its own context. A per-user and a per-machine installation can + coexist on the same computer, under distinct product names in Programs and Features, and neither removes + the other. +- Builds of the same `Major.Minor.Patch` replace each other in either direction, only because of + `AllowSameVersionUpgrades`: a preview upgrades to its release, and a preview installed over its release is + not blocked as a downgrade. +- A lower `Major.Minor.Patch` is blocked by `AllowDowngrades="no"`; for the consequence on `1.0.x` see + [ADR 0001](0001-releases-without-update-channels.md). +- The `UpgradeCode` does not tell the two packages apart; the product name and the product code do. Bravo has + no winget manifest. diff --git a/docs/design/code-conventions.md b/docs/design/code-conventions.md deleted file mode 100644 index cd178285..00000000 --- a/docs/design/code-conventions.md +++ /dev/null @@ -1,36 +0,0 @@ -# Code conventions - -Rules that apply to code in this repository, beyond what `.editorconfig` enforces automatically. - -## Files - -New or rewritten `.cs` files are UTF-8 **with** BOM and use CRLF. This is not configured anywhere: it is what -every existing file under `src/` is, and `.editorconfig` sets `charset` only for project and resource files. -A tool that defaults to UTF-8 without BOM, or to LF, turns a small edit into a whole-file diff. - -## Naming and design - -Follow the Microsoft .NET naming and design guidelines. Where a pattern already in the repository diverges -from them, the guidelines win: a local precedent is not an argument for repeating a mistake. - -## Types - -Assign primary constructor parameters to explicit `private readonly` fields. Do not reference a parameter -directly from the body of the type: an explicit field declares the dependency, its lifetime and its -mutability at the top of the type, where they can be read. - -## Comments - -Comment only what the code cannot express: origin, constraints, or rationale. Never restate the code. Document -the member, not its callers. Usage rules belong to the caller or to docs/. Keep summaries short and semantic -and exclude implementation details; put them in inline comments when they matter. - -## Testability - -Design for testability from the start: - -- keep classes small and focused on one responsibility; -- inject dependencies through DI instead of relying on static state; -- pass external state — process, registry, filesystem, clock, network — through dependencies that tests can control. - -A type that can only be tested by running the application is not finished. diff --git a/docs/documentation-guidelines.md b/docs/documentation-guidelines.md deleted file mode 100644 index daa84f92..00000000 --- a/docs/documentation-guidelines.md +++ /dev/null @@ -1,42 +0,0 @@ -# Documentation guidelines - -Rules for writing and updating the documents under `docs/`. The reader is an LLM working on this repository, -not an end user. - -## What a document contains - -Write what the code cannot state: - -- constraints and invariants, each with the consequence of violating it; -- the reason behind a choice, when the result alone does not explain it; -- facts that live outside the repository: external services, deployed clients, published artifacts. - -Do not restate the code: no property or method lists, no file walkthroughs, no description of a control flow -that can be read in the source. Anything copied from the code drifts as soon as the code changes. Reference a -file by link when the reader needs it; do not reproduce its content. - -## Scope - -One document, one topic. A document named after a topic covers that topic only. Adjacent material belongs to -the adjacent document, or does not exist yet. - -## Form - -- Schematic: short sections, tables for enumerable facts, one idea per paragraph. -- Direct, professional language. Short sentences. No filler, no narration. -- English. -- A rule states what holds and what breaks when it is violated. -- Motivation is a clause attached to the rule it justifies, never a section of its own. - -## What a document is not - -- Not a changelog: no dates, no "decision taken on", no "supersedes", no history of what changed. -- Not a plan: no open items, no TODO, no status. Planned work belongs to a plan document. -- Not a tutorial: no step-by-step walkthrough of ordinary tasks. - -A document describes the current state as if it had always been that way. - -## Maintenance - -Update a document in the same change that alters the behaviour it describes. When a rule stops holding, delete -it; do not annotate it as obsolete.