diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e791676..905c067 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,6 +7,7 @@ This description is published verbatim as the release notes of a release-bound p - `(#123)` at the end of a bullet closes the issue it delivers; `(part of #123)` or `see #123` keeps it open; `Cratis/Repo#123` names another repository. One `(#123)` per issue, never `(#123, #124)`, which closes nothing: write `(#123) (#124)`. Put `(#123)` nowhere else, headings and the summary included. - Never write `Closes #123`, `Fixes #123` or `Refs #123`, with a number or an issue URL. - No Overview, Test plan, Verification, review notes, test or CI results, "Original prompt" transcripts or which agent/model wrote it - those go in a pull request comment. +- No "verified by/in/against ...", test counts, "not verified", stacking, retarget, merge or deploy instructions, also with `no-release`: any pull request may become release-bound. After merging main, re-check every bullet against the diff. - Absolute https:// links only; relative links 404 on the release page. - Replace the placeholder bullets and delete empty sections. --> diff --git a/.github/scripts/bootstrap-common-workflows.sh b/.github/scripts/bootstrap-common-workflows.sh index 096bfb9..dfc1f7e 100644 --- a/.github/scripts/bootstrap-common-workflows.sh +++ b/.github/scripts/bootstrap-common-workflows.sh @@ -8,6 +8,8 @@ # - auto-approve-publish-deployments.yml — auto-approves npm/nuget trusted publishing deployments # - verify-no-work-records.yml — fails PRs that track AI session work records # - verify-release-notes.yml — fails release-bound PRs whose description breaks the release-note contract +# - verify-semver-label.yml — requires exactly one release-intent label; no-release only on Dependabot PRs; +# only in repositories that release with Cratis/release-action # - .github/codeql/codeql-config.yml — shared CodeQL query filters # # Called by .github/workflows/bootstrap-common-workflows.yml after checkout. @@ -134,9 +136,10 @@ BOOTSTRAPPED_FILES[".github/workflows/verify-no-work-records.yml"]="bmFtZTogVmVy # # # # `edited` re-runs the check when the description changes and `labeled` and # # `unlabeled` when the release label does. There is no branch or label filter: the -# # gate itself only checks pull requests into the default branch that carry exactly -# # one of major, minor or patch, and passes the rest (no release label yet, -# # no-release, Dependabot) with a notice. +# # gate itself checks pull requests into the default branch, fails one that carries +# # major, minor or patch, warns on one labelled no-release or not labelled yet, and +# # passes Dependabot's with a notice. `pull-requests: read` lets it read the pull +# # request as it is now, so a re-run sees the current labels and description. # # # # The job is named release-notes so the check reads `release-notes / verify` and # # does not collide with other `verify / verify` gates. @@ -155,13 +158,67 @@ BOOTSTRAPPED_FILES[".github/workflows/verify-no-work-records.yml"]="bmFtZTogVmVy # # permissions: # contents: read +# pull-requests: read # # jobs: # release-notes: # uses: Cratis/Workflows/.github/workflows/verify-release-notes.yml@main # with: # runs-on: ${{ vars.RUNNER_GATE || 'ubuntu-latest' }} -BOOTSTRAPPED_FILES[".github/workflows/verify-release-notes.yml"]="bmFtZTogVmVyaWZ5IFJlbGVhc2UgTm90ZXMKCiMgVGhpbiBjYWxsZXIgb2YgdGhlIG9yZ2FuaXphdGlvbi13aWRlIHJlbGVhc2Utbm90ZXMgZ2F0ZS4gVGhpcyBwdWxsIHJlcXVlc3QncwojIGRlc2NyaXB0aW9uIGlzIHB1Ymxpc2hlZCB2ZXJiYXRpbSBhcyB0aGUgcmVsZWFzZSBub3Rlcywgc28gdGhlIGdhdGUgY2hlY2tzIGl0CiMgYmVmb3JlIHRoZSBtZXJnZS4gVGhlIGNvbnRyYWN0IC0gd2hpY2ggc2VjdGlvbnMsIGlzc3VlIHJlZmVyZW5jZXMgYW5kIGNvbnRlbnQKIyBhcmUgYWxsb3dlZCwgYW5kIHdoYXQgdGhlIGVycm9ycyBzYXkgLSBsaXZlcyBpbgojIENyYXRpcy9Xb3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3MvdmVyaWZ5LXJlbGVhc2Utbm90ZXMueW1sOyBkbyBub3QgcmVpbnRyb2R1Y2UKIyBsb2dpYyBoZXJlLiBJbnN0YWxsZWQgYW5kIGtlcHQgY3VycmVudCBieSBDcmF0aXMvV29ya2Zsb3dzJwojIGJvb3RzdHJhcC1jb21tb24td29ya2Zsb3dzLgojCiMgYGVkaXRlZGAgcmUtcnVucyB0aGUgY2hlY2sgd2hlbiB0aGUgZGVzY3JpcHRpb24gY2hhbmdlcyBhbmQgYGxhYmVsZWRgIGFuZAojIGB1bmxhYmVsZWRgIHdoZW4gdGhlIHJlbGVhc2UgbGFiZWwgZG9lcy4gVGhlcmUgaXMgbm8gYnJhbmNoIG9yIGxhYmVsIGZpbHRlcjogdGhlCiMgZ2F0ZSBpdHNlbGYgb25seSBjaGVja3MgcHVsbCByZXF1ZXN0cyBpbnRvIHRoZSBkZWZhdWx0IGJyYW5jaCB0aGF0IGNhcnJ5IGV4YWN0bHkKIyBvbmUgb2YgbWFqb3IsIG1pbm9yIG9yIHBhdGNoLCBhbmQgcGFzc2VzIHRoZSByZXN0IChubyByZWxlYXNlIGxhYmVsIHlldCwKIyBuby1yZWxlYXNlLCBEZXBlbmRhYm90KSB3aXRoIGEgbm90aWNlLgojCiMgVGhlIGpvYiBpcyBuYW1lZCByZWxlYXNlLW5vdGVzIHNvIHRoZSBjaGVjayByZWFkcyBgcmVsZWFzZS1ub3RlcyAvIHZlcmlmeWAgYW5kCiMgZG9lcyBub3QgY29sbGlkZSB3aXRoIG90aGVyIGB2ZXJpZnkgLyB2ZXJpZnlgIGdhdGVzLgojCiMgUlVOTkVSX0dBVEUgaXMgYW4gb3V0YWdlIGVzY2FwZSBoYXRjaCBhbmQgaXMgbm9ybWFsbHkgdW5zZXQ6IHNldCBpdCB0ZW1wb3JhcmlseSB0byByZXJvdXRlIHRoZQojIGpvYiB3aGVuIHRoZSBkZWZhdWx0IHJ1bm5lciBpcyBkb3duLiBBIHByaXZhdGUgcmVwb3NpdG9yeSBkb2VzIG5vdCB1c2UgdGhpcyBjYWxsZXIgYXMgaXM7IGl0CiMga2VlcHMgaXRzIG93biBjb3B5IHdpdGggaXRzIG93biBmYWxsYmFjaywgZm9yIGV4YW1wbGUKIyBgcnVucy1vbjogJHt7IHZhcnMuUlVOTkVSX0dBVEUgfHwgJ2NyYXRpcy1hcmMnIH19YC4KY29uY3VycmVuY3k6CiAgZ3JvdXA6ICR7eyBnaXRodWIud29ya2Zsb3cgfX0tJHt7IGdpdGh1Yi5ldmVudC5wdWxsX3JlcXVlc3QubnVtYmVyIHx8IGdpdGh1Yi5yZWYgfX0KICBjYW5jZWwtaW4tcHJvZ3Jlc3M6IHRydWUKCm9uOgogIHB1bGxfcmVxdWVzdDoKICAgIHR5cGVzOiBbb3BlbmVkLCBlZGl0ZWQsIHJlb3BlbmVkLCBzeW5jaHJvbml6ZSwgbGFiZWxlZCwgdW5sYWJlbGVkLCByZWFkeV9mb3JfcmV2aWV3XQoKcGVybWlzc2lvbnM6CiAgY29udGVudHM6IHJlYWQKCmpvYnM6CiAgcmVsZWFzZS1ub3RlczoKICAgIHVzZXM6IENyYXRpcy9Xb3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3MvdmVyaWZ5LXJlbGVhc2Utbm90ZXMueW1sQG1haW4KICAgIHdpdGg6CiAgICAgIHJ1bnMtb246ICR7eyB2YXJzLlJVTk5FUl9HQVRFIHx8ICd1YnVudHUtbGF0ZXN0JyB9fQo=" +BOOTSTRAPPED_FILES[".github/workflows/verify-release-notes.yml"]="bmFtZTogVmVyaWZ5IFJlbGVhc2UgTm90ZXMKCiMgVGhpbiBjYWxsZXIgb2YgdGhlIG9yZ2FuaXphdGlvbi13aWRlIHJlbGVhc2Utbm90ZXMgZ2F0ZS4gVGhpcyBwdWxsIHJlcXVlc3QncwojIGRlc2NyaXB0aW9uIGlzIHB1Ymxpc2hlZCB2ZXJiYXRpbSBhcyB0aGUgcmVsZWFzZSBub3Rlcywgc28gdGhlIGdhdGUgY2hlY2tzIGl0CiMgYmVmb3JlIHRoZSBtZXJnZS4gVGhlIGNvbnRyYWN0IC0gd2hpY2ggc2VjdGlvbnMsIGlzc3VlIHJlZmVyZW5jZXMgYW5kIGNvbnRlbnQKIyBhcmUgYWxsb3dlZCwgYW5kIHdoYXQgdGhlIGVycm9ycyBzYXkgLSBsaXZlcyBpbgojIENyYXRpcy9Xb3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3MvdmVyaWZ5LXJlbGVhc2Utbm90ZXMueW1sOyBkbyBub3QgcmVpbnRyb2R1Y2UKIyBsb2dpYyBoZXJlLiBJbnN0YWxsZWQgYW5kIGtlcHQgY3VycmVudCBieSBDcmF0aXMvV29ya2Zsb3dzJwojIGJvb3RzdHJhcC1jb21tb24td29ya2Zsb3dzLgojCiMgYGVkaXRlZGAgcmUtcnVucyB0aGUgY2hlY2sgd2hlbiB0aGUgZGVzY3JpcHRpb24gY2hhbmdlcyBhbmQgYGxhYmVsZWRgIGFuZAojIGB1bmxhYmVsZWRgIHdoZW4gdGhlIHJlbGVhc2UgbGFiZWwgZG9lcy4gVGhlcmUgaXMgbm8gYnJhbmNoIG9yIGxhYmVsIGZpbHRlcjogdGhlCiMgZ2F0ZSBpdHNlbGYgY2hlY2tzIHB1bGwgcmVxdWVzdHMgaW50byB0aGUgZGVmYXVsdCBicmFuY2gsIGZhaWxzIG9uZSB0aGF0IGNhcnJpZXMKIyBtYWpvciwgbWlub3Igb3IgcGF0Y2gsIHdhcm5zIG9uIG9uZSBsYWJlbGxlZCBuby1yZWxlYXNlIG9yIG5vdCBsYWJlbGxlZCB5ZXQsIGFuZAojIHBhc3NlcyBEZXBlbmRhYm90J3Mgd2l0aCBhIG5vdGljZS4gYHB1bGwtcmVxdWVzdHM6IHJlYWRgIGxldHMgaXQgcmVhZCB0aGUgcHVsbAojIHJlcXVlc3QgYXMgaXQgaXMgbm93LCBzbyBhIHJlLXJ1biBzZWVzIHRoZSBjdXJyZW50IGxhYmVscyBhbmQgZGVzY3JpcHRpb24uCiMKIyBUaGUgam9iIGlzIG5hbWVkIHJlbGVhc2Utbm90ZXMgc28gdGhlIGNoZWNrIHJlYWRzIGByZWxlYXNlLW5vdGVzIC8gdmVyaWZ5YCBhbmQKIyBkb2VzIG5vdCBjb2xsaWRlIHdpdGggb3RoZXIgYHZlcmlmeSAvIHZlcmlmeWAgZ2F0ZXMuCiMKIyBSVU5ORVJfR0FURSBpcyBhbiBvdXRhZ2UgZXNjYXBlIGhhdGNoIGFuZCBpcyBub3JtYWxseSB1bnNldDogc2V0IGl0IHRlbXBvcmFyaWx5IHRvIHJlcm91dGUgdGhlCiMgam9iIHdoZW4gdGhlIGRlZmF1bHQgcnVubmVyIGlzIGRvd24uIEEgcHJpdmF0ZSByZXBvc2l0b3J5IGRvZXMgbm90IHVzZSB0aGlzIGNhbGxlciBhcyBpczsgaXQKIyBrZWVwcyBpdHMgb3duIGNvcHkgd2l0aCBpdHMgb3duIGZhbGxiYWNrLCBmb3IgZXhhbXBsZQojIGBydW5zLW9uOiAke3sgdmFycy5SVU5ORVJfR0FURSB8fCAnY3JhdGlzLWFyYycgfX1gLgpjb25jdXJyZW5jeToKICBncm91cDogJHt7IGdpdGh1Yi53b3JrZmxvdyB9fS0ke3sgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5udW1iZXIgfHwgZ2l0aHViLnJlZiB9fQogIGNhbmNlbC1pbi1wcm9ncmVzczogdHJ1ZQoKb246CiAgcHVsbF9yZXF1ZXN0OgogICAgdHlwZXM6IFtvcGVuZWQsIGVkaXRlZCwgcmVvcGVuZWQsIHN5bmNocm9uaXplLCBsYWJlbGVkLCB1bmxhYmVsZWQsIHJlYWR5X2Zvcl9yZXZpZXddCgpwZXJtaXNzaW9uczoKICBjb250ZW50czogcmVhZAogIHB1bGwtcmVxdWVzdHM6IHJlYWQKCmpvYnM6CiAgcmVsZWFzZS1ub3RlczoKICAgIHVzZXM6IENyYXRpcy9Xb3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3MvdmVyaWZ5LXJlbGVhc2Utbm90ZXMueW1sQG1haW4KICAgIHdpdGg6CiAgICAgIHJ1bnMtb246ICR7eyB2YXJzLlJVTk5FUl9HQVRFIHx8ICd1YnVudHUtbGF0ZXN0JyB9fQo=" + +# verify-semver-label.yml — requires exactly one of major, minor, patch or no-release, +# and only no-release on a Dependabot pull request (whose labels it corrects first). +# Installed only where the repository releases (see releases_with_release_action). +# Decodes to: +# name: Verify Semver Label +# +# # Thin caller of the organization-wide release-intent gate. The policy - which +# # labels are accepted and what the errors say - lives in +# # Cratis/Workflows/.github/workflows/verify-release-intent.yml. Thirty diverging +# # per-repository copies of that logic are how the 2026-08-25 unintended releases +# # happened; do not reintroduce logic here. Installed and kept current by +# # Cratis/Workflows' bootstrap-common-workflows. +# # +# # A Dependabot pull request first has its labels corrected: Dependabot adds major, +# # minor or patch on its own, and a Dependabot pull request only ever carries +# # no-release. The gate then reads the labels as they are now, so it sees the +# # correction although a label change made with GITHUB_TOKEN starts no new run. +# # +# # The job is named release-intent so the check reads `release-intent / verify` and +# # does not collide with `verify / verify` from verify-no-work-records. +# concurrency: +# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} +# cancel-in-progress: true +# +# on: +# pull_request: +# types: [opened, reopened, synchronize, labeled, unlabeled] +# # Scoped to the same branch Publish releases from. A pull request stacked onto another one's branch cannot +# # cut a release, so demanding a version label of it would be asking which version a merge that publishes +# # nothing should carry. +# branches: +# - main +# +# permissions: +# contents: read +# +# jobs: +# dependabot-labels: +# if: github.event.pull_request.user.login == 'dependabot[bot]' +# uses: Cratis/Workflows/.github/workflows/normalize-dependabot-labels.yml@main +# permissions: +# pull-requests: write +# +# release-intent: +# needs: dependabot-labels +# # Also after a failed or skipped correction: the gate then reports the labels as they are. +# if: ${{ !cancelled() }} +# uses: Cratis/Workflows/.github/workflows/verify-release-intent.yml@main +# permissions: +# contents: read +# pull-requests: read +BOOTSTRAPPED_FILES[".github/workflows/verify-semver-label.yml"]="bmFtZTogVmVyaWZ5IFNlbXZlciBMYWJlbAoKIyBUaGluIGNhbGxlciBvZiB0aGUgb3JnYW5pemF0aW9uLXdpZGUgcmVsZWFzZS1pbnRlbnQgZ2F0ZS4gVGhlIHBvbGljeSAtIHdoaWNoCiMgbGFiZWxzIGFyZSBhY2NlcHRlZCBhbmQgd2hhdCB0aGUgZXJyb3JzIHNheSAtIGxpdmVzIGluCiMgQ3JhdGlzL1dvcmtmbG93cy8uZ2l0aHViL3dvcmtmbG93cy92ZXJpZnktcmVsZWFzZS1pbnRlbnQueW1sLiBUaGlydHkgZGl2ZXJnaW5nCiMgcGVyLXJlcG9zaXRvcnkgY29waWVzIG9mIHRoYXQgbG9naWMgYXJlIGhvdyB0aGUgMjAyNi0wOC0yNSB1bmludGVuZGVkIHJlbGVhc2VzCiMgaGFwcGVuZWQ7IGRvIG5vdCByZWludHJvZHVjZSBsb2dpYyBoZXJlLiBJbnN0YWxsZWQgYW5kIGtlcHQgY3VycmVudCBieQojIENyYXRpcy9Xb3JrZmxvd3MnIGJvb3RzdHJhcC1jb21tb24td29ya2Zsb3dzLgojCiMgQSBEZXBlbmRhYm90IHB1bGwgcmVxdWVzdCBmaXJzdCBoYXMgaXRzIGxhYmVscyBjb3JyZWN0ZWQ6IERlcGVuZGFib3QgYWRkcyBtYWpvciwKIyBtaW5vciBvciBwYXRjaCBvbiBpdHMgb3duLCBhbmQgYSBEZXBlbmRhYm90IHB1bGwgcmVxdWVzdCBvbmx5IGV2ZXIgY2FycmllcwojIG5vLXJlbGVhc2UuIFRoZSBnYXRlIHRoZW4gcmVhZHMgdGhlIGxhYmVscyBhcyB0aGV5IGFyZSBub3csIHNvIGl0IHNlZXMgdGhlCiMgY29ycmVjdGlvbiBhbHRob3VnaCBhIGxhYmVsIGNoYW5nZSBtYWRlIHdpdGggR0lUSFVCX1RPS0VOIHN0YXJ0cyBubyBuZXcgcnVuLgojCiMgVGhlIGpvYiBpcyBuYW1lZCByZWxlYXNlLWludGVudCBzbyB0aGUgY2hlY2sgcmVhZHMgYHJlbGVhc2UtaW50ZW50IC8gdmVyaWZ5YCBhbmQKIyBkb2VzIG5vdCBjb2xsaWRlIHdpdGggYHZlcmlmeSAvIHZlcmlmeWAgZnJvbSB2ZXJpZnktbm8td29yay1yZWNvcmRzLgpjb25jdXJyZW5jeToKICBncm91cDogJHt7IGdpdGh1Yi53b3JrZmxvdyB9fS0ke3sgZ2l0aHViLmV2ZW50LnB1bGxfcmVxdWVzdC5udW1iZXIgfHwgZ2l0aHViLnJlZiB9fQogIGNhbmNlbC1pbi1wcm9ncmVzczogdHJ1ZQoKb246CiAgcHVsbF9yZXF1ZXN0OgogICAgdHlwZXM6IFtvcGVuZWQsIHJlb3BlbmVkLCBzeW5jaHJvbml6ZSwgbGFiZWxlZCwgdW5sYWJlbGVkXQogICAgIyBTY29wZWQgdG8gdGhlIHNhbWUgYnJhbmNoIFB1Ymxpc2ggcmVsZWFzZXMgZnJvbS4gQSBwdWxsIHJlcXVlc3Qgc3RhY2tlZCBvbnRvIGFub3RoZXIgb25lJ3MgYnJhbmNoIGNhbm5vdAogICAgIyBjdXQgYSByZWxlYXNlLCBzbyBkZW1hbmRpbmcgYSB2ZXJzaW9uIGxhYmVsIG9mIGl0IHdvdWxkIGJlIGFza2luZyB3aGljaCB2ZXJzaW9uIGEgbWVyZ2UgdGhhdCBwdWJsaXNoZXMKICAgICMgbm90aGluZyBzaG91bGQgY2FycnkuCiAgICBicmFuY2hlczoKICAgICAgLSBtYWluCgpwZXJtaXNzaW9uczoKICBjb250ZW50czogcmVhZAoKam9iczoKICBkZXBlbmRhYm90LWxhYmVsczoKICAgIGlmOiBnaXRodWIuZXZlbnQucHVsbF9yZXF1ZXN0LnVzZXIubG9naW4gPT0gJ2RlcGVuZGFib3RbYm90XScKICAgIHVzZXM6IENyYXRpcy9Xb3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3Mvbm9ybWFsaXplLWRlcGVuZGFib3QtbGFiZWxzLnltbEBtYWluCiAgICBwZXJtaXNzaW9uczoKICAgICAgcHVsbC1yZXF1ZXN0czogd3JpdGUKCiAgcmVsZWFzZS1pbnRlbnQ6CiAgICBuZWVkczogZGVwZW5kYWJvdC1sYWJlbHMKICAgICMgQWxzbyBhZnRlciBhIGZhaWxlZCBvciBza2lwcGVkIGNvcnJlY3Rpb246IHRoZSBnYXRlIHRoZW4gcmVwb3J0cyB0aGUgbGFiZWxzIGFzIHRoZXkgYXJlLgogICAgaWY6ICR7eyAhY2FuY2VsbGVkKCkgfX0KICAgIHVzZXM6IENyYXRpcy9Xb3JrZmxvd3MvLmdpdGh1Yi93b3JrZmxvd3MvdmVyaWZ5LXJlbGVhc2UtaW50ZW50LnltbEBtYWluCiAgICBwZXJtaXNzaW9uczoKICAgICAgY29udGVudHM6IHJlYWQKICAgICAgcHVsbC1yZXF1ZXN0czogcmVhZAo=" # .github/codeql/codeql-config.yml — shared CodeQL configuration # Decodes to: @@ -173,6 +230,59 @@ BOOTSTRAPPED_FILES[".github/workflows/verify-release-notes.yml"]="bmFtZTogVmVyaW # id: ca1031 BOOTSTRAPPED_FILES[".github/codeql/codeql-config.yml"]="bmFtZTogIkNyYXRpcyBDb2RlUUwgY29uZmlnIgoKcXVlcnktZmlsdGVyczoKICAjIENBMTAzMSBpcyBpbnRlbnRpb25hbGx5IGV4Y2x1ZGVkIGZyb20gdGhlIHNoYXJlZCBiYXNlbGluZS4KICAtIGV4Y2x1ZGU6CiAgICAgIGlkOiBjYTEwMzEK" +# ================================================================ +# Per-file skips +# ================================================================ +# A repository that keeps its own, deliberately different copy of one bootstrapped +# file is listed here for that file only; it still receives every other file. +# Unlike REPOS_IGNORE, which skips a repository entirely. +# +# Each entry: path in target repo -> space-separated repository names +# +# Arc.Kotlin - its verify-semver-label.yml pins verify-release-intent.yml to a full +# SHA and uses a literal concurrency-group prefix, each explained in +# that file; the @main caller would undo both + +declare -A SKIP_FILE_REPOS +SKIP_FILE_REPOS[".github/workflows/verify-semver-label.yml"]="Arc.Kotlin" + +# Whether $2 keeps its own copy of the bootstrapped file $1. +skips_file() { + local file_path="$1" repo="$2" skipped + for skipped in ${SKIP_FILE_REPOS[$file_path]:-}; do + [ "$skipped" = "$repo" ] && return 0 + done + return 1 +} + +# ================================================================ +# Release-intent caller: only where a repository releases +# ================================================================ +# verify-semver-label.yml demands one of major/minor/patch/no-release on every pull request. That is only +# meaningful where merging can cut a release, so a repository that never releases (a blog, a samples or workshop +# repository, a Homebrew tap) must not get a gate that turns every one of its pull requests red. + +SEMVER_LABEL_FILE=".github/workflows/verify-semver-label.yml" + +# Whether repository $1 releases with Cratis/release-action: it already has the release-intent caller, or one of +# its workflows uses Cratis/release-action (the owner is matched case-insensitively). $2 is the repository's +# recursive tree (JSON). A repository that starts releasing is picked up on the next run, without a list to +# maintain. +releases_with_release_action() { + local repo="$1" tree="$2" blob_sha content + if echo "$tree" | jq -e --arg path "$SEMVER_LABEL_FILE" '.tree[] | select(.path == $path)' >/dev/null 2>&1; then + return 0 + fi + while read -r blob_sha; do + [ -n "$blob_sha" ] || continue + content=$(gh api "repos/Cratis/$repo/git/blobs/$blob_sha" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null || true) + if grep -qi 'Cratis/release-action' <<<"$content"; then + return 0 + fi + done < <(echo "$tree" | jq -r '.tree[] | select(.type == "blob" and (.path | test("^[.]github/workflows/[^/]+[.]ya?ml$"))) | .sha') + return 1 +} + # ================================================================ # Pre-flight: verify PAT has write permission on target repositories # ================================================================ @@ -269,7 +379,21 @@ echo "$repos" | jq -r '.[]' | while read -r repo; do has_changes=false commit_parts=() + repo_releases="" for file_path in "${!BOOTSTRAPPED_FILES[@]}"; do + if skips_file "$file_path" "$repo"; then + echo " ℹ Skipping $file_path (Cratis/$repo keeps its own copy)" + continue + fi + if [ "$file_path" = "$SEMVER_LABEL_FILE" ]; then + if [ -z "$repo_releases" ]; then + if releases_with_release_action "$repo" "$subtree"; then repo_releases=yes; else repo_releases=no; fi + fi + if [ "$repo_releases" = no ]; then + echo " ℹ Skipping $file_path (Cratis/$repo does not release with Cratis/release-action)" + continue + fi + fi file_b64="${BOOTSTRAPPED_FILES[$file_path]}" case "$file_path" in .github/codeql/codeql-config.yml) diff --git a/.github/scripts/tests/fixtures/release-notes/corpus.json b/.github/scripts/tests/fixtures/release-notes/corpus.json new file mode 100644 index 0000000..304f56e --- /dev/null +++ b/.github/scripts/tests/fixtures/release-notes/corpus.json @@ -0,0 +1,7575 @@ +{ + "about": "Published release bodies of Cratis repositories (latest non-prerelease releases per repository, fetched 2026-10-01 with gh api graphql), each with the rules verify-release-notes reports for it as a release-bound pull request. Regenerate the rules only together with a deliberate rule change, and inspect every body whose rules change.", + "releases": [ + { + "repo": "Chronicle", + "tag": "v19.25.1", + "date": "2026-10-01", + "rules": [], + "body": "## Fixed\n\n- A projection replay on MongoDB no longer leaves a silo writing live changes into the `replay-` collection when the final write of the replay is rejected for some partitions. That silo kept sending every later change to a collection nothing reads until it restarted, so new and changed read model instances never appeared. Ending a replay now always returns the sink to the read model's own collection.\n- When the final write of a replay is rejected for some partitions, the rebuilt read model is still promoted, and the rejected partitions are recorded as failed partitions and retried from the event that failed. This matches the SQL sink and what a silo whose final write succeeded already did. If the final write throws and its outcome is unknown, the sink returns to the read model's collection without promoting the rebuilt one.\n- A sink no longer stays in bulk mode when its final bulk write throws. Before, later writes were held back until a thousand of them had queued up.\n- Failed partitions caused by a rejected MongoDB bulk write now say why: the failure message carries the MongoDB error code, category and server message, for example `MongoDB write error 11000 (DuplicateKey): E11000 duplicate key error …`. Each write error is also logged with its code, server message, operation index and partition, and write concern errors are logged too. Document contents and error detail documents are never logged.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.25.0", + "date": "2026-10-01", + "rules": [], + "body": "## Changed\n\n- Clearer messages for conflicting concurrency scopes and refused decision reads: (#4432)\n - Reading an event source with a decision read and also giving it an explicit concurrency scope in the same unit of work throws `DecisionReadConflictsWithConcurrencyScope`, a `ConflictingConcurrencyScopesForLabel`, whose message names the event source and says to remove the explicit scope or read it without a decision read.\n - Other conflicting scopes for one event source are described in words, such as \"expects no events yet\", instead of dumping `ConcurrencyScope` records with sentinel sequence numbers. `ConflictingConcurrencyScopesForLabel` exposes the event source as `ScopeLabel`.\n - `DecisionReadRefused` explains each `DecisionReadRefusalReason` and what to do, for example that a `Hierarchy` projection has children or nested objects and a flat read model should be read instead.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.24.1", + "date": "2026-09-30", + "rules": [], + "body": "## Fixed\n\n- An event whose constrained properties are all null or missing no longer claims the empty-string value in a unique constraint, so a later event from another source holding `\"\"` is no longer rejected as a violation. Clearing a constrained value now frees the claim the source held, so another source can then claim it. (#4383)\n- `Observers.MaxConcurrentPartitions` is now documented as what it is, an upper bound on parallel job steps applied together with `Jobs.MaxParallelSteps`, not a limit on live event delivery. The setting is added to the configuration reference. (#4405)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.24.0", + "date": "2026-09-30", + "rules": [], + "body": "The Workbench landing page works again, and the dashboards now follow the overview of the CLI workbench (see #3731).\n\n## Added\n\n- The namespace dashboard - the page an event store opens on - is laid out like the CLI workbench overview: a server health rail (connection, version, CPU and memory, the store, namespace and tail sequence number, and the event types, projections, read models and namespaces in the catalog), observer, failure, recommendation and job tiles, event throughput over the last hour, day or week, observers over time, the top event types, a list of what needs attention and the jobs that are running.\n- The event store dashboard adds the same figures up across its namespaces and lists every namespace with its events, observers, lag, failed partitions, recommendations and jobs, each leading to its own dashboard.\n- Every tile and list entry leads to the page where the figure can be acted on.\n\n## Fixed\n\n- The Workbench landing page no longer crashes to a blank screen while the key figures of an event store are loading, and the event stores are laid out across the width of the window.\n- Event statistics now update as events are appended. Observers of all event types, such as the statistics, received nothing live and only moved forward when the server restarted.\n- Event statistics now count each event type and namespace separately instead of folding every event into a single type in a single namespace.\n- An event store created while the server is running now gets the kernel's own projections, such as the event statistics, straight away instead of after the next restart.\n- The kernel's own projections now write to the storage the kernel runs on. On SQL storage they tried to use MongoDB and failed every event.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.23.1", + "date": "2026-09-30", + "rules": [], + "body": "## Fixed\n\n- The event serializer and the reactor side-effect handlers are always registered as scoped services, even when convention binding has already bound them as singletons. As singletons they captured the scoped event types registry (#4410)\n- Reducers now log observation stream errors by kind: cancellation at Debug, a lost kernel connection at Warning while reconnecting, and any other failure at Error. Reconnect behavior is unchanged (#4374)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.23.0", + "date": "2026-09-30", + "rules": [], + "body": "## Added\n\n- A constraint can declare the event sequences it applies to, with `ForEventSequences(...)` or `ForEventLog()` on the constraint builder, or `EventSequences` on `[Unique]`. The kernel then neither validates nor indexes it for any other sequence, so facts forwarded to the outbox no longer claim values there (#4398)\n- When declarations for one constraint are combined, one that names no sequence (meaning every sequence) wins over ones that name some (#4398)\n- Changing a constraint's sequences takes effect at registration. For every newly covered sequence in every namespace, the kernel first makes that sequence validate against the new definitions, then rebuilds its index from its existing events, whether or not the sequence was active. A sequence no longer covered stops validating and reading its index (#4398)\n- Known limits: sequences are enumerated from the well-known sequences plus those with persisted state, so a custom or inbox sequence whose state was never persisted can be missed. A crash between saving the definitions and starting the rebuild loses the rebuild. While a rebuild runs, validation sees a partially rebuilt index, and a concurrent append's newer entry can be overwritten (see #4408)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.22.2", + "date": "2026-09-30", + "rules": [], + "body": "Observing materialized read-model instances failed on MongoDB: the sink subscribed through Arc's `collection.Observe()` on `BsonDocument`, and the subscriber got an error right after the first page. It now works on MongoDB, and observation is more reliable on every backend.\n\n## Changed\n\n- Known limitation: during a replay, a lookup by key still reads the replay collection while lists read the primary one, as with the SQL sink (see #4391).\n- Known limitation: materialized observation of a globally scoped projection may read the wrong namespace (see #4392).\n- Known limitation: under sustained writes, pages queue behind each other; latest-wins delivery is proposed (see #4378).\n- Each shared change stream watches its read-model database, which needs the `changeStream` and `find` actions there. The built-in `read`/`readWrite`/`dbOwner` roles grant both. See Cratis/Documentation#19.\n\n## Fixed\n\n- **MongoDB:** `ObserveInstances` emits a fresh page after every change to the read model's collection, instead of failing after the first page. (#4365)\n- **One change stream per collection:** observers of the same collection share a single MongoDB change stream. It opens with the first observer and closes with the last, so many observers no longer hold one pooled connection each.\n- **Transient MongoDB errors** (a stepdown, a lost connection, a read cut short by a rename) are retried with a short, bounded backoff. Observers get an error only if the failure persists, and each failure is logged.\n- **Replays:** during a replay, observers and `GetInstances` see the primary collection, and the page and its `TotalCount` come from the same collection, as with the SQL sink. When the replay is promoted, observers get the replayed state without first flashing an empty page. A promoting collection left behind by an interrupted promotion doesn't block observation.\n- **Empty read models:** a read model with no instances yet, including one whose MongoDB collection was never created, emits an empty first page on MongoDB, InMemory and SQL alike.\n- **InMemory:** readers see the primary collection during a replay, observers are told when the replay is promoted, and clearing the primary collection (initial run, removal) is announced.\n- **Page ordering:** observed pages are delivered in the order the sink produced them, both in the kernel's gRPC service and in the .NET client's `IMaterializedReadModels.ObserveInstances`. Before, a slow earlier page could arrive after a newer one.\n- **.NET client:** a failed materialized observation in read-model reactors is logged instead of being rethrown unhandled.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.22.1", + "date": "2026-09-29", + "rules": [], + "body": "## Fixed\n\n- A projection now auto-maps properties from an event type registered at a later generation. The kernel read every event type's schema as generation 1, so a read model built from a generation 2 or later event got none of its properties mapped. Event type storage now returns the latest stored generation, and projections resolve the schema by event type. (#4367)\n- The Workbench's projection declaration editor (preview, save, code generation and schema inference) resolves event types by name at any generation. It uses the named generation when one is given, and otherwise the latest. (#4367)\n- Replayed, caught-up and joined events are always released with the compliance schema of the generation they were stored at, so `[PII]` values are decrypted correctly for event types that have more than one generation. (#4367)\n- An event forwarded from one event store's outbox to another store's inbox keeps the time it originally occurred. Before, the inbox copy was stamped with the forwarding time. (#4375)\n\nFollow-ups: #4376 (event-type schemas for defaulted concept parameters).\n" + }, + { + "repo": "Chronicle", + "tag": "v19.22.0", + "date": "2026-09-29", + "rules": [], + "body": "## Added\n\n- `IJsonSchemaGenerator.GenerateForReadModel` generates the JSON schema for a read model. Its default implementation returns `Generate`, so existing custom generators keep working. (#4330)\n\n## Fixed\n\n- A unique constraint with `IgnoreCasing` now rejects a value that differs only in casing. The .NET client did not send `IgnoreCasing` when it registered the constraint, so the kernel stored every unique constraint as case-sensitive. (#4368)\n- A reactor that loses its connection to the kernel (gRPC `Unavailable`) now logs a Warning and reconnects, instead of the Error \"observation stream errored out\". A cancelled stream logs at Debug and does not reconnect, and any other stream error is still an Error. An error thrown by your reactor handler is still an Error, even a gRPC `Cancelled` or `Unavailable` from your own calls. It is logged at Debug only when the reactor itself is stopping, such as during host shutdown. If the kernel stays away, the connection watchdog still logs each failed reconnect attempt at Error. (#4373)\n- A read-model property whose type is a concept, and whose record constructor parameter has a default (for example `OwnerSubject? Owner = null`), now gets its type, format and compliance metadata in the read-model schema. Before, it was untyped: the value was dropped when the read model was read, and a `[PII]` value was stored unencrypted. Event-type schemas are generated exactly as before, so event types that are already registered keep registering. Event types are tracked in #4376. (#4330)\n\n**Rollout for existing read models:** when the client registers the corrected schema, the kernel replaces the stored read-model definition. A schema change alone does not start a replay or create a replay recommendation. New writes use the corrected schema, but documents already stored under the old schema are not rewritten, including any `[PII]` value stored unencrypted. They are repaired only by replaying the projection or reducer that builds the read model, and you have to start that replay yourself.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.21.1", + "date": "2026-09-28", + "rules": [], + "body": "## Fixed\n\n- CommonJS applications can `require('@cratis/chronicle.contracts')`. The CommonJS build is now marked as CommonJS and has matching type declarations, and the packed-consumer check covers `require()` and Node16 CommonJS type-checking. (#4331)\n\n## Security\n\n- A pinned server certificate (the configured client certificate) is now trusted only when its chain is the sole validation failure. A host-name mismatch or a missing server certificate is rejected even when the certificate matches the pin. (#4344)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.21.0", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword", + "Heading not allowed" + ], + "body": "## Added\n- `UnitOfWork.RollbackAsOwner` lets a protected command transaction owner abandon enrolled decision reads with its opaque owner capability.\n\n## Changed\n- Application `Rollback()` and `Dispose()` refuse open owner-claimed units with enrolled decision reads, keeping their ambient transaction and guard intact. Owner rollback and public rollback during an in-flight protected commit refuse with `UnitOfWorkIsCompleting`. Plain legacy units keep their Compatibility behavior.\n- ASP.NET Core middleware uses owner rollback on request failure, preserving the action exception across concurrent commit/rollback completion races.\n- Reconciled with Chronicle#4258 lifecycle policies: preserves Strict/Compatibility late-staging behavior, in-flight Dispose no-op, locked commit snapshots, and named-tag append routing.\n\n## Verification\n- Whole Release solution build: zero warnings. Affected DotNET/AspNetCore/Testing specs pass. CI-filtered non-integration Release suite passes in serial execution (all projects, including Mongo and SQL). Nine client packages plus Contracts build for shipped frameworks, and documentation/format checks pass.\n- Final full cross-provider review of the exact feature diff passed with no findings. A subsequent rebase over Elixir-only main changes preserved the binary diff hash exactly. Local targeted Mongo integration timed out; **fresh PR CI integration must pass before merge**.\n\nCloses #4292\nRefs Cratis/Arc#2828" + }, + { + "repo": "Chronicle", + "tag": "v19.20.0", + "date": "2026-09-28", + "rules": [ + "Heading not allowed", + "Section order" + ], + "body": "This PR consolidates five fixes into one release. Each fix was reviewed on its own, and the combined branch passed the full Release build and all spec projects.\n\n## Added\n\n- A kernel option `Events.CausationPropertyRetention` (`events.causationPropertyRetention` in `chronicle.json`, `Cratis__Chronicle__Events__CausationPropertyRetention` in the environment) with values `Retain` (default, unchanged behavior) and `Omit`.\n - `Omit` stores each causation entry's type and time but empties its property values, on single and batch appends, revisions, redaction and forwarding to other event stores.\n - It prevents new retention only; values stored earlier are not rewritten. #4238 stays open for the classification-based follow-up (#4238)\n- `ReactorScenario` accepts a per-run `Defaults`, so reactor specs can use event types generated for that run.\n - Each in-process test event store registers its own event types instead of the process-wide set (#4162)\n\n## Fixed\n\n- The kernel no longer fails to start when another socket briefly holds its port. On Linux, port 35000 lies in the ephemeral source-port range, so an outbound connection made during startup could briefly take it, and the kernel then failed with \"address already in use\".\n - The kernel now retries only that bind error, for up to 30 seconds (the new `BindTimeout` option; `00:00:00` turns it off). The retry stops as soon as shutdown is requested.\n - The diagnostics name the socket holding the port (#4174)\n\n- A projection replay is treated as finalized only when at least one silo actually finalizes it.\n - A failed replay setup (`Establish`) is reported instead of ignored.\n - Before this, a replay could be treated as complete and clear failed partitions even though the read-model swap never happened (#4243)\n- Failed partitions from the final MongoDB bulk flush of a catch-up or replay are now recorded as failed partitions. Previously they were discarded and the catch-up reported success (#4257)\n\n## Changed\n\n- Test fixtures wait for PostgreSQL's final server instead of the temporary initialization server. They pin SQL Server to `2022-CU20-ubuntu-22.04` with 3 GB of memory, retry a SQL Server container that exits during startup once, and reserve the kernel ports from the ephemeral range where the container runtime allows it. This removes CI flakes (#4174)\n\n\n## Fixed (CI regressions on SQL storage, found while releasing this batch)\n\n- Projection replays on SQL storage (SQLite, PostgreSQL, SQL Server) no longer fail. This covers replays after a projection definition change and every replay after the first. The replay context's generation column was missing, the replayed-read-model table was mapped under the wrong name (the table is now keyed by observer and replay start), and replayed tables kept their temporary primary-key names after the swap. #4243 exposed these long-standing SQL bugs by no longer ignoring replay start and finalization errors.\n- Primary-key names on PostgreSQL and SQL Server are now distinct within the identifier limits (a readable prefix plus a hash). Replay starts are stored at microsecond precision, and recording a replay twice is idempotent.\n\nKnown limitation, pre-existing and tracked in #4340: containers whose names reach PostgreSQL's 63-byte table-name limit once suffixed (about 49 bytes or more) can still collide on backup and shadow table names.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.19.0", + "date": "2026-09-28", + "rules": [], + "body": "## Changed\n\n- The Elixir contracts package (`cratis_chronicle_contracts`) now requires `grpc ~> 1.0`, `mint ~> 1.11` and Elixir 1.18 or later. The generated contracts API is unchanged. If you use the contracts directly (#4214):\n - `GRPC.Stub.connect/2` still defaults to the Gun adapter, which is optional in grpc 1.x. Pass `adapter: GRPC.Client.Adapters.Mint` or add `:gun` to your dependencies.\n - Remove any `{GRPC.Client.Supervisor, []}` child; grpc 1.x starts its own client supervisor.\n - Server modules now live in the separate `grpc_server` package.\n- The `cratis_chronicle` client keeps resolving an earlier contracts release until its own grpc 1.x release. See the [grpc changelog](https://github.com/elixir-grpc/grpc/blob/v1.0.5/grpc/CHANGELOG.md#v100-2026-06-15).\n\n## Security\n\n- Moves past the grpc 0.x advisories [CVE-2026-48599](https://github.com/advisories/GHSA-mwr4-5g34-j5cq), [CVE-2026-48853](https://github.com/advisories/GHSA-grp7-v8xh-rj7h), [CVE-2026-48854](https://github.com/advisories/GHSA-q8gf-9rvj-gmgj) and [CVE-2026-53430](https://github.com/advisories/GHSA-6ccx-9c9f-327w), all patched in grpc 1.0.0, and requires Mint 1.11, which fixes three advisories in Mint 1.10. (#4214)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.18.0", + "date": "2026-09-28", + "rules": [], + "body": "## Added\n\n- **Cleartext HTTP/2 behind a proxy.** `Tls.Enabled=false` is now honored as an explicit opt-in: the kernel serves cleartext HTTP/2 (h2c) only, behind an HTTPS-terminating reverse proxy. TLS stays enabled by default. With TLS enabled, a missing or invalid certificate still stops startup and never falls back to cleartext. A dedicated health port with TLS still requires a certificate. Authenticated h2c requires an explicit HTTPS external authority, because Chronicle's internal `/connect/token` is not served over cleartext. (#3681)\n- **Trusted proxies setting.** New options `forwardedHeaders.knownProxies` and `forwardedHeaders.knownNetworks` list the reverse proxies whose `X-Forwarded-*` headers the kernel trusts. This also holds when `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true` is set. (#3681)\n\n## Changed\n\n- **Forwarded headers are trusted only from loopback or configured proxies.** Before, they were trusted from any caller, which let anyone spoof the client IP and scheme. Existing TLS deployments behind a proxy keep working; the only difference is that `RemoteIpAddress` becomes the proxy's address until the proxy is configured. h2c deployments, and a shared cleartext health port reached through a proxy, need the proxy configured before upgrading. (#3681)\n- **Ignored identity-provider certificate.** `identityProvider.certificate` has never been applied. The kernel now logs a startup warning when it is set, and the documentation explains that the top-level `tls` certificate serves `/connect/token`. The setting will be rejected in the next major. (#4204)\n- SDKs can't obtain tokens from an external authority yet, so SDK ClientCredentials mode doesn't work with authenticated h2c. (see #4350)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.17.0", + "date": "2026-09-28", + "rules": [ + "Heading not allowed", + "Issue reference position" + ], + "body": "## Fixed\n\n- `WaitForCompletion()` no longer times out on observers whose tag, event source type, or event stream type filters exclude the appended events. Matching events in the same batch still must be handled before the wait completes. (#4198)\n\n\n## Also in this PR (after review)\n\n- Failed partitions are reported only for observers whose filters match the appended events, including observers that are already caught up.\n- Cached subscription matches are refreshed when an observer unsubscribes or changes its filters or event types.\n- The .NET client now says explicitly when it sends the first sequence number (additive proto field 8), so a batch that starts at sequence 0 is handled correctly. Legacy clients, invalid ranges and ranges over 10,000 events fall back to waiting through the target.\n- An explicit `Default` event source type filters the same way in the append queue, in handling and in the wait.\n- Observers that are caught up and have no failed partitions no longer trigger subscription lookups on each poll.\n\nFilter-aware waits apply only to clients that send the first sequence number. Today that is the .NET client; the documentation says so.\n\nKnown gaps are tracked separately: catch-up/replay reads ignore tag, source-type and stream-type filters, and batch-level filtering in `Observer.Handling` (#4329).\n" + }, + { + "repo": "Chronicle", + "tag": "v19.16.1", + "date": "2026-09-28", + "rules": [], + "body": "## Fixed\n\n- On a kernel with more than one silo, on-demand (passive) read models no longer start returning nothing for every instance. Once a read model with children had been projected on one silo and cached on another, the next event for an existing child threw. The projection then returned no read model for any instance, and reported nothing, until the kernel restarted or the projection was registered again. Objects now keep their shape when sent between silos (Cratis.Orleans 1.12.2), and a projection that fails once no longer stays broken for every instance read after it. (#4337)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.16.0", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "Closes #4258.\n\n## Added\n\n- `ChronicleOptions.UnitOfWorkLifecyclePolicy`, which controls what happens when events are staged on a unit of work after its completion has begun (commit started, commit failed, rolled back or disposed). The default is `Compatibility`; `Strict` is opt-in and will become the default in the next major. (#4258)\n - **Strict:** `AddEvent`/`AddEvents` throw `UnitOfWorkIsCompleted` before the input is read, so staged events are unchanged. `Rollback()` during an in-flight commit throws `UnitOfWorkIsCompleting`.\n - **Compatibility:** keeps the existing behavior, but every late staging attempt now logs an error with the correlation id and no event content. It is no longer silent.\n\n## Fixed\n\n- Starting a commit, rolling back and staging now share one lock, and the commit batch is captured before late events can enter it. Previously an event added during a commit could be neither committed nor rejected. (#4258)\n- `Dispose()` during an in-flight commit no longer rolls back or throws. The in-flight commit completes, and an exception thrown by the request is no longer hidden by one from `Dispose` in the ASP.NET Core middleware. (#4258)\n- The in-memory testing event store applies the configured lifecycle policy and logger. (#4258)\n\nThe public API is additive: existing constructors and custom `IUnitOfWork` implementations keep working. The behavior is documented in `events/transactions.mdx`, the decision-read guidance and the options reference.\n\nChecks:\n- Release builds with 0 warnings.\n- DotNET.Specs (3,540), AspNetCore.Specs (63) and Testing.Specs (670) pass.\n- The local CI-equivalent gate is green.\n- The cross-provider review passed after two rounds.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.15.0", + "date": "2026-09-28", + "rules": [ + "Heading not allowed" + ], + "body": "## Added\n\n- Structured named tags: an event can carry `NamedTag` values, each a `TagName` and a string value, next to its existing plain tags. Append them with `AppendWithNamedTags`, `AppendManyWithNamedTags` and `AddEventWithNamedTags` on `IEventSequence`, `ITransactionalEventSequence` and `IUnitOfWork`; they are available on `EventContext.NamedTags` (#3953)\n- Query events and sequence histograms by named tags through the kernel gRPC API (`IEventSequences.QueryEventsWithNamedTags` and `SequenceHistogramWithNamedTags`). Criteria are combined with OR across criteria and AND with the other query dimensions; each criterion matches a name and value on the same tag, compares ordinally, and treats a missing value list as \"any value\" (#3953)\n- Named tags are stored and indexed on MongoDB, SQL (SQLite, PostgreSQL and SQL Server) and in-memory storage. An exact duplicate name and value pair on one event is kept once. Named-tag batch appends keep per-event causation and migrated generations (#3953)\n\n## Upgrading\n\n- Upgrade servers before clients: a new client appending named tags to an older server gets `UNIMPLEMENTED` instead of silently losing the tags.\n- SQL storage reserves the per-namespace table `__cratis_named_tags`. If a table with that name already exists, event-sequence operations in that namespace fail with `NamedTagsTableCollision` until it is renamed.\n- Tag names and values are stored in plaintext and indexed. Never put personal data in tags.\n- The Workbench HTTP proxies for the named-tag queries are generated but cannot carry structured criteria yet; use the gRPC API (#4277).\n\n" + }, + { + "repo": "Chronicle", + "tag": "v19.14.2", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "Closes #4310. Closes #4121.\n\n## Fixed\n\n- A reactor, reducer, read-model reactor or event type that carries `[EventLog]`/`[EventSequence]` is no longer also registered as an empty model-bound projection. Before this, a reactor pinned to an event sequence stopped receiving events. Model-bound read models that use those attributes, including record structs, are still discovered. (#4310)\n- Synchronous reactor handlers can return arrays and lists of events (`SomeEvent[]`, `List`, `IReadOnlyList`, any single-dimensional array or `IEnumerable` of known events, `EventForEventSourceId` or `object`). Discovery and invocation agree on these return types, and the CHR0054 analyzer no longer reports them as unsupported. Multidimensional arrays are rejected. (#4121)\n\nThis restores the array/list part of #4152, which the consolidation reverted in `9b35fdd8f`.\n\nChecks:\n- Release builds of the client and analyzer projects and their specs with 0 warnings.\n- `DotNET.Specs` 3,350/3,350 and `DotNET.CodeAnalysis.Specs` 415/415.\n- `Documentation/verify-markdown.sh`.\n- The local CI-equivalent gate is green.\n- A cross-provider review passed with no remaining findings after two rounds.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.14.1", + "date": "2026-09-28", + "rules": [ + "Review, verification or provenance note" + ], + "body": "## Changed\n\n- The kernel's observers now use the generic state machine from `Cratis.Orleans.StateMachines` (Cratis.Orleans 1.11.0), and Chronicle's own copy is removed. Runtime behavior and persisted observer state are unchanged. The transition trace log category is now `StateMachine` (#4289)\n- Chronicle now builds against Cratis.Orleans 1.11.0, Arc 22.30.0 and Fundamentals 7.19.6 — the minimum versions Cratis.Orleans 1.11.0 requires. The Arc proxy-generator build package stays at 22.23.0 because 22.30.0 crashes on Core\n\nValidation: full solution Debug build and all kernel projects in Release build with zero warnings. All spec projects pass, including Core.Specs (4,119) and Storage.MongoDB.Specs (882). The 48 generic state-machine specs now live in Cratis.Orleans.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.14.0", + "date": "2026-09-28", + "rules": [ + "Issue reference position", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- `IProjections.Register(define, id)` registers a read model together with a declarative projection defined through the same `IProjectionBuilderFor` an `IProjectionFor` uses, without assembly discovery. The read model behaves like any other through `IReadModels`, including passive (on-demand) read models (#4287)\n- `IProjections.Register()` registers the model-bound projection of a read model that discovery did not find (#4287)\n- `IReactors.Define(id, reactor => ...)` and `IReactors.Register(id, reactor => ...)` declare reactors fluently with typed `On` handlers — `(TEvent)`, `(TEvent, EventContext)` and their `Task`-returning forms — and a catch-all `Subscribe((object, EventContext) => ...)`. `IReactorDefinition` exposes the reactor id, event types and whether it subscribes to all events (#4288)\n- `ChronicleOptions.ExplicitArtifacts` registers explicit projections, read models and fluent reactors before the client connects; they are included in every registration pass, including after a reconnect (#4287, #4288)\n\nExplicitly registered projections and fluent reactors use the same observer, projection and reactor infrastructure as discovered ones — state, failed partitions and replay all behave the same. A catch-all `Subscribe` expands to every event type the client knows when the reactor is defined.\n\nValidation: 3,327 client specs pass; 8 new integration specs against the real kernel pass (active and passive explicit read models, typed and catch-all fluent reactors). This is the Chronicle foundation for `EventSourcedStateMachine` in Cratis/Orleans#34.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.13.3", + "date": "2026-09-28", + "rules": [], + "body": "## Fixed\n\n- A replay of a projection no longer leaves its MongoDB read model frozen when the replay ends on more than one kernel silo at once, or overlaps another replay of the same projection. The replay is promoted exactly once, and every silo goes back to writing the read model itself. Before, a silo that lost the race kept sending every live change to `replay-`, with the observer still reporting `Active` and no error recorded (#4296)\n- A replay no longer fails to end because the revert container name from a previous replay of the same read model is reused (#4296)\n- Starting a replay on several silos at once no longer fails with a duplicate key on `replay-contexts` (#4296)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.13.2", + "date": "2026-09-27", + "rules": [ + "Closing or linking keyword" + ], + "body": "## Changed\n\n- Document that a webhook's non-2xx response fails delivery: transient failures are first retried by the HTTP client, then the partition follows the observer retry settings and is quarantined when attempts are exhausted.\n- Document that an event source can re-claim its own unique value, and that changing the value frees the old one.\n\n## Fixed\n\n- The `MissingImmediateProjectionForModel` exception no longer tells you to implement an interface that does not exist.\n\nCloses #4290\nCloses #4267\nCloses #4203\n" + }, + { + "repo": "Chronicle", + "tag": "v19.13.1", + "date": "2026-09-27", + "rules": [ + "Closing or linking keyword", + "Heading not allowed" + ], + "body": "## Fixed\n\n- `WaitForCompletion()` no longer fails with an `InvalidOperationException` when the append result carries event-type tails. The request's repeated `EventTypeTails` field defaulted to an array, which protobuf-net cannot add to while decoding, so every non-empty request failed on the server (#4280)\n- A `WaitForCompletion()` timeout response that lists outstanding observers now decodes on the client, for the same reason (#4280)\n\n## Compatibility\n\nOnly the default values changed; field numbers and property types are unchanged, so the wire schema does not move. Both sides need the fix: an unfixed 19.11.0–19.13.0 server still rejects non-empty tails from a fixed client, and an unfixed client still cannot decode a timeout response that names outstanding observers.\n\n## Verification\n\n- Round-trip specifications under `Server.Specs/Contracts/` for non-empty tails, omitted tails and non-empty outstanding observers. The two non-empty cases reproduce the exception without the fix.\n- All Server specs pass; Release solution build with warnings as errors and formatting pass.\n- A search of every protobuf-net contract found no other `IEnumerable` member with an array default.\n\nCloses #4280\n" + }, + { + "repo": "Chronicle", + "tag": "v19.13.0", + "date": "2026-09-27", + "rules": [ + "Closing or linking keyword", + "Heading not allowed" + ], + "body": "## Added\n- `IUnitOfWork.HasEnrolledDecisionReads` reports whether a protected decision read was successfully enrolled, including after commit, rollback or disposal. Command owners can use it to refuse an unguarded returned-event fallback. Existing custom units of work default to `false`; no decision tokens or sequence numbers are exposed.\n\n## Verification\n- Release solution build (zero warnings), all non-integration specifications, client packaging, formatting and markdown validation passed.\n- Local MongoDB integration could not start: Docker Desktop LinuxKit kernel 7.0.12 is incompatible with the MongoDB image. A bounded reproduction failed before test assertions; integration CI on GitHub must pass before merge.\n\nCloses #4278\nRefs Cratis/Arc#2828" + }, + { + "repo": "Chronicle", + "tag": "v19.12.0", + "date": "2026-09-27", + "rules": [], + "body": "This PR consolidates six fixes into one release. Each was reviewed separately, then tested together on top of v19.11.0.\n\n## Added\n\n- The .NET client's generated JSON schemas give inline object types a `title` with the type name. A declaration generated from a fluent composite-key projection (for example in the Workbench) can then name the key type and recompile. The fluent `CompositeKeyBuilder` still emits untyped expressions, so older kernels are unaffected (#4253)\n- The ASP.NET Core causation middleware records the matched route template in a new `routeTemplate` causation property, alongside the existing request path (#4260)\n\n## Changed\n\n- Pattern mining derives request features from route templates only, never from raw request paths, so identifiers embedded in a URL (such as `/customers/jane@example.com`) are no longer stored in mined patterns. Patterns stored before this release are not rewritten (#4260)\n- Registering an event type whose stored schema differs only in titles no longer rewrites the stored schema, and no longer invalidates caches on every reconnect. This matters when old and new clients run side by side (#4253)\n\n## Fixed\n\n- A MongoDB bulk write that fails part way now applies the remaining operations instead of silently dropping them. Later changes for the partition that failed are held back, so they can't overtake its replay. Each failed partition is reported once, at its earliest sequence number (#4244)\n- Forwarding events to another event store's inbox no longer acknowledges events whose append failed. It stops at the first failure and reports the last event it actually forwarded, so the retry resumes from the failed event (#4259)\n- Projection keys for immediate (passive) projections round-trip read-model keys that contain `#` (#4241)\n- `add` and `subtract` projection mappings accept negative, decimal and exponent operands (for example `-1`, `-2.5`, `1e-3`), and reject malformed operands. Exponent literals and integer literals beyond 2^53 in declarations still depend on the declaration parser (Cratis/Screenplay#285) (#4239)\n- Composite-key validation for generated declarations resolves the key type from the read model's `Id` schema first, so a same-named type elsewhere in the read model can't be picked by mistake (#4253)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.11.1", + "date": "2026-09-27", + "rules": [], + "body": "## Fixed\n\n- The local Chronicle image starts its embedded MongoDB only when configured to use it; connections to a separate MongoDB or another storage provider no longer start an unused replica set (#4111).\n- Generating a declarative projection preserves nested mappings, including inner nested blocks and `clear with` rules. Nested composite keys use the nested read model's schema while generating their declarations (#4116).\n" + }, + { + "repo": "Chronicle", + "tag": "v19.11.0", + "date": "2026-09-26", + "rules": [], + "body": "## Summary\n\nThis PR consolidates the open PRs listed at the end, plus several fixes that had no PR of their own, into one release. The main themes are reactor side-effect reliability, projection and read-model correctness, batch-append fidelity, and erasing more data when an event is redacted. The CI change in #4237 was merged separately and is not part of this PR.\n\n## Added\n\n- Register a .NET reactor at runtime by stable ID, explicit event types and generations, and an awaited delegate that receives the event context, the JSON content and the generational content map. Unregister it by ID; the observer keeps its stored position. Tags and filters are not yet configurable on delegate reactors (#4196)\n- Reactor side-effect handlers can claim synchronous return types with `CanHandleReturnType(Type)`, including a declared `IEnumerable`. Existing handlers stay compatible (#4121)\n- `IReactors.RetryFailedPartitionFor(partition)` retries a reactor's failed partition from the .NET client and returns a `ReactorPartitionRetryOutcome` (#3909)\n- Batch event payloads accept an optional per-event `causation` field. Older kernels ignore it and store only the ambient chain (#4179)\n- Append results expose the event types the operation wrote, including for batch appends (#4132)\n\n## Changed\n\n- A reactor whose return value no side-effect handler claims now fails its partition, naming the return type, instead of logging a warning and acknowledging the event. Fix the handler, then retry the partition (#4156)\n- Duplicate reactor IDs, across discovered classes and runtime delegates, are rejected before existing subscriptions disconnect, and the error names both types. A duplicate `Register()` now throws `ReactorAlreadyRegistered` instead of `ArgumentException` (#4196)\n- A `WaitForCompletion()` timeout returns a result with `TimedOut` set instead of throwing a gRPC cancellation. Supported servers also report which observers are still behind (#4132)\n- Read-model instance pages from MongoDB, SQL and in-memory sinks are ordered by key, so reading a page again returns the same instances while membership is unchanged (#3741)\n- A dynamic dictionary key that uses an unknown `$` expression fails with `UnsupportedDynamicPropertyPathExpression` instead of being written as a literal key (#4119)\n- SQL redaction content describes the original event (type, occurrence time, correlation id, caused-by identities and causation types) instead of the redaction itself, matching the other providers (#4145)\n- `Cratis.Chronicle.Testing` depends on `Cratis.Arc` and `Cratis.Arc.Core` 22.19.1 or later, which brings in the ASP.NET Core shared framework. Projects pinned to an older Arc must upgrade (#4158)\n- Aspire uses the development Chronicle image in run mode and the production image in publish mode. A configuration callback no longer implies the production image; `WithImageTag` still overrides (#3686)\n\n## Fixed\n\n- Observer tail lookups and in-process reactor scenarios recognize claimed synchronous reactor return types. Before, such a reactor could silently never fire for that event (#4121)\n- Reactors returning events complete in standalone hosts configured with `AddCratisChronicle`, and loaded but unconfigured integrations no longer block return-value processing. A configuration failure can no longer cause duplicate appends on retry (#4107)\n- A failed catch-up job start now quarantines the observer. Before, a returned failure was ignored and the recovery work was lost (#3777)\n- A successful replay clears the failed-partition records it covered, so failed-partition monitoring returns to healthy. Failures the replay did not cover, and failures recorded after it started, stay recorded (#4148)\n- `$causedBy`, `$causedBy(subject)`, `$causedBy(name)` and `$causedBy(userName)` resolve at runtime in projection values and dynamic dictionary keys. Before, they compiled but failed when the projection ran (#4119)\n- A first-level child event that arrives before its root now resolves when the root is created during live processing, giving the same state as a replay (#4182)\n- `AppendMany` stores every migrated generation of each event, so consumers of newer and older generations receive the correct content (#4190)\n- `AppendMany` and unit-of-work commits keep each event's explicit cause after the ambient chain without carrying it into other events (#4179)\n- With the .NET client and an updated server, `WaitForCompletion()` waits only for observers affected by the appended event types. Observers subscribed to all events are still included. On older servers, mixed-type batches can still time out (#4132)\n- `Materialized.ObserveInstances()` on PostgreSQL and SQLite delivers the initial page and later updates instead of ending with an opaque error. Several writes between polls can arrive as one update, and failures name the sink, read model and container (#4120)\n- SQL storage updates the stored content hash when an event is revised. Before, the original hash remained (#3820)\n- CHR0001 no longer treats tags or concurrency-scope arguments as event types, now reports unmarked events passed by name or in an array to `AppendMany`, and its code fix works for named arguments (#4144)\n- CHR0028 reports only `.AutoMap()` calls that are certainly redundant, and no longer flags a call that re-enables AutoMap after `.NoAutoMap()` or on a `[NoAutoMap]` read model (#4205)\n- Projects that reference only `Cratis.Chronicle.Testing` can run event, read-model and reactor scenarios without adding Arc packages themselves (#4158)\n- In-process testing scenarios return the appended generation's content and generational content for events with migrations (#4190)\n- `ReadModelScenario` retries a child whose parent arrives later instead of dropping it. A parent that never arrives raises the same descriptive error as an unresolved root key (#3700)\n\n## Security\n\n- Redacting an event no longer keeps the original causation property values, which can carry command input such as personal data. MongoDB, SQL and in-memory storage store the same redaction content, without property values (#4145)\n- Redaction removes the event's revision history and stored content hashes. Before, a revised event that was later redacted kept its revisions, and MongoDB served the revised payload instead of the redaction marker. Redaction does not reach backups, oplogs or events already forwarded to other event stores (#4145)\n- The Workbench's `js-yaml` dependency is updated to 3.15.2, which limits CPU usage from merge sequences (#4012)\n\nAbsorbed pull requests: #4191, #4194, #4185, #4233, #4187, #4188, #4210, #4218, #4217, #4193, #4215, #4232, #4229, #4234, #4235, #4012.\n\nNot included, because their owners ship them separately: #4023, #4157, #4152, #4111 and #4146 (in #4250), #4159 (catch-up, held for compatibility work), and #4166 (in #4247). #4162 and #4163 shipped separately in #4242 and #4245.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.10.1", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- A projection can clear a nested object and later set it again, including an object nested inside another nested object. The MongoDB sink no longer fails the projection, and the testing engine no longer throws. (#4166)\n- Clearing a nested object in MongoDB removes the field with `$unset` instead of storing `null`. A document already stored with a `null` parent from an earlier version is repaired before the nested value is written again. The same applies to a nested object inside an array element. The repair only touches that document's parent, or that one element, and only when it really holds `null`.\n- The in-memory sink and the testing engine clear the right property for deeper paths such as `outer.info`. Clearing an object whose parent is missing no longer creates that parent, which matches MongoDB.\n\n" + }, + { + "repo": "Chronicle", + "tag": "v19.10.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- A join in a projection declaration can set its own `automap` or `no automap`, which overrides the projection's setting. The setting is stored with the definition, and applies to live join events and to backfill. Older clients and stored definitions keep inheriting the projection's setting. The fluent .NET `JoinBuilder` and generated client code don't express it yet.\n- Joins inside a `nested` object are wired: joined values are set on the nested object, both when the join event arrives and through backfill.\n\n## Changed\n\nThe declaration language now rejects shapes that were silently ignored, overwritten or failing, instead of compiling them. Stored definitions still load; this only affects compiling or saving a declaration.\n\n- `all` inside `children` or `nested`. Use `all` at the root with a dotted path instead.\n- `remove via join` at the root or inside `nested`.\n- `children` inside `nested`.\n- `join` inside a `nested` object beneath `children`.\n- More than one `every`/`all` block at the same level. Two `every` blocks inside `children` used to be merged; they are now an error.\n- The same event more than once at one level, including across join blocks, repeated `clear with`, and `remove with` plus `clear with` for the same event. These checks now also run when no schema is supplied.\n\nFluent and model-bound definitions that use the unsupported root `remove via join`, or a join inside a nested object beneath children, now log a warning and leave out that wiring, instead of failing the projection's partition.\n\nA nested object inside another nested object now automaps against its own schema instead of the parent's, so values projected for new events can differ.\n\n## Fixed\n\n- `every` mappings, such as `count`, `increment` and `add`, ran twice for join events. They now run once. **Existing read models keep their doubled values until you replay the affected projections.** Their definitions don't change, so no replay is triggered automatically.\n\nPart of #4125. Follow-ups cover live joins in nested objects beneath children, root removal via join, and letting child events update parent `every` fields as an explicit opt-in.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.9.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- Decision reads: `IDecisionReads.Get(key)` returns a read model instance (or its absence) with an opaque guard, and the unit of work's commit is rejected if an event that could change what was read has been appended since. Developers never handle sequence numbers. Admitted read models are projections keyed on the event source without joins, children, custom keys or `all`; everything else is refused with a typed reason. Text keys and canonical (lowercase) GUID keys are admitted. `Chronicle.Testing` supports decision reads in-process. This is the client part of the design for #4138.\n- A unit of work that holds a decision read is completed only by its owner, so application code cannot commit it early and bypass the guard. The ASP.NET Core unit-of-work middleware owns its request's unit of work.\n- `GetDecisionConflicts` explains a rejected guarded append without exposing sequence numbers.\n\n## Changed\n\n- An append with no events but at least one concurrency scope that will actually be checked is now accepted and only validates those scopes, so a command that decides to write nothing can still confirm its decision was current. Empty appends without such a scope are still rejected, and empty appends no longer notify append subscribers.\n- Requires Chronicle kernel 19.0.0 or later for decision reads. With plain ASP.NET Core the middleware commits after the response is written, so a caller that needs a conflict-aware response must commit before writing it.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.8.1", + "date": "2026-09-26", + "rules": [], + "body": "## Changed\n\n- Generating a declaration from a stored, untyped composite-key expression takes the key type from the read model's `Id` schema (including nullable types), or from the child item's `Id` inside a children block. When the schema has no composite type it falls back to `CompositeKey`, and in that case the generated declaration only recompiles if the schema defines that type.\n\nFluent .NET projections still emit untyped composite-key expressions, so a newer client can keep targeting an older kernel. Declarations that name a composite-key type need a kernel that includes this change.\n\n## Fixed\n\n- Composite-key expressions in projection declarations resolve on the kernel. The kernel, the declaration-language generator and the declarative code generator now share one `$composite(...)` parser. It accepts an optional leading key type name, and spacing between components doesn't matter. This is part of #4163.\n- Malformed composite-key expressions fail with a clear error instead of resolving to a wrong key. That covers an invalid type name, duplicate or empty components, and whitespace-only content.\n- Composite keys that contain characters such as `-`, `@` or `:` in their mappings are now resolved as composite keys. Previously they fell through to value resolution, which could put every event into one read model instance. **If a projection used such a key, replay it after upgrading.** New events go to the correct instances, but the old instance stays until you replay.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.8.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- Testing scenarios accept per-run artifacts. Pass `new Defaults(clientArtifactsProvider, serviceProvider)` to `EventScenario` or `ReadModelScenario`, so each test run discovers only the artifacts you give it, not the assembly-wide set.\n- `EventScenario(Defaults, ICanProvideConstraints?)` discovers constraints from the supplied defaults when no constraint provider is passed.\n- `ReadModelScenario.WithProjection(Action>)` defines a projection inline for a single scenario. It takes precedence over discovered projections and reducers, and it does not leak into other runs.\n\n## Fixed\n\n- `ReadModelScenario` seeds and reads read models through the artifacts provider of its own `Defaults`, instead of always using the process-wide provider.\n\n`ReactorScenario` still uses the process-wide defaults. It is left for a follow-up because #4210 changes the same file. This is part of #4162.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.11", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- The kernel no longer registers its MongoDB camel-case convention pack twice (#3589)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.10", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- A projection declaration containing a block the engine cannot build, such as `variant`, now fails compilation with a compiler error that points at the block. Before this fix, compilation threw an exception. This also applies when compiling without read model or event schemas and to blocks inside `children` and `nested` (#4109)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.9", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- Watching a read model before the kernel has finished registering it now waits for registration instead of throwing `NullReferenceException`. If it is never registered, the watch fails with `ReadModelNotFound` (#3925)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.8", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- The .NET client now loads a passwordless PKCS#12 client certificate, including its private key, for mutual TLS on .NET 9 and later. Before, it read the file as a plain certificate and lost the key (#3680)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.7", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- Webhook deliveries that get a non-success HTTP response (4xx or 5xx) now fail the partition and go through the normal failed-partition retry, instead of counting as delivered (#4200)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.6", + "date": "2026-09-26", + "rules": [], + "body": "## Security\n\n- The kernel development Compose file (`Source/Kernel/Server/docker-compose.yml`) now publishes MongoDB, PostgreSQL, SQL Server and the Aspire dashboard on `127.0.0.1` only, instead of on all network interfaces. These services use well-known development credentials (#4208)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.5", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- Append notifications raised by the .NET client now carry each event's subject: an explicit subject or one resolved from a `[Subject]` property. Before, they always reported the event source id. This covers single appends, `AppendMany` and batch appends across event sources (#4133)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.4", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- The Chronicle health check now reports the failure status it was registered with (for example `Degraded`) when the kernel is disconnected or unreachable, instead of always reporting `Unhealthy` (#4201)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.3", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- CHR0054 reports a reactor method whose return type Chronicle cannot discover as a handler, with a message about the return type (#4206)\n\n## Fixed\n\n- The reactor analyzer no longer reports a synchronous `EventsWithConcurrencyScopes` return, which the runtime supports. It also no longer reports unsupported return types as CHR0004, a parameter error (#4206)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.2", + "date": "2026-09-26", + "rules": [], + "body": "Three projection engine fixes that bring the .NET client, the testing engine and the in-memory sink in line with the declaration language and the MongoDB sink.\n\n## Changed\n\n- Fluent `.FromAll()` and model-bound `[FromAll]` projections now subscribe to every event type in the event sequence, as a declaration `all` block already does. **Existing projections that use `FromAll` will be redefined, and will start creating or updating read-model instances for events from any event source**, keyed by event source id unless another key is given. Use `FromEvery` for mappings that should only apply to the projection's own event types. Combining `FromAll` with `FromEvery` applies both sets of mappings to all event types. Event types already mapped through `From`, a join or a derivative are not processed a second time by the all-events subscription (#4164)\n\n## Fixed\n\n- Literal set-mappings in projection declarations (`status = \"draft\"`, `isActive = true`, `version = 1`, and non-negative numeric `add`/`subtract` operands) now project their values. Before this fix they resolved to null. Stored definitions pick up the literal values without migration, but read models built before the fix keep their null values until they are replayed. An empty or otherwise unsupported value expression now fails when the projection is created instead of silently resolving to null (#4124)\n- Removing a joined child with `RemovedWithJoin` now removes it from every parent in the in-memory sink and in `ReadModelScenario`, matching MongoDB. Before this fix the parent that received the event kept the child (#4167)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.1", + "date": "2026-09-25", + "rules": [], + "body": "## Fixed\n\n- CHR0037 no longer reports valid migration chains where two or more generations written with `EventTypeGenerationFor` belong to the same event type. It still reports generations that resolve to different event types (#4207)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.7.0", + "date": "2026-09-25", + "rules": [ + "Heading not allowed", + "Section heading level" + ], + "body": "### Added\n\n- The event-store dashboard, built from projections with composite keys rather than counting rows on demand, with per-namespace figures and drill-down, and key figures under each event store on the landing page (#3731)\n- A declarative surface for kernel-owned projections - identified with a `$system.` prefix, owned by the kernel rather than a client, and refused by replay, because replaying one rebuilds state the kernel keeps for itself\n- `ProjectionScope`, so a read model can be built per namespace, once for the whole store, or both. A `Both` declaration lowers to two single-scope definitions, and a global one is keyed with the unset namespace\n- A statistics query surface over the new projections, and the Workbench screens that show it\n\n### Changed\n\n- The job system is now `Cratis.Orleans` rather than Chronicle's own. The local engine, its storage for MongoDB and SQL, and its specs are gone; what remains here are the job scenarios that are about Chronicle\n- Job storage is resolved through `IJobsStorage`. The kernel asked for the concrete MongoDB type, which the container satisfied by building a second, unconfigured instance - grains wrote to one database and the kernel read from another, and reading an always-empty database does not throw. Six integration specs asserted job steps were empty and passed for the wrong reason\n- The kernel names its own assemblies for BSON serializer registration. Adopting the package's registration silently dropped every serializer the kernel ships, including the one that upgrades constraint definitions written before 16.12\n- A development wipe tells the job system its database was emptied, so the tables it creates on resolution are made again\n- A replay specification no longer depends on catching a transient job. A replay job is removed the moment it completes, and two events against a fast database finish before the first poll looks; the replay is verified from the rebuilt read model and the absence of a recommendation instead\n\n### Fixed\n\n- Jobs could not be written on PostgreSQL at all: the state column is `jsonb`, which refuses an implicit cast from `text`. Starting a job still reported success because the failure was inside the grain's write, so the kernel believed it had started work that never existed - a failed partition was never retried, and a redaction's replay never ran (fixed in `Cratis.Orleans` 1.10.1)\n- The SQL job storage shipped a context it could not create tables for, and the MongoDB one never registered the conventions its own queries assumed, so writes and reads disagreed on casing (fixed in `Cratis.Orleans` 1.6.1)\n\n### Notes\n\nDepends on `Cratis.Orleans` 1.10.1. Every defect found along the way was fixed in the package rather than worked around here, and each one carries a specification there: SQL storage now runs in a real silo and, when one is available, against a real PostgreSQL." + }, + { + "repo": "Chronicle", + "tag": "v19.6.1", + "date": "2026-09-25", + "rules": [], + "body": "## Fixed\n\n- Restored the Chronicle release pipeline: adding `IObservers Observers` to `IEventStore` as a required member broke the actual publish, because an existing external implementation of `IEventStore` (Arc's `CommandScenario` test harness) was compiled against the interface before this member existed, and does not have it. A type that implements an interface has to implement all of it, or the runtime refuses to load it before any of its own code runs — which is exactly what happened when the release pipeline tried to start Chronicle 19.5.0 beside the currently published Arc packages.\n- `Observers` is now an optional interface member with a default implementation that throws `ObserversNotSupported`, the same shape `IPatterns Patterns`/`PatternsNotSupported` already use on this interface for the identical reason. Every real implementation continues to provide it explicitly and is unaffected.\n\nConfirmed with a packed local build run through `.github/scripts/verify-consumer-smoke.sh` against the actually published Arc packages — the same check that caught the break during the v19.5.0 publish attempt." + }, + { + "repo": "Chronicle", + "tag": "v19.6.0", + "date": "2026-09-25", + "rules": [], + "body": "## Added\n\n- `[NotProjected]` declares a read model member that the projection deliberately does not populate, because whatever reads the model assembles it. A projection may only consume events, never another read model, so a value joined in from a second read model can only be filled in by the query that serves it — and there was previously no way to say so. Declaring it also fences AutoMap off the member, so a subscribed event carrying the same name can no longer write over the assembled value\n\n## Fixed\n\n- CHR0024 no longer reports a `[Nested]` projection object as having no mapping source. The attribute was missing from the analyzer's set of recognized mapping attributes, so every nested object was reported as one that would never be populated (#4175)\n- CHR0024 no longer reports the properties of a `[ChildrenFrom]` child record as unmapped. The events that populate a child are named by its parent, but the child was judged against only its own attributes and so against an empty set of subscribed events (#4176)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.5.0", + "date": "2026-09-25", + "rules": [], + "body": "## Summary\n\nAn observer outlives the code that declared it. Delete a read model and its projection, or remove a reactor, and nothing tells the event store — the observer stops being reported, settles into `Disconnected` and stays there forever, keeping its definition, its state and handled counts in every namespace, its failed partitions and its projection definition. Until now nothing short of editing storage directly got rid of it.\n\nRemoval covers the whole event store, because an observer's definition and its projection definition are store-level records shared by every namespace. It refuses while the observer is running or has a subscribed client in any namespace, and names the namespace that blocked it. There is no force flag. Read models and sink containers are left untouched.\n\n## Added\n\n- Removing an observer and everything the event store keeps for it — its definition, its state and handled counts in every namespace, its failed partitions, its in-flight events and, for a projection, its projection definition. Refuses while the observer is running or still has a subscribed client in any namespace, naming the namespace that blocked it. Read model data is left untouched (#4151)\n- `EventStore.Observers` on the .NET client, for listing the observers of an event store and removing one (#4151)\n- A **Remove** action on the Workbench observers page, with a confirmation that states what removal deletes, what it leaves alone and that it cannot be undone (#4151)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.8", + "date": "2026-09-24", + "rules": [ + "Heading not allowed" + ], + "body": "`Cratis.Chronicle.CodeAnalysis` has been published for as long as it has existed, and delivered to nobody. Every CHR analyzer has been inert in every application that consumes Chronicle.\n\n## Fixed\n\n- `Cratis.Chronicle` now carries `Cratis.Chronicle.CodeAnalysis` as a package dependency, so a consumer actually receives the CHR analyzers. The analyzer project packed itself while setting `DevelopmentDependency`, which NuGet reads as \"never emit me as a dependency\" — so the client's nuspec never mentioned it and a consumer had to know the package existed and reference it by hand. The package identity moves into a package-only project the client can depend on, the same shape `Cratis.Arc.Chronicle` already uses for its ARCCHR analyzers.\n\n## How it was found\n\nAn application had 306 specification facts that asserted nothing: `void`-bodied, discarding the `Task` the assertion throws on. `CHR0039` exists precisely for that shape and had never once run. Planting a deliberately false predicate in one of them confirmed it still passed.\n\nVerified by packing and consuming from a local feed — `CHR0039` fires on the discarded-assertion shape with this change, and does not fire on published `19.4.7`.\n\n## Known follow-up\n\nChronicle does not yet run these analyzers over its own source. `CHR0001` and `CHR0002` ask whether a type carries `[EventType]`, which cannot be answered about the open generic parameters the client's own plumbing is written in — they are questions about application code, asked of the library that defines the attribute. Making the client assembly analyzer-clean is its own piece of work.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.7", + "date": "2026-09-24", + "rules": [], + "body": "## Fixed\n\n- An observer whose stopped catch-up job refused to resume was left believing catch-up was under way — dropping every live event, never advancing, and eventually quarantined for a strand it could not escape\n\n---\n\nA stopped catch-up job is offered back to the observer that owns it, and resuming is allowed to refuse: the job was never prepared, or the observer is no longer subscribed. `IJobsManager.Resume` computed that refusal, logged it, and returned `void` — so `StartOrResumeObserverJobFor` reported the job's id as though something had taken it.\n\nNothing had. A refused job stays `Stopped`: it will not run, will not finalize, and will never report back — and reporting back through `CaughtUp` is what lowers the preparing flag. While the flag is up, `Handle` drops every live event and `Observing` skips its missed-events check, so the observer stops advancing at all. The watchdog then clears the flag and re-routes, catch-up finds the same stopped job, it refuses again, and after five rounds the observer is quarantined.\n\nFound by restoring a production event store locally and reading the kernel's own job records:\n\n```\nCatchUpObserver Stopped x30 <- offered and refused, forever\nCatchUpObserver CompletedWithFailures x31\nCatchUpObserver Failed x3\n```\n\nAgainst 185 observers in that store, 100 were more than 500 events behind the tail and 29 were quarantined, with no failed partitions and nothing surfacing the cause. The affected observers were reactors — issue triage, status propagation, branch naming — all stalled at the same position, so the application's automation had silently stopped.\n\n`Resume` now reports whether the job was taken forward, and a refusal clears the preparation rather than pretending it is under way. A job that fails to **start** is a different situation and keeps its existing retry-then-quarantine handling — the specs covering that path are untouched and still pass.\n\nVerified non-vacuous: the new assertion fails with the check removed.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.6", + "date": "2026-09-23", + "rules": [ + "Review, verification or provenance note" + ], + "body": "## Fixed\n\n- A failed partition is now retried within the retry interval even if the first retry reminder tick is lost. Previously the next attempt could be deferred by the 48-hour reminder period. (#4113)\n- SQL storage (PostgreSQL, SQL Server, SQLite) now loads and refreshes reminders from storage correctly. Range reads follow the Orleans reminder table contract, and reminders stored by earlier versions are rehashed on startup. (#4113)\n- The SQL reminder table now returns the stored ETag when a reminder is registered, and returns nothing instead of failing when a reminder does not exist. (#4113)\n\nVerification: the `for_Observer` specs in Kernel `Core.Specs` and the new `for_ReminderTable` specs in `Storage.Sql.Specs` pass, and the touched projects build in Release with no warnings. The new specs fail against the previous code. The integration suite was not run locally.\n\n\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.5", + "date": "2026-09-23", + "rules": [], + "body": "## Added\n\n- `CHRONICLE_MONGODB_IMAGE` selects the MongoDB image the integration fixtures start. Current MongoDB refuses to start on some host kernels, and the refusal surfaces as a container start timeout that explains nothing, leaving integration scenarios unrunnable on that machine.\n\n## Fixed\n\n- A projection whose children use a constant parent key now applies every event to them, not just the ones that create them. Previously, when the root carried event types of its own, later events for an existing child were dropped: a fixed-key document counting things kept an accurate root total while updates and removals to its children went nowhere, so a count of what was still open kept counting things that had closed. Creations still landed, so the document looked healthy. (#4105)\n- A projection can now combine a root-level constant key with constant-parent-key children in one read model. It previously failed to resolve the child's parent, forcing the total and the membership set into separate read models. (#4099)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.4", + "date": "2026-09-23", + "rules": [], + "body": "## Summary\n\nA `ReadModelScenario` initial state is now converted through the same schema-aware JSON path as seeded events, so a projection-backed read model can be seeded with nonempty `JsonObject` values. Seeded scalars, concepts and dictionaries keep their values, and reducers still receive the typed initial state.\n\nA possible follow-up is teaching the shared `ExpandoObjectExtensions.AsExpandoObject` to handle `JsonNode` values explicitly, so no other caller can recurse through `JsonNode.Parent`. That is shared infrastructure and is left out of this change.\n\n## Fixed\n\n- Seeding a projection-backed `ReadModelScenario` with an initial state containing a nonempty `JsonObject` no longer crashes the test host with a stack overflow (#3993)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.3", + "date": "2026-09-23", + "rules": [], + "body": "## Fixed\n\n- An observer could be left permanently believing it was preparing catch-up — dropping every live event, never advancing, and eventually quarantined — when its catch-up job concluded without having prepared steps of its own\n\n---\n\n`CatchUp` raises a preparing flag before asking for a catch-up job, and only `RegisterCatchingUpPartitions` lowered it again. That is reached exclusively when a **brand-new** job prepares its steps. Three other outcomes conclude catch-up without ever going through `PrepareSteps`:\n\n- a job that was already running,\n- a stopped job that was merely resumed,\n- a job found with every step already completed, which is finalized rather than resumed.\n\nAll three call back through `CaughtUp`, and `CaughtUp` did not lower the flag. A raised flag makes `Handle` drop every live event and makes `Observing` skip its missed-events check, so the observer never observes anything again — and being kept alive, it is never reactivated out of it.\n\nThe watchdog's stranded-preparation rescue then clears the flag and re-routes, catch-up concludes exactly the same way on the next tick, and after five consecutive attempts the observer is quarantined for a strand it could not escape.\n\nObserved in a production store on 19.4.1 — which already carries the rescue and revive fixes, so this is the cause upstream of them. Over 20 minutes on one silo:\n\n```\n 259 Job was left running with all of its steps already completed - finalizing it instead of resuming\n 101 Watchdog detected that the observer is preparing catch-up with no catch-up job to finish the preparation\n 16 Observer has failed to recover from a stranded catch-up preparation 6 times in a row (max 5). Quarantining\n```\n\n100 of 185 observers were more than 500 events behind the tail, 28 quarantined, and the worst frozen ~35,000 events back. Read models served stale state indefinitely with no failed partitions and nothing to indicate a problem.\n\n`CaughtUp` now lowers the flag and resets the consecutive-strand counter, since catch-up genuinely concluded.\n\nVerified non-vacuous: the new assertion fails with the two lines removed.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.2", + "date": "2026-09-23", + "rules": [], + "body": "## Changed\n\n- `SecurityJsonSchemaExtensions` gains `HasSubjectIndependentSecurityMetadata`, built on a new predicate-based recursive schema walk in `SchemaMetadataExtensions`, so `Release` can tell a namespace-/global-scoped value apart from a subject-scoped (`[PII]`, or `[Encrypted]` with the default `EncryptionScope.Subject`) one before deciding whether a missing subject means \"nothing to release\"\n\n## Fixed\n\n- `IReadModels.Release` no longer skips a namespace- or global-scoped `[Encrypted]` value on a read model that has no resolvable subject (no `[Subject]` property and no `Id` property) — that scope's key does not depend on a subject at all, and a read model carrying only that kind of value ordinarily has no subject-identifying property in the first place (Cratis/Chronicle#4095)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.4.1", + "date": "2026-09-23", + "rules": [ + "Issue reference position", + "Section heading level" + ], + "body": "### Fixed\n\n- A kernel with a large artifact set could no longer start. Registering projections awaited a reconciliation pass whose cost scales with projections × namespaces, inside the caller's response timeout, so every registration timed out and the silo never became ready. Registration now schedules that pass instead of awaiting it. (#4088)\n\n---\n\nBounding concurrent observer subscriptions (#4058) was correct — it stops a real cluster-wide recovery storm — but it changed the resubscribe fan-out from \"fast because unbounded\" into work of unbounded *duration*. `Register` awaited that work on every call, including the unchanged-definitions fast path.\n\nThe activation-time timer had already been moved off the activation turn for exactly this reason. That left `Register` as the remaining path: whichever of the two won the in-flight guard performed the whole fan-out, and when `Register` won it, the call exceeded the response timeout. Raising the timeout only moves the threshold, since the pass has no bound a fixed timeout can rely on.\n\nRegistration still subscribes the definitions it actually changed, so events seeded immediately after it are not missed. Repairing projections left dark by an earlier activation is background work whose failures are logged — which is how the timer-driven pass already treated them.\n\nThe spec asserts both halves of the contract: `Register` subscribes nothing itself, and the projection is still subscribed once the scheduled pass runs. Verified non-vacuous — `should_not_subscribe_while_registering` fails when the inline `await` is restored." + }, + { + "repo": "Chronicle", + "tag": "v19.4.0", + "date": "2026-09-23", + "rules": [], + "body": "## Added\n\n- `[Encrypted]` attribute (`Cratis.Chronicle.ProtectedValues`) for values that need encryption at rest without being personal data subject to GDPR right-to-erasure — an API key, a webhook signing secret, a partner credential (#4095)\n- `EncryptionScope.Subject` (default), `.Namespace`, and `.Global` — the identity an `[Encrypted]` value's key is provisioned under. A subject-scoped key is always disjoint from any `[PII]` key for the same subject; a namespace-scoped key is shared by every value marked that way in one event store namespace; a global-scoped key is shared across the whole installation\n- `[PII]` and `[Encrypted]` cannot both apply to the same value — rejected at schema-generation time, and flagged at compile time by a new analyzer (CHR0053), because the two would otherwise encrypt the value twice under two different keys and be unable to release it correctly\n- `[Encrypted]` cannot be applied to an `EventSourceId`, flagged at compile time by a new analyzer (CHR0052), mirroring the existing restriction on `[PII]`\n- No API anywhere can delete an `[Encrypted]` value's key — `PIIManager`'s erasure operations explicitly refuse an identifier belonging to an `[Encrypted]` value\n- `Cratis.Chronicle.Testing`'s `EventScenario` and related scenario helpers now protect `[Encrypted]` values the same way they already protect `[PII]` values\n- `Cratis.Chronicle.Confidentiality` — a security metadata provider system (`ICanProvideSecurityMetadataForType`/`ICanProvideSecurityMetadataForProperty`, `ISecurityMetadataResolver`) parallel to the existing GDPR compliance one, for classifying a value as needing protection for security reasons rather than compliance reasons. `[Encrypted]` is its first consumer; `[PII]` continues to resolve only through the compliance provider system and never gains `[Encrypted]`'s wider scopes\n" + }, + { + "repo": "Chronicle", + "tag": "v19.3.2", + "date": "2026-09-22", + "rules": [], + "body": "## Fixed\n\n- `Cratis:Chronicle:Clustering:ResponseTimeout` now governs the call it was added for. A grain service is a system target, and Orleans times system target calls out against `SystemResponseTimeout` rather than `ResponseTimeout`, so registering projections from the startup task stayed on the 30 second default while the setting appeared to have been applied. (#4088)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.3.1", + "date": "2026-09-22", + "rules": [], + "body": "## Fixed\n\n- `Cratis:Chronicle:Clustering:ResponseTimeout` now also applies to the calls it was added for. A silo hosts a client of its own, and the startup task reaches its grain services through that hosted client, so those calls take the client's timeout rather than the silo's — which left the startup path that needs the larger budget still running on Orleans' 30 second default and made the setting look like it did nothing. (#4088)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.3.0", + "date": "2026-09-22", + "rules": [], + "body": "## Added\n\n- `Cratis:Chronicle:Clustering:ResponseTimeout` sets how long a grain call waits for its response. Some startup work grows with the deployment — the projections for an event store are registered across every namespace it has — while Orleans' 30 second default does not, and a server that grows past that point cannot finish starting. Because it cannot finish starting it cannot start on the next attempt either, so it stays down with no way to give it more room. Defaults to the same 30 seconds Orleans uses. (#4088)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.2.2", + "date": "2026-09-22", + "rules": [], + "body": "## Fixed\n\n- A Chronicle kernel could reach a state where it was unable to start at all. Registering the persisted projection definitions was the one startup step that did not retry, and it is the step likeliest to exceed the response timeout, because registering fans a subscribe out across every observer in the store rather than doing a single grain's work — so the time it needs grows with the store. Once a store grew past that point the timeout terminated the host, the next attempt hit the same wall, and the kernel stayed down until someone intervened. (#4088)\n- The `Week` calendar accessor is now an extension method. It shipped as an extension property, which cannot be used for the one thing it exists for — the language does not permit an extension property inside an expression tree, and these accessors are only ever written inside one. A path built with it is now also rendered as the bare name rather than with a trailing `()`, matching the name the registry is keyed by; the parenthesized form is still accepted when parsing.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.2.1", + "date": "2026-09-22", + "rules": [ + "No release notes" + ], + "body": "Proper implementation using extension(DateTimeOffset) syntax. Week is now a true property accessor, not a method call." + }, + { + "repo": "Chronicle", + "tag": "v19.2.0", + "date": "2026-09-22", + "rules": [ + "Heading not allowed", + "No release notes", + "Review, verification or provenance note" + ], + "body": "## Summary\n\nAdds first-class support for ISO week number and day-of-week in composite-key builders without requiring translator reactors or precomputed event properties. Derived properties render as property accessors (`.Week`) even though they're called as methods in expressions.\n\n## Changes\n\n- Added `DerivedPropertyFunction` registry as extensible mechanism for derived property accessors\n- Implemented `DateTimeOffset.Week()` extension method wrapping `System.Globalization.ISOWeek.GetWeekOfYear()`\n- Updated expression parser to recognize and render derived property calls without parentheses\n- `PropertyPath` recursively evaluates paths with derived properties\n- Works identically with reflected properties, dynamic ExpandoObject, and injected event content\n- Day-of-week (`DayOfWeek` property) already works as a real property\n\n## Usage\n\nIn fluent API (method call syntax, renders as property):\n```csharp\n.ToEventContextProperty(c => c.Occurred.Week()) // renders as: Occurred.Week\n```\n\nIn string paths (no parens needed):\n```csharp\nnew PropertyPath(\"Occurred.Week\")\n```\n\nIn declarative code generation:\n```\n$eventContext(Occurred.Week)\n```\n\n## Acceptance\n\n- [x] Week-of-year accessible declaratively without translator reactor or new event type\n- [x] Client expression parser accepts `.Week()` method calls, renders without parens\n- [x] Infrastructure PropertyPath evaluates derived functions (including ISO 8601 edge cases: 2024-12-30 and 2025-01-02 both week 1)\n- [x] Kernel expression resolver handles week-of-year in composite keys\n- [x] Property-like rendering makes API surface clean and consistent\n- [x] All specs pass: Infrastructure 686, DotNET 2944, Core 3688, Testing 556 = 7,874 total\n- [x] `dotnet build` (Debug/Release): 0 errors, 0 warnings\n\n## Design Note\n\nRather than special-casing `ISOWeek` by name, added a documented, supported extension point so future derived accessors (day-of-quarter, business-day, fiscal-year) can be added in one place (DerivedPropertyFunctions) without modifying the parser, kernel resolver, or test infrastructure.\n\nAddresses: #4086\n\nCo-Authored-By: Claude Sonnet 5 " + }, + { + "repo": "Chronicle", + "tag": "v19.1.8", + "date": "2026-09-22", + "rules": [], + "body": "## Changed\n\n- `IReadModels.Release` for a sequence now resolves the schema once for the whole sequence instead of once per instance, and hands the sequence back untouched when the read model carries no compliance metadata at all.\n\n## Fixed\n\n- Reading a collection of read models no longer regenerates the same JSON schema once per row. Generating a schema is a function of the CLR type alone, so it is now generated once per type and shared. The compliance release pass asked for it per instance, which made serving a collection of read models — every emission of an observable query, for every subscriber — spend time proportional to the number of rows on work that produces an identical answer each time. Measured at 328 ms for 916 rows of a moderately sized read model, now effectively zero.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.1.7", + "date": "2026-09-21", + "rules": [], + "body": "## Fixed\n\n- Subscribing an observer no longer waits for every failed partition to be handed to the jobs manager for recovery. An observer with hundreds of failed partitions previously exceeded the 30 second grain-call budget inside `Subscribe`, so the client timed out and the observer stayed unsubscribed - after a kernel restart this left reactors quarantined and projections disconnected until the next restart. Recovery now runs in a turn of the observer's own once the subscription is established.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.1.6", + "date": "2026-09-21", + "rules": [], + "body": "## Fixed\n\n- Projection subscribers now discard their silo-local cached pipeline when a projection definition changes, so events of a newly consumed event type are handled with the updated key resolvers instead of failing with `Missing key resolver` until the kernel restarts.\n" + }, + { + "repo": "Chronicle", + "tag": "v19.1.5", + "date": "2026-09-20", + "rules": [], + "body": "## Fixed\n\n- A job step that was `Scheduled` or `Running` when its silo died now restarts its work on the next start or resume. Previously the step answered `AlreadyStarted` from its persisted status even though the background task doing the work died with the process - the step stayed `Scheduled` forever and the job it belonged to looked alive without ever running. Observers whose catch-up waited on such a job stranded on every retry until the strand bound quarantined them, and every kernel restart mid-catch-up minted more wedged steps. A step already running in the same activation still answers `AlreadyStarted`." + }, + { + "repo": "Chronicle", + "tag": "v19.1.4", + "date": "2026-09-20", + "rules": [], + "body": "## Fixed\n\n- A quarantined observer now comes back to life when its client subscribes again. Previously a new subscription was recorded but the observer stayed quarantined - making quarantine terminal in practice: no redeploy, restart or reconnect could ever revive it, and observers stayed dead across every subsequent deploy while the events they should have handled kept appending. A fresh subscription resets the stranded-catch-up counter and routes the observer, which re-evaluates the gap and drives catch-up; if catch-up keeps stranding under the new subscription, the bound quarantines it again.\n- Clearing an observer's quarantine through the API now also resets the stranded-catch-up counter. Previously the counter was already past the bound, so the very next transient strand re-quarantined the observer immediately - the manual clear survived exactly one hiccup." + }, + { + "repo": "Chronicle", + "tag": "v19.1.3", + "date": "2026-09-19", + "rules": [], + "body": "## Fixed\n\n- Event-type registration from a gRPC client now reaches the handler after command validation. The protobuf-backed repeated `Types` sequence may be single-use; validation enumerated it first, leaving the handler with no types and allowing a later append to fail with `MissingEventSchemaForEventType`. Registrations are now materialized when the command is created." + }, + { + "repo": "Chronicle", + "tag": "v19.1.2", + "date": "2026-09-19", + "rules": [ + "Review, verification or provenance note" + ], + "body": "## Fixed\n\n- Registering event types no longer silently drops a registration when the incoming sequence can only be enumerated once. `EventTypeRegistrar.Register()` used to walk the incoming registrations twice — once to validate, once to build what gets written — without ever materializing the sequence first; a single-use sequence saw the first (validation) pass consume everything and the second pass see nothing, so the command still reported success while the event type was never stored. Every later append of that event type then failed with `is missing from the event store`, with nothing pointing at registration as the cause. Reproduced end-to-end with the Kotlin client scaffolded from `dotnet new cratis-kotlin` (Cratis/Chronicle.Kotlin#86)." + }, + { + "repo": "Chronicle", + "tag": "v19.1.1", + "date": "2026-09-17", + "rules": [], + "body": "Brings in `Cratis.Screenplay` / `@cratis/screenplay-language` 4.14.0, which adds grammar\nsupport for projection variants (Cratis/Screenplay#200, Cratis/Screenplay#185) — the\nScreenplay counterpart to the read model variants added to the .NET client SDK in #4062.\n\n## Changed\n\n- Updated `Cratis.Screenplay` to 4.14.0 (Chronicle kernel `LanguageService.Compile`)\n- Updated `@cratis/screenplay-language` to 4.14.0 (Workbench `ProjectionEditor`)\n" + }, + { + "repo": "Chronicle", + "tag": "v19.1.0", + "date": "2026-09-17", + "rules": [], + "body": "Adds mutually exclusive read model variants: several read models sharing one logical\nidentity, where an entity is in exactly one variant at a time and the engine — not the\nauthor — maintains the exclusion. No kernel, wire, or storage changes; each variant\ncompiles to an ordinary, independent projection definition.\n\n## Added\n\n- `[VariantOf]` and `[EntersOn]` attributes to declare a model-bound\n read model as one variant of a mutually exclusive group, and `[GlobalFor]`\n to declare a handler shared by every variant in the group (#3956)\n- A fluent equivalent — `builder.VariantOf(_ => _.Id).EntersOn()` — on\n the projection builder (#3956)\n\n## Fixed\n\n- `ReadModelScenario` now builds a variant's projection definition the same\n variant-aware way the real discovery pipeline does, so an in-process scenario for a\n variant read model actually exercises mutual exclusion instead of behaving like an\n ordinary upsert projection\n" + }, + { + "repo": "Chronicle", + "tag": "v19.0.1", + "date": "2026-09-17", + "rules": [ + "Heading not allowed", + "Section heading level" + ], + "body": "# Summary\n\n`ConnectionService.CheckCompatibility` always called `WireCompatibilityChecker.Check(clientContract, serverContract)` — treating the connecting client as the older side and the kernel as the newer one, unconditionally, regardless of which one actually was. `WireCompatibilityChecker` itself is correct by design (its own remarks: *\"Additions are never reported... an older peer simply does not use them\"*); the defect was entirely in which direction `ConnectionService` asked the question.\n\nOn 2026-09-17 a kernel still on 18.1.4 was asked this by a client built from 18.4.1, which had gained a field (`ReducerDefinition.Hash`, since #4058) the kernel had never declared. Because the client was always treated as the older side, that addition was reported as a field the kernel had removed, and the client refused to connect to a kernel it was fully able to talk to. Production was down for ~33 minutes.\n\n## Fixed\n\n- The kernel's connect-time compatibility check now orders the comparison by which side is actually older (by protocol version), rather than always treating the connecting client as the older side — a client that has only gained fields, methods, or messages since the kernel's release now connects successfully instead of being incorrectly refused (#4058)\n\n## How\n\nBoth sides already report the same release-stamped protocol version string — `Contracts.ProtocolVersion.Current` on the kernel, the equivalent client SDK property on the client's request — so ordering by that is enough to pick the right direction without any new information changing hands or any change required on the client side:\n\n- `ConnectCompatibility.Check(clientContract, clientProtocolVersion, kernelContract, kernelProtocolVersion)`, in `Cratis.Chronicle.Compatibility`, decides which of the two is actually older and calls `WireCompatibilityChecker.Check` in that order — always `Check(older, newer)`, regardless of whether the older one happens to be the client or the kernel.\n- `ProtocolVersionOrder.Compare` orders two protocol version strings numerically, not lexicographically (`\"1.10.0\"` sorts after `\"1.9.0\"`), and treats an empty or unparsable version as the oldest possible rather than throwing — a client too old to send a protocol version at all falls back to the direction Chronicle has always checked, so this cannot regress an older, pre-`CheckCompatibility` client.\n- `ConnectionService.CheckCompatibility` now calls `ConnectCompatibility.Check` instead of calling `WireCompatibilityChecker.Check` directly.\n\n## What this does not do, and what does not need to change elsewhere\n\nThis does not help a kernel that has already shipped with the old, one-directional check and will not be redeployed with this fix — #4061 (merged) covers that gap with a client-side `skipCompatibilityCheck` escape hatch, since nothing server-side can change what an already-deployed kernel computes. This is the fix for every kernel that *does* get redeployed, so this exact class of defect does not recur once it has.\n\n**The release/PR gate (`Source/Tools/WireCompatibility`) needs no change.** It was tempting to conclude, from this incident, that the gate must also be checking \"the wrong direction\" and needs a mirror-image check added — I drafted exactly that in an earlier version of this description, and it would have been wrong to add. The gate's one check, `WireCompatibilityChecker.Check(baseline, HEAD)` for every released baseline, already states the complete invariant: *the newer of any two contracts must still serve the older one*, and a released baseline is, by construction, always the older of the two contracts being compared — there is no ambiguity for the gate to get backwards. `ConnectCompatibility` (above) exists precisely because that ambiguity *is* real at connect time — a live kernel and a live client can be newer or older than each other in either order — and resolves it the same way the gate's own construction already guarantees: always compare `(older, newer)`, never `(client, kernel)`. Once that resolution happens correctly, the gate's existing single check already covers both real-world connection shapes (an old client reaching a new kernel, and a new client reaching an old kernel that has not yet redeployed this fix) for every contract change that has ever passed it. #4042 — the pull request that introduced the field this incident tripped over — passed that gate correctly, because the field addition itself was never incompatible; only the runtime's choice of direction was.\n\n## Verification\n\n`ConnectionService.CheckCompatibility` had **no specification at all** before this — neither the happy path nor the malformed-descriptor-set path was covered. This adds:\n\n- `for_ProtocolVersionOrder` (7 specs) — numeric vs. lexicographic ordering, missing/empty/unparsable components\n- `for_ConnectCompatibility` (6 specs) — the exact #4058 shape (newer client, older kernel, added field → compatible), its mirror (older client, newer kernel → compatible, unchanged), a genuine incompatibility still caught with the client as the newer side, equal-version tie-breaking, and an unversioned client falling back safely\n- `for_ConnectionService/when_checking_compatibility` (6 specs, new folder) — end-to-end through the real `ConnectionService.CheckCompatibility` method: the servers-own-descriptor-set happy path and the malformed-descriptor-set path\n\n```\ndotnet build Source/Kernel/Compatibility/Compatibility.csproj # 0 warnings, 0 errors\ndotnet build Source/Kernel/Grpc/Grpc.csproj # 0 warnings, 0 errors\ndotnet build Source/Kernel/Server/Server.csproj # 0 warnings, 0 errors\ndotnet test Source/Kernel/Compatibility.Specs # 50 passed, 0 failed\ndotnet test Source/Kernel/Core.Specs # 3681 passed, 0 failed (full suite, unaffected specs included)\n```\n\nLabeled `patch` — a runtime behavior fix, no public API removed or changed in an incompatible way (`ConnectCompatibility` is new, additive public surface; `ProtocolVersionOrder` is internal).\n" + }, + { + "repo": "Chronicle", + "tag": "v19.0.0", + "date": "2026-09-17", + "rules": [ + "Heading not allowed", + "Section heading level" + ], + "body": "# Summary\n\nThe server compatibility check runs entirely server-side: the client sends its descriptor set, the kernel computes the verdict, and refuses the connection when it says the two are incompatible. That computation is only as current as the *kernel build* answering it. On 2026-09-17 the kernel behind #4058's incident (18.1.4) computed the wrong verdict — a defect in which direction the comparison runs, not in the comparison itself — and no client-side change can make that already-deployed, already-compiled kernel decide differently. Fixing the direction (tracked separately, server-side) helps every kernel that gets redeployed with the fix. It does nothing for one that will not be.\n\n`skipCompatibilityCheck` already existed as a `ChronicleConnection` constructor parameter, but nothing surfaced it to an application: not the connection string, not `ChronicleOptions`, and not even the standalone `ChronicleClient` construction path used outside a host, which silently ignored the parameter altogether (it never appeared in the `new ChronicleConnection(...)` call there at all).\n\nThis is the only thing that can restore a connection to a kernel like 18.1.4 with the *next* client version, today, without waiting for that kernel to be redeployed.\n\n## Added\n\n- `skipCompatibilityCheck` connection-string option, alongside the existing `skipTlsValidation`, for a deliberate, visible, audit-friendly override of the server compatibility check against a known-safe version skew (#4058)\n- `ChronicleOptions.SkipCompatibilityCheck`, resolved together with the connection string's option — either input asking to skip is enough, since both default to performing the check\n\n## Fixed\n\n- `ChronicleClient`'s direct construction path (used outside a host) now actually honors a request to skip the compatibility check; it previously ignored the setting entirely\n- `AddCratisChronicleConnection`'s `skipCompatibilityCheck` parameter now falls back to the connection string's option when not explicitly specified, the same way `skipTlsValidation` already does\n\n## Notes for review\n\n**This is not a general-purpose override.** Turning it on hides every incompatibility the server would have reported, including a genuine one — it exists for one situation: a known-safe skew against a kernel whose fix cannot be redeployed. The XML docs on all three surfaces (`ChronicleOptions`, the connection string builder, `ChronicleConnectionString`) say this explicitly.\n\n**Why `major`.** `AddCratisChronicleConnection`'s `skipCompatibilityCheck` parameter changes from `bool skipCompatibilityCheck = false` to `bool? skipCompatibilityCheck = null`. Source-compatible for essentially every real call site (named arguments, direct `true`/`false` literals via implicit conversion), but it is a signature change on a public method — an already-compiled caller referencing the old overload would not bind against the new one without recompiling. Flagging it `major` rather than judging the practical blast radius myself.\n\n**Design mirrors the existing TLS pattern deliberately**, not a new convention: `CompatibilityCheckPolicy.ShouldSkip(options, connectionString)` is the same shape as `TlsCertificateValidationPolicy.ShouldSkip(tls, connectionString)`, with the inverse combinator — OR instead of AND — because this setting defaults to *performing* the check where TLS validation defaults to *skipping* it. The remarks on both explain why an unspecified input must not swallow an explicit request on the other.\n\n## Verification\n\n```\ndotnet build Source/Clients/DotNET/DotNET.csproj # 0 warnings, 0 errors\ndotnet build Source/Clients/DotNET.Specs/DotNET.Specs.csproj # 0 warnings, 0 errors\ndotnet test Source/Clients/DotNET.Specs -- for_ChronicleConnectionStringBuilder|for_CompatibilityCheckPolicy|for_TlsCertificateValidationPolicy|for_ChronicleConnectionString\n # 132 passed, 0 failed\ndotnet build Source/Clients/Connections.Specs/Connections.Specs.csproj # 0 warnings, 0 errors\ndotnet test Source/Clients/Connections.Specs # 71 passed, 0 failed (unaffected existing suite)\ndotnet format Source/Clients/Connections/Connections.csproj --verify-no-changes # clean\ndotnet format Source/Clients/DotNET/DotNET.csproj --verify-no-changes # clean\n```\n" + }, + { + "repo": "Chronicle", + "tag": "v18.5.1", + "date": "2026-09-17", + "rules": [], + "body": "## Summary\n\nTwo failures from one production incident, both in the path a silo takes as it starts. A slow sibling silo crash-looped both kernel replicas, and the projection registrations that timed out against them never recovered — leaving a consuming application's read model frozen for a day while every layer reported success.\n\n## Fixed\n\n- A silo no longer terminates when a grain call made during startup cannot reach a sibling silo that has not stabilized yet. The failures a forming cluster produces are now retried wherever they occur in the startup task, rather than at whichever individual call site was last discovered to be unprotected. A step that still fails once the retry budget is spent fails the host as before, so a genuine defect is not started around (#4051)\n- A projection can no longer stay permanently unsubscribed. The activation-time resubscribe no longer holds the projections manager's turn while it runs, so registrations arriving during it are answered instead of timing out; and a registration now reconciles projections that are registered but not subscribed, instead of deciding purely from whether definitions changed. Together these close a state where appends kept succeeding, commands kept returning success, the observer had no failed partitions — because it was observing nothing — and the read model simply never updated (#4052)\n" + }, + { + "repo": "Chronicle", + "tag": "v18.5.0", + "date": "2026-09-17", + "rules": [], + "body": "## Added\n\n- `EventSourceType` on `ForEventSourceIdAndEventTypesRequest`, additive and wire-compatible with every released 18.x baseline (#4049)\n\n## Fixed\n\n- `GetForEventSourceIdAndEventTypes` now applies its event source type argument instead of ignoring it, so a read narrowed to one event source type no longer returns events of every other type (#4049)\n" + }, + { + "repo": "Chronicle", + "tag": "v18.4.1", + "date": "2026-09-17", + "rules": [ + "Heading not allowed", + "Section heading level", + "Section order" + ], + "body": "# Summary\n\n18.4.0 was a mistake and this withdraws it. It answered a narrow problem — an append notification that reported an occurrence time and a content hash it did not have — by returning the full stored context of every appended event through the write path, roughly 400 bytes per event, behind three new contract messages. Chronicle already has two ways to learn what was stored: read the event, or observe it. The only consumer of that notification in this repository is the integration-test collection, which asserts on sequence number and event source id.\n\n## Changed\n\n- An append notification reports the event as the caller asked for it, with the sequence number the kernel assigned, and claims nothing else. No invented occurrence time, no invented hash.\n\n## Removed\n\n- `AppendReceipt`, `AppendCausation` and `AppendIdentity`, the `IncludeReceipt`/`IncludeReceipts` request flags and the `Receipt`/`Receipts` response fields.\n- `AppendResult.Receipt`, `AppendManyResult.Receipts` and the kernel plumbing behind them.\n- The routing-omission tracking that existed to serve receipts. It never changed what reached the kernel, because the client's defaults already equal the kernel's, and it broke value equality on `EventForEventSourceId` along the way.\n\n## Kept from 18.4.0\n\nAuthorization verdicts sent explicitly rather than omitted, which fixed authorized responses decoding as unauthorized in every proto3 client. The batch acknowledgment count guard. In-memory hash fidelity and MongoDB timestamp precision. The multi-target packaging fix. The central append route resolution.\n\nAgainst pre-18.4.0 main, the wire contract now differs only by the authorization fix.\n\n## Added\n\n- `--withdrawn` excludes a named release from the wire compatibility gate, which a floor cannot do for the newest release of a major. It refuses a version that is not a released baseline, refuses one from another major, and says out loud when a run was narrowed.\n\n## Compatibility\n\n18.4.0 is withdrawn from the gate: its append contract is not the one major 18 carries. 18.0.0, 18.1.0, 18.2.0 and 18.3.0 are all still served. The published 18.4.0 packages remain immutable; what is withdrawn is the claim that Chronicle must keep serving that contract. Anyone who took 18.4.0 in the hours it was current should move to this release.\n" + }, + { + "repo": "Arc", + "tag": "v22.41.1", + "date": "2026-10-01", + "rules": [], + "body": "## Fixed\n\n- ARCCHR0011 now reports `GetInstanceById` reads made through an application extension method on `IReadModels` (for example one taking a typed `EventSourceId` identity), including from rule lambdas over the whole command such as `RuleFor(c => c).MustAsync(...)` and from `Provide` (#2935)\n- ARCCHR0011 now treats a read model as Chronicle-backed when its model-bound projection attributes (`[ChildrenFrom]`, `[Join]`, `[SetFrom]` and similar) are declared on a property or record parameter, so plain parameters of such models in event-producing commands are reported (#2935)\n" + }, + { + "repo": "Arc", + "tag": "v22.41.0", + "date": "2026-10-01", + "rules": [], + "body": "## Added\n\n- `DecisionReadAdmissions` in `Cratis.Arc.Chronicle.Testing` checks, without executing any command, that Chronicle admits every `DecisionRead` parameter of the `Handle` and `Provide` methods of `[ProtectedDecision]` commands, including inherited ones. `DecisionReadAdmissions.ShouldAdmitDecisionReadsIn(assembly)` throws `DecisionReadsAreRefused`, listing each command, read model and `DecisionReadRefusalReason`, so a refused shape such as children or a reducer fails a spec instead of every execution. See [Check that Chronicle admits the decision reads](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/testing/chronicle.md#check-that-chronicle-admits-the-decision-reads). (#2929)\n\n## Changed\n\n- A `CommandScenario` now resolves a read model, or a reducer's `current` state, the way production does, in every scenario and not only with `UseDecisionReads()`. A read model whose projection or reducer handles none of the events seeded for its event source now resolves as `null` instead of as an empty instance, so a non-nullable injected read model receives `null`; and a reducer receives `null` as `current` for its first event instead of a default instance. A spec that relied on the old behavior should seed an event the read model handles, pin the read model with `.ReadModel(...)`, or have the reducer handle a `null` `current`. (#2928)\n\n## Fixed\n\n- A decision-mode `CommandScenario` (`UseDecisionReads()`) now provides the reads that do not guard the decision: injected read models, `IReadModels.GetInstanceById` and an `[Unprotected]` command's `DecisionRead` are projected from the events seeded with `Given.ForEventSource(...).Events(...)`, the same log the decision reads fold, and can be pinned with `.ReadModel(...)`. Pinning a read model that the command takes as a protected `DecisionRead` still throws `PinnedReadModelCannotProvideDecisionToken`, now naming the command and read model. (#2928)\n- A concurrency violation now reads \"Event source '…' has new events since the command read it: expected no events, but it has events up to sequence number 0. Read it again and resubmit.\" instead of showing raw sentinel sequence numbers such as `18446744073709551613`. (#2930)\n" + }, + { + "repo": "Arc", + "tag": "v22.40.3", + "date": "2026-10-01", + "rules": [], + "body": "## Fixed\n\n- ARCCHR0011 now recognizes event-producing command handlers that return nested wrappers such as `Task>`, `ValueTask>` or `object[]`, so unguarded `GetInstanceById` calls and plain read-model parameters in those commands and their validators are reported (#2931)\n" + }, + { + "repo": "Arc", + "tag": "v22.40.2", + "date": "2026-10-01", + "rules": [], + "body": "## Fixed\n\n- Screenplay generation now states the events a specification seeds through the event scenario of a command scenario (`scenario.EventScenario.Given.ForEventSource(...).Events(...)`) as `given` events, instead of dropping them. (#2921)\n- A rejected command's scenario no longer states an event it asserts is still in the event log as something the command produced, so a duplicate-rejection scenario that seeds an event and asserts it is still the only one generates a document that compiles instead of failing with `PLAY0285`. (#2921)\n" + }, + { + "repo": "Arc", + "tag": "v22.40.1", + "date": "2026-10-01", + "rules": [], + "body": "## Fixed\n\n- Screenplay generation names the declared enumeration member, including a `[Flags]` zero member and a member declared as a combination of others, when a scenario's expected event states it in a `ShouldHaveAppendedEvent` predicate, and does so for enumerations declared over `byte`, `long` and other integral types as well. The generated document no longer fails with \"not a declared member of enum\". (#2839)\n- A scenario that issues a command, starts from an event or expects an event with a value the generated document cannot state is now left out and reported as a warning (`SP0039`) naming the property, the value and why, instead of being written as a number, under an invented name or without the value, and the document no longer fails to compile on such rejection scenarios. The values are a number cast to an enumeration that declares no member with it, `default` for an enumeration that declares no zero member, several `[Flags]` members combined into a value no member is declared with (Screenplay cannot state combined flags), and `null` for a required record or list. `null` is still stated where the property may be absent. (#2920)" + }, + { + "repo": "Arc", + "tag": "v22.40.0", + "date": "2026-10-01", + "rules": [], + "body": "## Changed\n\n- A `[ProtectedDecision]` command now runs discoverable validators, including `ConceptValidator` and `CommandValidator`, whose only public constructor is parameterless and that Arc constructed itself. A validator with constructor dependencies is still refused before anything is resolved. A validator registered explicitly through a factory, an instance or another registration that supplies an instance Arc did not construct is resolved first, because only resolving it reveals what it supplies, and then refused: the command still fails and nothing the registration produced is used. Let convention discovery register the validator. Arc certifies only that it ran the parameterless constructor and that the rules and rule components are unchanged by reference; state changed after construction (including by a decorator that returns the original instance), property injection, `ApplyCondition`, cascade mode, nested child validators, static members and service locators are not covered and stay the developer's responsibility. See [protected-mode validator boundary](https://github.com/Cratis/Arc/blob/main/Documentation/backend/csharp/chronicle/read-models/injecting-into-commands.md#decision-reads-for-event-dependent-commands) (part of #2831)\n- The refusal messages and the ARCCHR0011 validator advice now say why a validator was refused and what to do instead (part of #2831)\n\n" + }, + { + "repo": "Arc", + "tag": "v22.39.2", + "date": "2026-09-30", + "rules": [], + "body": "## Fixed\n\n- An executable that references a project with a file-local type, such as the registration class the Cratis Fundamentals type discovery generator adds to every project, no longer fails to build with CS0234 in the generated `ProjectReferenceModules.g.cs`. The project reference modules generator now only names a type whose full name resolves to that type from the executable. (#2917)\n" + }, + { + "repo": "Arc", + "tag": "v22.39.1", + "date": "2026-09-30", + "rules": [], + "body": "## Fixed\n\n- A custom field error on a command form field is now reported once instead of twice, so screen readers no longer read it twice (#2842)\n" + }, + { + "repo": "Arc", + "tag": "v22.39.0", + "date": "2026-09-30", + "rules": [], + "body": "## Added\n\n- `[ProtectedDecision]` commands read the read models their decision depends on through Chronicle decision reads (`DecisionRead` / `IDecisionReads`) inside the command's transaction. If another command changes what the decision read before this one commits, the commit is rejected as a concurrency violation (a validation result, HTTP 400) instead of overwriting it (#2828)\n- `[Unprotected]`, `CommandDecisionMode`, command dependency safety checks, decision scenario helpers for `CommandScenario` (`UseDecisionReads`, `AppendConcurrently`), and the ARCCHR0011/ARCCHR0012 analyzers that point out decisions made outside protection (part of #2828)\n\n## Changed\n\n- Chronicle packages are updated from 19.13.0 to 19.23.0 and Fundamentals to 7.19.6 (part of #2828)\n- Arc claims ownership of each command's Chronicle unit of work and commits it as owner. A failed command whose unit has enrolled decision reads is rolled back as owner (`RollbackAsOwner`); a failed command without decision reads, including every unmarked command, is rolled back through the public `Rollback()` exactly as before. A direct SDK `Rollback()` or `Commit()` (including an aggregate root commit) of a protected unit is refused with `ProtectedUnitOfWorkRequiresOwner` (part of #2828)\n- `AddReadModels` registers Arc's command-aware `IDecisionReads` and `DecisionRead` for every read model. In unmarked commands `IDecisionReads.GetDetached` and `Admit` behave exactly as Chronicle's reader; `IDecisionReads.Get` and `DecisionRead` are refused with a message pointing to `[ProtectedDecision]` (on 19.13 an enrolled read in an unmarked command already failed at commit) (part of #2828)\n\nProtection is opt-in: unmarked commands keep their current behavior. A `[ProtectedDecision]` command refuses discoverable validators rather than supporting them (tracked in #2831); queries it performs still run their own validators, such as paging.\n\n### Behavior changes from Chronicle 19.13 to 19.23\n\nThese come with the package update and reach applications without code changes:\n\n- **Unique constraints honour `IgnoreCasing`.** The .NET client now sends `IgnoreCasing` when it registers a unique constraint. Before, the kernel stored every unique constraint as case-sensitive, so a constraint marked `IgnoreCasing` now rejects a value that differs only in casing (Cratis/Chronicle#4368, 19.22.0)\n- **Constraint indexes are rebuilt at registration when a constraint's sequences change.** A constraint can declare the event sequences it applies to (`ForEventSequences(...)`, `ForEventLog()`, `EventSequences` on `[Unique]`). For each newly covered sequence the kernel rebuilds the index from existing events; a sequence no longer covered stops validating. See the known limits in Cratis/Chronicle#4408 (Cratis/Chronicle#4398, 19.23.0)\n- **The ASP.NET Core event store falls back to the client options.** `UseCratisChronicle` now uses the client's configured event store unless a web-specific one is set (Cratis/Chronicle#4338)\n- **Logging levels changed.** A reactor that loses its kernel connection (gRPC `Unavailable`) logs a Warning and reconnects instead of an Error; a cancelled stream logs at Debug (Cratis/Chronicle#4373, 19.22.0). The observer transition trace log category is now `StateMachine` (Cratis/Chronicle#4289)\n- **Unit-of-work lifecycle.** `ChronicleOptions.UnitOfWorkLifecyclePolicy` is added with `Compatibility` as the default, and `Dispose()` during an in-flight commit no longer rolls back or throws (Cratis/Chronicle#4258)" + }, + { + "repo": "Arc", + "tag": "v22.38.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- The generator emits member walkers for the reachable graph of command and query argument types, and `ModelGraphValidator` reads their properties through generated, statically typed getters instead of reflection. Validation is unchanged: the same validators run in the same order with the same member paths. Walkers are generated only where they are provably identical to reflection: the type and all its property-declaring bases are declared in source in the same compilation, none of them is partial (another generator could add properties), and no two public properties in the hierarchy share a name. Every other type, and runtime-typed values behind `object` or interfaces, keeps the reflection walk (Refs #2854)\n- `ModelGraphWalkers` and `ModelGraphMember`, for generated code (Refs #2854)\n\nThe trim/AOT baseline is unchanged: the reflection walk remains for runtime-typed members.\n\nReviewed with Anthropic models only (no cross-provider review available right now).\n" + }, + { + "repo": "Arc", + "tag": "v22.37.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- `ArcOptions.AddJsonTypeInfoResolver(...)` adds an application's source-generated `JsonSerializerContext` (or any `IJsonTypeInfoResolver`) ahead of Arc's own metadata, for trimmed and NativeAOT applications. NativeAOT applications register their query data, change-set item and `CommandResult` types there (Refs #2854)\n\n## Changed\n\n- Arc's JSON options carry a type-info resolver chain: resolvers the application added, then one Arc resolver that asks any resolvers appended to the chain later, then a source-generated context for Arc's own wire types (query and command results, change sets, paging, observable query hub and WebSocket messages, identity, introspection), then reflection only when it is enabled. Arc serializes these types through resolved `JsonTypeInfo` instead of runtime-type overloads. The JSON on the wire is byte-for-byte unchanged, and resolvers an application appends to `TypeInfoResolverChain` keep deciding the contracts as before. The trim/AOT baseline drops from 488 to 332 (Refs #2854)\n- `ArcOptions.JsonSerializerOptions.TypeInfoResolver` is no longer `null` by default, and an appended resolver that does not know one of Arc's types now falls back to Arc's metadata instead of failing. An application that appends `JsonTypeInfoResolver.Combine(options.TypeInfoResolver, x)` to the chain now has Arc's resolver answer before `x` for types Arc or reflection knows; wrapping with `WithAddedModifier` and plain appends behave as before (Refs #2854)\n\nReviewed with Anthropic models only (no cross-provider review available right now).\n" + }, + { + "repo": "Arc", + "tag": "v22.36.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- `false` in an application's project file turns MVC off through a feature switch, so a trimmed or NativeAOT publish removes Arc's MVC registration. In a trimmed test app this dropped trim warnings from 140 to 16 on net10.0 and from 130 to 18 on net8.0, and MVC assemblies from 6 to 2. Controllers stay on by default, and `WithoutControllers()` still works at runtime (Refs #2895)\n\nThe switch is a `RuntimeHostConfigurationOption` written by a packed `build/Cratis.Arc.targets` (also in `buildTransitive/`), read by `[FeatureSwitchDefinition]`/`[FeatureGuard]` on net9.0+ and by embedded ILLink substitutions on net8.0. `Mvc.Core` and `Mvc.Abstractions` still ship because Arc references MVC types elsewhere.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.35.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- The command generator registers a typed `CommandResult` factory for each model-bound command's response types (looking through `Task`, `ValueTask`, nullable, value tuples, `System.Tuple`, `OneOf` and `Result`), so the command pipeline builds `CommandResult` for them without reflection. NativeAOT applications get typed command results for every response type the generator can name (Refs #2854)\n- `CommandResultFactories` (for generated code) and `MissingCommandResultFactory`, thrown only when a response type has no factory and the code is ahead-of-time compiled (Refs #2854)\n\nResponse types generated code cannot name (inaccessible, nested in open generic types, obsolete or experimental), subtypes of the declared response type, and commands built without the generator keep using reflection whenever code is JIT-compiled, including in applications built with `PublishAot` running under `dotnet run` or a test host. Results are identical either way.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.34.3", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Changed\n\n- Observable query handling, HTTP delivery and the WebSocket demultiplexer close over each element type once, through a cached typed helper, instead of building generic calls through reflection on every subscription. The handler now creates observables with typed activation, and the element-type lookup that ran on every empty emission is gone. Five trim/AOT diagnostics are removed outright; the remaining dispatch reflection is consolidated in one place. The trim/AOT baseline drops from 526 to 492 (Refs #2855)\n\nAn exception thrown synchronously by a custom `ISubject`'s `Subscribe` is no longer wrapped in `TargetInvocationException` in the logs; the client receives the same error message.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.34.2", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Issue reference position", + "Review, verification or provenance note" + ], + "body": "## Changed\n\n- Observable query change sets look up an item type's identity property once per type instead of on every emission (Refs #2856)\n- The runtime-typed JSON serialization and reflection in change set computation and subscription scope snapshots now sit behind single call sites, ready for a resolver-based fix once Arc's JSON options carry a type-info resolver (#2854). This does not make them trim- or AOT-safe yet; it merges repeated unsafe calls, which lowers the trim/AOT baseline by 51 diagnostics (577 to 526 after merging main) (Refs #2856)\n\nSpecs pinning the current change set and scope-snapshot behavior were added first and pass unchanged.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.34.1", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Changed\n\n- Arc serializes its own identity results through a source-generated `JsonSerializerContext` chained in front of the application's resolver, so they need no runtime reflection. The JSON written to the identity cookie and endpoint is byte-for-byte unchanged. Application-defined identity details still go through the application's resolver, falling back to reflection only when none is configured (Refs #2854)\n- Value conversion consolidates its parsing and default-value paths, with unchanged results. The trim/AOT baseline drops from 625 to 577 diagnostics (Refs #2856)\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.34.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- `IArcBuilder.WithoutControllers()` turns off MVC and controller discovery, for applications that only use model-bound commands and queries. Controllers stay on by default (Refs #2880)\n\n## Changed\n\n- Arc registers ASP.NET Core authorization itself instead of relying on MVC to do it (Refs #2880)\n- With controllers on, MVC is now registered after the `configureBuilder` callback; configure `MvcOptions` and MVC's `JsonOptions` after `AddCratisArc` returns (Refs #2880)\n- `AddControllersFromProjectReferencedAssembles` is annotated `[RequiresUnreferencedCode]`/`[RequiresDynamicCode]` (Refs #2880)\n\nWith controllers off, the application registers CORS (`AddCors`) and, for Swashbuckle, `AddEndpointsApiExplorer` itself, since MVC no longer does. `WithoutControllers` keeps MVC out at runtime; letting the trimmer remove it is tracked in #2895.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.33.1", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Changed\n\n- Query paging, counting and sorting, and read model interception, call typed generic helpers instead of building `Queryable` and interceptor calls through reflection, which trimming and NativeAOT cannot follow. The trim/AOT baseline drops from 674 to 625 diagnostics (Refs #2855, Refs #2856)\n- An open generic read model interceptor must implement `IInterceptReadModel` over its own type parameter; any other shape now fails at startup with `OpenGenericReadModelInterceptorMustInterceptItsTypeParameter` naming the type. Such an interceptor already made every query fail (Refs #2856)\n\n## Fixed\n\n- An interceptor class that implements `IInterceptReadModel` for more than one read model now intercepts each of them; before, only the first interface found was used. If such a class is also registered in DI for one of those read models, the discovered type is now used for it instead of the registered instance (Refs #2856)\n\nExceptions thrown synchronously by an interceptor or by `Skip`/`Take` are no longer wrapped in `TargetInvocationException`; query results are unchanged.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.33.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Added\n\n- An executable that references Arc now registers each referenced project's generated type-discovery metadata from generated code, so single-file and trimmed applications no longer depend on `DependencyContext` and `Assembly.Load` to find it. Applications built without the generator keep the existing runtime path (Refs #2857)\n\n## Fixed\n\n- Registering generated metadata no longer swallows every exception. A referenced assembly that is missing or cannot be loaded is logged as a warning, and any other failure, including a module initializer that throws, now stops startup (Refs #2857)\n\nThe runtime fallback's trim/single-file warnings are left visible instead of suppressed; the trim/AOT baseline grows by three `IL2026` (one per target framework) because `Assembly.Load` by name is now annotated `[RequiresUnreferencedCode]`. The repository's own test and sample applications reference Arc.Core as a project, so they keep using the runtime path.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.32.1", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Review, verification or provenance note" + ], + "body": "## Fixed\n\n- A MongoDB call no longer hangs forever when its cancellation token fires while it waits for a connection slot, or is already cancelled when the call starts. The caller's task now completes as cancelled (Refs #2858)\n\nAn opt-out of the resilience layer was dropped from this PR after review: with the layer off, the shared cached `MongoClient` would be disposed at the end of the first request scope. The opt-out, or removing the layer, stays with #2858 for a major release.\n\nReviewed with Anthropic models only (no cross-provider review available right now)." + }, + { + "repo": "Arc", + "tag": "v22.32.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword", + "Heading not allowed", + "Section heading level" + ], + "body": "Refs #2848 (the additive, non-breaking parts; changing the introspection default is left open).\n\n### Added\n- `[ExcludeFromDiscovery]` (`Cratis.Arc`) on a command type, read model, query method, controller or controller action. It leaves the operation out of `/.cratis/commands`, `/.cratis/queries` and the generated OpenAPI descriptions, including command validation endpoints. It does **not** unmap or authorize the endpoint. The marker is inherited.\n- Model-bound commands can use the existing `[Path]` attribute (`Cratis.Arc.Queries.ModelBound`). The explicit path is used consistently by endpoint mapping, the `/validate` endpoint, introspection and the generated TypeScript proxy. A command's path is not inherited and must be a literal route.\n\n### Fixed\n- Query introspection reports a query's custom `[Path]` route instead of the conventional route.\n- The runtime and the proxy generator match the Cratis `PathAttribute` by its full type name.\n\n### Changed (release-note worthy)\n- The lightweight OpenAPI document now drops every route marked `ExcludeFromApiDescription`, including the generated HTTP `QUERY` reader, which OpenAPI 3.0 cannot represent. Apps with `EnableQueryHttpMethod = true` no longer see `query` operations in that document.\n- An application's *own* class named `PathAttribute` is no longer honored on read models or query methods; only Cratis' `PathAttribute` is.\n\n### Not changed\n- Introspection defaults. The proposal for a future major release is to make `RequireAuthentication` default to `true` and require apps to opt back in to anonymous catalogs. Hosts without an authentication scheme would then fail at startup until configured. This protects only the two catalog routes; see also #2873.\n\n### Checks (local, mirroring dotnet-build.yml)\n- Release build: 0 warnings.\n- All 15 spec projects pass (Core 3,008; Screenplay 1,432; Chronicle 837; others).\n- ProxyGenerator specs: 1,562 pass.\n- Screenplay end-to-end reads back clean on the AspNetCore, Chronicle and Layered test apps.\n- An Opus review of the fix commits found no critical or major issues. Its documentation findings are addressed in the last commit.\n- The name-clash guard: a spec imports both ModelBound namespaces and uses `[Path]`.\n\nKnown gap, documented: duplicate explicit command paths are not detected at startup." + }, + { + "repo": "Arc", + "tag": "v22.31.2", + "date": "2026-09-28", + "rules": [], + "body": "## Changed\n\n- Arc checks the caller's identity before and after each authorization policy, including policy construction, for both Arc and ASP.NET policies. A policy that changes the identity now denies the request immediately, and later policies are no longer run against the changed identity, so a later policy cannot restore an earlier change unnoticed (#2830)\n- The authorization documentation and decision 0006 state what the identity check covers: claims, authentication type, name and role claim types and the actor chain. They also state what it does not cover: hidden state in custom principal types, or a change undone within a single policy (#2830)" + }, + { + "repo": "Arc", + "tag": "v22.31.1", + "date": "2026-09-28", + "rules": [], + "body": "## Changed\n\n- ASP.NET model validation no longer builds `ValidationContext` through `MakeGenericType` and `Activator.CreateInstance`, which NativeAOT and trimming cannot preserve. Command-only and query-only rules behave as before (#2853)\n\n## Fixed\n\n- A discoverable validator that derives from a non-generic intermediate base class no longer fails discovery with an `IndexOutOfRangeException`, and one whose intermediate base has several generic arguments is registered for its own model instead of the first argument (#2853)" + }, + { + "repo": "Arc", + "tag": "v22.31.0", + "date": "2026-09-28", + "rules": [], + "body": "## Added\n\n- A named Arc authorization policy can opt in to being evaluated for unauthenticated callers: `AddArcAuthorizationPolicy(name, evaluatesAnonymous: true)`. The policy then receives an empty, unauthenticated principal and decides whether to allow the guest. This works the same with Arc's `[Authorize]` and ASP.NET Core's `[Authorize(Policy = ...)]`, for commands, queries and observable queries. (#2761)\n- `AddArcAnonymousAspNetAuthorizationPolicy(name)` gives the same opt-in to a policy registered through ASP.NET Core. It is checked at startup: the name must resolve to exactly one ASP.NET Core policy that doesn't require an authenticated user. Otherwise startup fails with `InvalidAuthorizationConfiguration`. (#2761)\n\n## Changed\n\n- Policies without the opt-in behave as before and still require an authenticated caller. Role requirements and authentication-scheme requirements always require an authenticated caller, even when combined with an opted-in policy. If several policies apply, a guest is only evaluated when every one of them opted in. (#2761)\n- Arc's method-level authorization replaces the type-level declaration. A method with an opted-in policy is therefore reachable by guests even when its type requires a role. The authorization docs now call this out. (#2761)\n\n## Security\n\n- For commands and queries protected by a policy or authentication scheme, Arc checks the caller and the authorization requirements again immediately before the handler, query method or controller action runs, and denies the request if either changed after the policy verdict. (#2761)\n- Missing or conflicting authorization configuration now denies the request with Unauthorized (403 at the HTTP endpoints) instead of running it or returning a server error, including when a custom command or query pipeline is registered. (#2761)\n\nDecision 0006 records this and amends decision 0004.\n" + }, + { + "repo": "Arc", + "tag": "v22.30.1", + "date": "2026-09-27", + "rules": [], + "body": "## Changed\n\n- Update the Chronicle integration to Cratis.Chronicle 19.13.0, with the Cratis.Fundamentals, Cratis.Metrics and MongoDB driver versions it requires.\n" + }, + { + "repo": "Arc", + "tag": "v22.30.0", + "date": "2026-09-27", + "rules": [], + "body": "## Added\n\n- `IFallbackAuthorizationEvaluator` supplies baseline authorization requirements for commands and queries that declare none of their own. It is consulted only when neither the method nor the type has an explicit declaration, so `[AllowAnonymous]` members are no longer ambiguous with a baseline. Commands consult only the `Type` overload. MVC controller actions, including direct HTTP and WebSocket requests to controller-based queries, stay on ASP.NET Core authorization: use `AuthorizationOptions.FallbackPolicy` there (#2751)\n- `ReceivedAt` on `CommandContext`, `QueryContext` and `AuthorizationPolicyContext`, plus an injectable `IOperationContextAccessor`, expose the time Arc received the operation. It is captured with `TimeProvider` at Arc's transport dispatch, before binding, or at pipeline entry for programmatic calls, and per subscribe operation for streams. It is Arc's receipt time, not network arrival. `ReceivedAt` takes part in record equality, so contexts from different receipts compare unequal (#2767)\n- Query filters can set `SubscriptionScope` on the query context. Arc snapshots it after filtering and before the query runs, and gives each emission guard an independent copy, so guards can base decisions on membership that the arguments don't carry. Only state that Arc's JSON options serialize is kept, and a scope that cannot be represented is rejected with `InvalidSubscriptionScope`. The scope is always `null` for controller-based observable queries (#2768)\n\n## Fixed\n\n- An authentication scheme or policy supplied by a fallback evaluator is applied before command context values and execution scopes are built, instead of failing every such command (#2751)\n- The ambiguity error for conflicting authorization declarations names the conflicting evaluators (#2751)\n- Observable queries over collections whose items have no `Id` send complete snapshots instead of unreliable change sets, and log one warning per subscription explaining that deltas need a stable identity. Collections with an `Id` are unchanged (#2428)\n- The manual query pipeline composition recipe registers `AuthorizationDeclarations`, which the pipeline needs to authorize queries (#2753)\n" + }, + { + "repo": "Arc", + "tag": "v22.29.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- JavaScript `RuleBuilder.withSeverity` and `PropertyRule.withSeverity` APIs for explicit per-rule validation severity in generated command validators.\n\n## Fixed\n\n- Command filters continue past non-blocking validation results, so a later Error rejects execution and preflight validation even when an earlier Warning is allowed (#2817).\n- Client-side command validation blocks only at the effective severity threshold; non-blocking results remain advisory, server preflight still runs, and CommandForm validity follows the validation verdict.\n- Generated validators emit severity calls only for non-Error rules, retaining compatibility with existing client libraries for default rules. Rules with runtime-dependent severity are left to the server. Documentation describes the default-severity assumption at build time.\n" + }, + { + "repo": "Arc", + "tag": "v22.28.1", + "date": "2026-09-26", + "rules": [], + "body": "## Changed\n\n- Query argument parsing is stricter: values that previously fell back silently now return HTTP 400, including `?flag=1` or `?flag=yes` for a boolean, overflowing integers, whitespace-only values, invalid nullable value types that used to become null, and invalid concept values (including nullable or defaulted concepts). Clients must send valid values or omit optional arguments.\n- The new public `InvalidQueryArgument` exception represents an invalid supplied query argument; `QueryHttpExtensions.GetQueryArguments` can now throw it. Invalid collection elements use the same named malformed-request validation shape.\n\n## Fixed\n\n- Invalid scalar, concept, and collection arguments on GET and QUERY endpoints now return HTTP 400 with a named malformed-request validation error instead of invoking model-bound or controller queries with default values or reporting invalid values as missing. (#2794)\n" + }, + { + "repo": "Arc", + "tag": "v22.28.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- EF Core `Observe`, `ObserveSingle` and `ObserveById` accept `ignoreQueryContext: true` for auxiliary sources in composed observable queries. Opted-in sources read their full filtered results without client paging or sorting and do not update `TotalItems`; the primary observation keeps its paging, sorting and count. Existing composed queries must opt in on their auxiliary observations to get this fix, as with the MongoDB overloads in v22.27.0 (#2804)\n- `AuthorizationPrincipalScope` is public, so a query or command pipeline can be composed without `AddCratisArc`. The query pipeline guide shows the registrations it needs (#2753)\n" + }, + { + "repo": "Arc", + "tag": "v22.27.2", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- API path joins collapse repeated separators at segment boundaries without damaging `http://`, `https://`, or leading and trailing slashes (#2593)\n- Path-prefix hosting guidance now covers `apiBasePath`, router basename, ASP.NET Core `UsePathBase` ordering, and Arc.Core static files and fallback (#2593)\n\n## Deprecated\n\n- `` is deprecated because it does not configure routing or API requests; its context value remains available for compatibility (#2593)\n" + }, + { + "repo": "Arc", + "tag": "v22.27.1", + "date": "2026-09-26", + "rules": [], + "body": "## Changed\n\n- Creating several Arc Core containers over the same types is faster: model-bound command, query and read-model discovery runs once per type set and is reused. A local .NET 10 Release measurement of 50 containers over 581 types dropped from 58.8 ms to 23.7 ms median, about 60% faster (#2673)\n- Read models no longer expose private static methods or compiler-generated methods as queries through runtime discovery; public and internal static query methods remain supported (#2651)\n\n## Fixed\n\n- Local functions and other compiler-generated methods inside read models no longer create query endpoints, generated proxies or route conflicts. The runtime, the metadata generator, the proxy generator and the `QueryScenario` testing helpers apply the same rule (#2651)\n- Proxies are no longer generated for query methods returning a collection that is not of the read model, such as `string` or `IEnumerable`, which the runtime never served; covariant collections of a derived read model still get proxies (#2651)\n- Multiplexed SSE and WebSocket observable queries keep paging totals on an empty first result and pick up updated totals on later results, without mixing metadata between subscriptions (#2707)\n" + }, + { + "repo": "Arc", + "tag": "v22.27.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- MongoDB `Observe`, `ObserveSingle`, and `ObserveById` accept `ignoreQueryContext: true` for auxiliary sources in composed observable queries. Opted-in sources read their full filtered results without client paging or sorting and do not update `TotalItems`; the primary observation keeps its paging, sorting and filtered count. Existing composed queries must opt in on their auxiliary observations to get this fix; the existing overloads remain available (#2756)\n\n## Fixed\n\n- ARCCHR0006 recognizes inherited `[Replay]` handlers when checking whether reactor command execution has a replay decision, including handlers on generic base classes (#2790)\n" + }, + { + "repo": "Arc", + "tag": "v22.26.1", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- The NuGet and npm packages for the changes listed in [v22.26.0](https://github.com/Cratis/Arc/releases/tag/v22.26.0) are published with this release. Publishing v22.26.0 stopped before any package was pushed, because two independently merged changes together broke the TypeScript build; this release contains everything in v22.26.0 and restores the build.\n" + }, + { + "repo": "Arc", + "tag": "v22.26.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- Select a tenant with `ITenantScope.Begin` for commands, validation, queries, and tenant-scoped reads in background work. The selection takes precedence over request tenant resolution inside the current flow. Dispose to restore the previous tenant; disposal is idempotent, and disposing an outer scope early leaves an active inner tenant selected until it ends (#2747)\n - Custom `ITenantIdAccessor` registrations are preserved whether they are registered before or after Arc. Explicit tenant scopes require Arc's `TenantIdAccessor` as the effective accessor; resolving `ITenantScope` with a custom accessor throws `ExplicitTenantScopeRequiresArcAccessor` instead of silently selecting a tenant that downstream services cannot see.\n - Independent HTTP requests reset inherited tenant selection when they enter Arc's request pipeline, before authentication and tenant-bound resolution. On ASP.NET Core this covers all application middleware, but not middleware added by another `IStartupFilter` registered before Arc's. Observable emissions use the subscriber's tenant, not a producer's explicit tenant, on every streaming transport. Tasks and timers started inside a tenant scope keep its tenant after the scope is disposed: do not carry that captured context into unrelated work.\n- Configurable access to the command and query introspection catalogs (`/.cratis/commands` and `/.cratis/queries`) through `Cratis:Arc:Introspection` (`ArcOptions.Introspection`) (#2764):\n - `Enabled` (default `true`): set to `false` to leave both routes unmapped.\n - `RequireAuthentication` (default `false`): deny anonymous callers. Arc.Core returns 401; ASP.NET Core uses the configured scheme's challenge behavior.\n - `Roles` (comma-separated, any one grants access, requires `RequireAuthentication`): deny callers without a listed role. Arc.Core returns 403; ASP.NET Core uses the configured scheme's forbid behavior.\n - `TrustForwardedIdentityHeaders` (default `false`): lets the Microsoft Identity Platform header handler authenticate protected catalog requests; enable it only behind a trusted ingress that replaces those headers.\n\n Defaults are unchanged: both catalogs stay mapped and anonymous. `/.cratis/identity-details/schema` is not affected. The JVM backend has no equivalent options.\n\n## Changed\n\n- Plain minimal API responses that return concepts change from objects such as `{\"value\":\"...\"}` to primitive values such as `\"...\"`, matching Arc-generated endpoints and the TypeScript proxies. To keep the object shape for a concept, register your own `JsonConverter` with `ConfigureHttpJsonOptions`; it takes precedence for individual concepts, concept collections, and, when it implements property-name conversion, dictionary keys, whether registered before or after `AddCratisArc` (#2755)\n- Only concept-related converters are added to ASP.NET Core's HTTP JSON options. Your converters keep precedence and the naming policy is unchanged; enums (including `long`-backed values), `DateOnly`, `TimeOnly`, `Uri`, and other non-concept types keep ASP.NET Core's defaults on plain minimal APIs (#2755)\n- A concept query parameter is always supplied by the caller. If you injected a value into a query by registering a concept type in dependency injection, inject a service or accessor instead (#2757)\n- Each JavaScript package's `package.json` now declares `\"type\": \"module\"`. `import` still targets `dist/esm`; `require` still targets `dist/cjs`, which has its own `\"type\": \"commonjs\"` marker. Applications do not need to change their import syntax (#2775)\n- Custom single-input CommandForm fields receive the field id; put it on your input so the label is associated with it. For multi-input fields, set `groupRole` in `asCommandFormField` and label each option individually (#2661)\n- Proxy generation looks for concept validators in dependencies that reference Arc Core directly or through another assembly, including validators derived from a shared validator base. It rejects multiple discoverable validators for a requested type, matching Arc's runtime discovery; unrelated validators no longer affect generation. A partial type-load failure in the generated assembly or the requested model's assembly fails generation; other dependencies contribute the types that load and produce a warning naming the dependency. Failures to load executable counterparts or to construct validators can still leave rules ungenerated (#2766)\n\n## Fixed\n\n- Concept-typed (`ConceptAs`) query arguments, including nullable ones, are bound from the request on model-bound and controller-based queries. Previously they were resolved from the dependency injection container whenever it reported the type as a service, which could fail with HTTP 500. An explicit `[FromServices]` still resolves from the container on controllers. Missing or invalid required concept arguments on controller-based queries are rejected instead of reaching the action as `null` (#2757)\n- Collection query arguments sent with GET or QUERY no longer run the query with silently changed values: an invalid element, or `null` in a collection of a non-nullable element type, fails validation with HTTP 400 instead of becoming the element's default value. Nested collections are rejected the same way, including on controller-based queries, and raw collection arguments rejected by the query pipeline report a validation failure naming the argument (#2789)\n- QUERY bodies keep array element boundaries and empty string elements, so `[\"\", \"x\"]` binds as two strings rather than one comma-joined value or `null` (#2789)\n- Collections of `DateOnly`, `TimeOnly`, `Uri`, nullable value types, and `HashSet` are bound from the request instead of being resolved from dependency injection or failing with HTTP 500. JSON node collections bind from QUERY bodies; GET rejects `JsonObject` and `JsonArray` collections with HTTP 400 because comma-delimited values cannot represent them. Generated proxies classify the same element types as arguments (#2789)\n- Concepts on plain minimal APIs bind from and serialize to primitive JSON values, like Arc-generated endpoints: individual concepts, concept collections, and dictionaries keyed by concepts (#2755)\n- OpenAPI describes concept values and collections as primitives and arrays, and resolves recursive dictionary and polymorphic references. A concept with your own converter keeps an unspecified schema unless you add a matching schema transformer. Enum schemas follow the effective numeric or string converter (#2755)\n- Named imports from `@cratis/arc`, `@cratis/arc.react`, `@cratis/arc.react.mvvm` and `@cratis/arc.vite` resolve for TypeScript ESM (`.mts`) and CommonJS (`.cts`) consumers using `moduleResolution: Node16` or `NodeNext`, as well as Bundler consumers. Published declarations use fully specified `.js` relative imports, and each export selects declarations matching its `import` or `require` entry (#2775)\n- Clicking a CommandForm field's auto-rendered label focuses its input, and the label gives the input its accessible name. Radio-group titles name the group without selecting its first option or firing a change event; untitled groups are rendered without an unnamed group role (#2661)\n- `DbSet.Observe()`, `ObserveSingle()`, and `ObserveById()` support primary keys mapped as EF Core indexer properties, including shared-type entities backed by property bags. `ObserveById()` keeps filtering by a mapped public CLR `Id` when there is one and otherwise uses a single, non-shadow primary key; composite keys and shadow-only keys without a CLR `Id` are rejected when observation is set up. Entities with a shadow primary key stay observable through a mapped CLR `Id`, and query-context sorting works for mapped indexer properties (#2741)\n- Validation rules from a `ConceptValidator` declared in a referenced project or package appear in the generated TypeScript proxies of commands and queries that use the concept, even when the command, concept and validator live in three different assemblies (#2766)\n\n## Security\n\n- Identity responses can no longer be stored by browser or intermediary caches and served to another user. `/.cratis/me`, `/.cratis/users` and `/.cratis/tenants` responses, including 401 and 403, and any response that writes the identity cookie carry `Cache-Control: no-store, private` and `Vary: Cookie` on both the ASP.NET Core and Arc.Core hosts. An existing `Vary` value, such as `Origin` from CORS, is kept. Responses a cache stored before you upgrade are not invalidated; evict cached identity responses if a cache in front of your application may have stored them (#2665)\n- Protected introspection catalogs require an authenticated user even when the ASP.NET Core default authorization policy permits anonymous callers or an unauthenticated principal carries a listed role (#2764)\n- A protected introspection catalog cannot be opened with forged `x-ms-client-principal` headers. On ASP.NET Core, startup fails with `InvalidIntrospectionConfiguration` when protection is enabled and the header handler, including subclasses, schemes that forward to it, or schemes on the default authorization policy, could authenticate these endpoints, unless `TrustForwardedIdentityHeaders` is set; this also applies when mapping directly with `AspNetCoreEndpointMapper`. Without the opt-in, the header handler does not authenticate protected catalog requests, including when a route group or named policy supplies its scheme, and rebuilding the principal's identity cannot bypass this. If header authentication succeeds before endpoint routing, the catalog returns 401. On the Arc.Core host the handler ignores those headers for protected catalogs unless trust is enabled (#2764)\n\n## Deprecated\n\n- `IEndpointMapper.MapIntrospectionEndpoints()` without options is obsolete; use the overload that takes the configured introspection options. Builds that treat warnings as errors report CS0618 (#2764)\n" + }, + { + "repo": "Arc", + "tag": "v22.25.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- A model-bound command can declare `[BlockOnValidationSeverity(ValidationResultSeverity.Warning)]`, or `Information`, so that warning- or information-level validation failures reject it. The declared severity is the lowest severity that blocks. It applies to HTTP execution, validation, `Provide()` and in-process `ICommandPipeline` execution, and derived command records inherit it. (#2760)\n- Generated TypeScript proxies carry the declared severity and apply the same threshold on the client. The server stays authoritative. An out-of-range declared severity fails proxy generation.\n\n## Changed\n\n- For a command that declares a blocking severity, the caller's `X-Allowed-Severity` (or `allowedSeverity`) can make validation stricter, never looser. A failure with `Unknown` severity also rejects such a command. Commands without the attribute behave as before. This deliberately differs from the caller opt-out for controller actions in #21; decision 0005 records why.\n" + }, + { + "repo": "Arc", + "tag": "v22.24.0", + "date": "2026-09-26", + "rules": [], + "body": "## Added\n\n- `QueryScenario` runs model-bound snapshot queries in-process through the same hosted pipeline as the HTTP endpoint, including argument conversion, authorization, validation, interceptors and owned query scopes. The principal comes from an `ICurrentPrincipalAccessor` registered in `Services`, as with `CommandScenario`. (#2762)\n- `IQueryScenarioExtender` is a seam for extending query scenarios. `Cratis.Arc.Chronicle.Testing` uses it to seed read models and event history for Chronicle-backed queries, including other read model types the query reads.\n\n- Query scenarios materialize lazy results that depend on scoped services before releasing the query scope, so the result stays readable after `Perform` returns.\n\nStreaming (observable) queries are rejected with `StreamingQueryNotSupported` before the query method runs. Queries restricted to an authentication scheme fail closed in-process, because scheme selection needs a live HTTP context.\n" + }, + { + "repo": "Arc", + "tag": "v22.23.8", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- Committing to an aggregate that already had events no longer fails with a concurrency violation when no one else wrote to it. Loading the aggregate left its expected position at zero, so any aggregate whose event source had a later event was rejected on every commit.\n- The expected position is now captured before the aggregate's events are read, so an event appended by someone else while the aggregate is loading is still detected as a conflict at commit instead of being silently overwritten.\n- For aggregates without event handlers, `IsNew` now reflects only the aggregate's own stream: events for the same event source in other streams no longer make it look existing.\n" + }, + { + "repo": "Arc", + "tag": "v22.23.7", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- Generated TypeScript query, observable query and command proxies no longer contain whitespace-only lines, so they pass whitespace lint rules without editing generated files (#2705)\n" + }, + { + "repo": "Arc", + "tag": "v22.23.6", + "date": "2026-09-26", + "rules": [], + "body": "## Fixed\n\n- GET queries honor paging and sorting parameters regardless of casing, so `sortBy`, `sortDirection`, `page` and `pageSize` sent by the generated TypeScript proxies are applied on both the ASP.NET Core and Arc.Core hosts; ordinary query argument matching is unchanged (#2758)\n" + }, + { + "repo": "Arc", + "tag": "v22.23.5", + "date": "2026-09-25", + "rules": [], + "body": "## Fixed\n\n- ARC0013 no longer reports static members reached through a concept type, such as a `NotSet` sentinel or a static factory, in validator rules; nullable instance dereferences like `RuleFor(c => c.Name.Value)` are still reported (#2658)\n" + }, + { + "repo": "Arc", + "tag": "v22.23.4", + "date": "2026-09-25", + "rules": [], + "body": "## Fixed\n\n- Model-bound queries no longer risk binding a dependency to the wrong parameter under concurrent first use. Parameters are classified once when the query is discovered and bound by position, instead of by comparing reflection metadata instances across calls (#2765)\n" + }, + { + "repo": "Arc", + "tag": "v22.23.3", + "date": "2026-09-25", + "rules": [], + "body": "## Fixed\n\n- A frontend using `useIdentity`/`IdentityProvider` no longer gets stuck showing \"not signed in\" while the tab stays open. When a session that was active ends server-side (expiry, revoked access) an identity refresh now reloads the page instead of quietly settling into an unset identity nothing on the page could act on - the reload sends the browser through the ordinary sign-in redirect (see Cratis/AuthProxy#136) instead of leaving every command failing silently against a session that is already gone.\n" + }, + { + "repo": "Arc", + "tag": "v22.23.2", + "date": "2026-09-25", + "rules": [], + "body": "## Changed\n\n- During a scenario, code that reads Arc's process-wide provider directly, such as the MongoDB and EF Core `Observe()` extensions, now sees the host's provider, or throws `ServiceProviderNotConfigured` when no host has set one, instead of using the scenario's provider. Command validation in a scenario is unaffected (#2763)\n\n## Fixed\n\n- `CommandScenario` no longer replaces Arc's process-wide service provider. A host that runs in-process specs keeps resolving services from its own provider after a scenario is disposed, and overlapping scenarios no longer interfere with each other (#2763)\n" + }, + { + "repo": "Arc", + "tag": "v22.23.1", + "date": "2026-09-25", + "rules": [], + "body": "## Fixed\n\n- An `EventForEventSourceId` returned from a command now keeps its `Occurred` and `Tags` when appended, for a single wrapper, a collection or mixed collection of wrappers, and `EventsWithConcurrencyScopes`, on both the transactional and the immediate append path. Previously both values were silently dropped, so imported or backfilled events were recorded with the append time and without their per-event tags (#2570)\n\nRuntime tags supplied through the command context, the original subject of #2570, are not part of this change.\n" + }, + { + "repo": "Arc", + "tag": "v22.23.0", + "date": "2026-09-24", + "rules": [], + "body": "## Added\n\n- Register scoped, asynchronous authorization policies with `IAuthorizationPolicy` and `AddArcAuthorizationPolicy()` on either Arc host (#2736).\n- Use ASP.NET Core policies and authentication schemes on model-bound commands, queries, and observable subscriptions, following the [authorization contract](https://github.com/Cratis/Arc/blob/main/decisions/0004-evaluate-authorization-policies-asynchronously.md) (#2736).\n\n## Fixed\n\n- Observable streams retain their identity and service lifetime through in-flight emissions, including disconnects and failing cancellation or disposal callbacks.\n- Authorization diagnostics now describe contradictory attributes accurately; ARC0021 is limited to authentication schemes without Arc’s ASP.NET Core integration.\n\n## Security\n\n- `Policy` and `AuthenticationSchemes` no longer silently fall back to authentication-only checks. Unknown, ambiguous, malformed, or unsupported requirements fail closed; applications using previously ignored settings must register valid policies and schemes before startup (#2736).\n- Scheme-selected execution isolates the effective principal, tenant, and scoped services. Unsafe caller-supplied providers and identity-changing nested scheme calls are rejected rather than reusing services bound to another identity. Standalone Core does not support named authentication schemes (#2736).\n" + }, + { + "repo": "Arc", + "tag": "v22.22.0", + "date": "2026-09-23", + "rules": [], + "body": "## Added\n\n- `CratisProxiesUseProxyFileSuffix` names generated TypeScript files `Name.proxy.ts` instead of `Name.ts`, so generated proxies can be told apart from hand-written TypeScript in the same folders. Imports between generated files and `index.ts` barrels follow the suffix; imports of modules you mapped yourself are left as mapped. Switching the option on or off replaces the previously generated files. Off by default." + }, + { + "repo": "Arc", + "tag": "v22.21.0", + "date": "2026-09-23", + "rules": [], + "body": "## Added\n\n- Analyzer `ARC0021` reports a `Policy` or `AuthenticationSchemes` on an authorization attribute of a model-bound command or read model. Arc enforces authentication and roles but evaluates neither of these, so the artifact is less protected than its attribute reads (#2736)\n- `IAuthorizationAttributeEvaluator.GetAuthorizationRequirements`, so an evaluator can report every attribute it finds. It defaults to the existing single answer, so existing implementations keep working\n\n## Changed\n\n- Analyzer `ARC0020` now reports an ASP.NET Core authorization attribute only in a project without Arc's ASP.NET Core integration (`Cratis.Arc`), since that integration now enforces it (#2719)\n\n## Fixed\n\n- The authorization attribute evaluator specs could not fail: a null-conditional assertion skipped the assertion whenever the evaluator found nothing\n\n## Security\n\n- `[Authorize]` and `[AllowAnonymous]` from `Microsoft.AspNetCore.Authorization` are enforced again on model-bound commands and read models when Arc is hosted on ASP.NET Core. Since v18.2.0 a model-bound artifact protected only with Microsoft's `[Authorize]` was open to every caller — over HTTP, over hub subscriptions and through direct pipeline execution — because the evaluators meant to honour it read Arc's same-named attributes instead (#2719)\n- Every authorization attribute on a declaration now applies. Previously only the first one found was evaluated, so a second, stacked requirement — `[Roles(\"Admin\")]` together with `[Authorize(Roles = \"Auditor\")]` — was silently dropped\n- A declaration marked both `[AllowAnonymous]` and `[Authorize]`/`[Roles]` is always rejected as ambiguous. Previously it either threw or admitted anonymous callers depending on which evaluator was discovered first (#2714)\n" + }, + { + "repo": "Arc", + "tag": "v22.20.0", + "date": "2026-09-23", + "rules": [], + "body": "## Added\n\n- Analyzer `ARC0019` reports `[AllowAnonymous]` declared on the same class or method as `[Authorize]` or `[Roles]`. Arc resolves that contradiction differently depending on evaluator order — rejecting the call in one order and admitting anonymous callers in the other — so the declaration now fails loudly at build time instead. Overriding a class on one query method is supported and not reported (#2714)\n- Analyzer `ARC0020` reports `[Authorize]` or `[AllowAnonymous]` from `Microsoft.AspNetCore.Authorization` on a model-bound `[Command]` or `[ReadModel]`. Arc enforces only its own `Cratis.Arc.Authorization` attributes on model-bound artifacts, so the ASP.NET Core attribute protects nothing: a command marked with it is open to every caller. Use the Arc attribute of the same name. Controller-based commands and queries are unaffected (#2719)\n\nBoth rules are warnings. A project that treats warnings as errors will fail to build where either applies — which, for ARC0020, is a command or query that is currently unprotected.\n" + }, + { + "repo": "Arc", + "tag": "v22.19.2", + "date": "2026-09-23", + "rules": [], + "body": "## Changed\n\n- `Cratis.Chronicle`/`Cratis.Chronicle.AspNetCore`/`Cratis.Chronicle.Testing` bumped from `18.2.0` to `19.4.2` to pick up `[Encrypted]` and the read-model release fix it depends on; `Cratis.Fundamentals`/`Cratis.Metrics.Roslyn` bumped from `7.19.2` to `7.19.3` alongside it\n\n## Fixed\n\n- A read model injected into a command's `Handle()`, `Provide()`, or a `CommandValidator` is now released (its `[PII]`/`[Encrypted]` properties decrypted) regardless of whether the *command* declares a `Subject`/`[Subject]`/`ICanProvideSubject` — most commands rely on Chronicle's implicit event-source-id subject fallback instead, which was never checked, so release was silently skipped for the common case independent of what the injected read model actually carried\n- A namespace- or global-scoped `[Encrypted]` value (Cratis/Chronicle#4095) now reaches release correctly even on a read model that has no `Id`/`[Subject]` at all — the ordinary shape for that kind of value, since its key does not depend on a subject\n" + }, + { + "repo": "Arc", + "tag": "v22.19.1", + "date": "2026-09-22", + "rules": [], + "body": "## Fixed\n\n- Prevent CommandForm from executing while custom field validation errors are present, including immediate and imperative submissions. (#2723)\n- Keep validation messages and displayed submission results consistent when custom errors change or clear. (#2723)\n" + }, + { + "repo": "Arc", + "tag": "v22.19.0", + "date": "2026-09-22", + "rules": [], + "body": "## Summary\n\nThree places where the quieter of two possible behaviours was also the less safe one, and where the C# implementation disagreed with the JVM one over the shared HTTP contract.\n\n## Added\n\n- `SortDirectionIsNotRecognized`, the validation failure raised for an unrecognized sort direction (#2715)\n\n## Changed\n\n- A query request reader that rejects input as invalid now surfaces as the documented `malformedRequest` validation failure rather than an exception envelope. Reader failures that are not validation failures are unchanged (#2715)\n- The shared HTTP contract documents SSE connection ownership and the sort direction vocabulary as single behaviours, with the remaining narrower difference in what each implementation compares for ownership stated explicitly (#2713)\n\n## Fixed\n\n- `sortDirection=descending` sorted ascending. `asc`, `ascending`, `desc` and `descending` are all accepted case-insensitively now, and any other value is rejected as `malformedRequest` with 400 instead of being answered in the opposite order to the one asked for (#2715)\n\n## Security\n\n- SSE subscribe and unsubscribe now verify that the caller owns the connection they name. The identity that opened the stream is captured once and compared on each control request; a mismatch returns 404, exactly as an unknown connection id does, so the endpoints never confirm another caller's connection exists. Previously a caller holding another connection's id could attach a subscription whose results were written into that caller's stream, or cancel their subscriptions (#2713)\n" + }, + { + "repo": "Arc", + "tag": "v22.18.0", + "date": "2026-09-22", + "rules": [], + "body": "## Added\n\n- `HttpRequestContextEndpointExtensions` on `Cratis.Arc.Http`: `SetEndpointMetadata`, `GetEndpointMetadata`, and `AllowsAnonymous()` on `IHttpRequestContext`, so an `IAuthenticationHandler` can tell whether the endpoint it is about to authenticate allows anonymous access (#2706)\n\n## Fixed\n\n- `AuthenticationMiddleware` now sets the matched endpoint's metadata on the request context before any `IAuthenticationHandler` runs, so a handler can check `context.AllowsAnonymous()` and return `AuthenticationResult.Anonymous` instead of rejecting - and logging a rejection for - a request the endpoint never required a credential for, such as a Kubernetes health probe (#2706)\n" + }, + { + "repo": "Arc", + "tag": "v22.17.0", + "date": "2026-09-22", + "rules": [], + "body": "## Added\n\n- `IHaveKnownChanges` lets an observable collection query state which of its items changed, so the delta for an emission is the one the provider already knows rather than one rediscovered by comparison. An emitted collection is still an `IEnumerable` of the read model, so existing subscribers are unaffected, and a provider that cannot know what changed continues to be served by the comparison fallback.\n\n## Fixed\n\n- An observable collection query backed by MongoDB no longer costs time proportional to the size of the collection every time a single document changes. The change stream already names the changed document, and the observable already decides what that meant for the observed set; that verdict is now carried on the emission instead of being discarded and then rediscovered by serializing every item of the new snapshot and the previous one.\n- The comparison fallback no longer serializes every item twice on every emission. Unchanged items are the very same instances the previous snapshot held, so reference equality settles most comparisons and record value equality settles the rest; serializing is now the last resort for an item type that overrides neither.\n- Removing an item from a **paged** observable query now reports the document pulled onto the page to replace it, so a client applying the delta no longer ends up one row short.\n" + }, + { + "repo": "Arc", + "tag": "v22.16.1", + "date": "2026-09-16", + "rules": [], + "body": "## Fixed\n\n- Treat command responses containing neither events nor concurrency scopes as successful no-ops, including inside transactions, instead of attempting an empty append. Responses with concurrency scopes continue through the existing concurrency-validation path.\n- Avoid disposed cancellation-token errors when MongoDB observable queries finish or their final subscriber disconnects.\n" + }, + { + "repo": "Arc", + "tag": "v22.16.0", + "date": "2026-09-16", + "rules": [ + "Heading not allowed", + "Review, verification or provenance note", + "Section heading level" + ], + "body": "# Summary\n\n`CommandForm` resolved a field's property by matching the first `.member` in its value accessor's source text. A field whose accessor reads the property dynamically has no such member, so every such field resolved to the same name.\n\nThis surfaced through `Components`' `AutoCommandForm`, which builds one accessor per runtime property descriptor — every generated field compiles to `instance => instance[descriptor.name]`. All fields bound to the first match, so a date-typed field received another property's string value and the whole form unmounted on first edit with `date.getTime is not a function`.\n\nNo tracking issue exists; found while exercising a generated form against Arc 22.6.2. Happy to file one if preferred over this description.\n\n## Added\n\n- `fieldName` on `BaseCommandFormFieldProps`, so a field can declare the command property it binds to instead of relying on inference from the accessor.\n\n## Fixed\n\n- Fields whose value accessor reads a property dynamically no longer all bind to the same command property, and no longer feed one field's value to another field's component.\n\n## Notes for review\n\n- Inference is unchanged and remains the fallback: every existing `command => command.property` caller behaves exactly as before. The prop is additive and optional.\n- `cloneElement` already overwrote `fieldName`, so a caller-supplied value was previously inert; that overwrite is now a no-op when the caller supplies one.\n- `extractPopulatedValues.ts` and `ValidationMessage.tsx` keep their own copies of the same heuristic and would still mis-resolve a computed accessor. Left alone deliberately — worth a follow-up, not part of a minimal fix.\n- Optional hardening not included: `getPropertyName` mis-binds silently here because the matched name is a real property, so the existing dev warning never fires. A guard rejecting a match that is not a static member access on the parameter would make this loud rather than silent.\n- Requires the matching `Components` change to take effect for generated forms: Cratis/Components `fix/auto-command-form-field-binding`.\n\n## Verification\n\n- New spec `for_CommandFormFields/when_field_accessor_is_a_computed_property.ts`: two fields with identical computed accessor source text must bind to their own properties, and editing one must leave the other untouched. Verified to fail with the change stashed and pass with it applied.\n- `commands/CommandForm` suite: 14 files, 35 tests passing. Full `Arc.React` run: 130 files, 335 tests passing." + }, + { + "repo": "Arc", + "tag": "v22.15.0", + "date": "2026-09-15", + "rules": [ + "Issue reference position" + ], + "body": "## Summary\n\nA batch of reported issues. Most were silent failures — things that went wrong without telling anyone. Two change runtime behavior rather than just a diagnostic and are worth reading before upgrading: collection query arguments now actually bind, and a single-instance observable query now notifies when the observed document is gone.\n\n## Added\n\n- `detailsType` on ``, forwarded to the identity provider, so identity details are deserialized into their declared type instead of staying raw JSON (#2583)\n- `IEnumerable` is supported as a model-bound query argument (#2571)\n- Documentation for the `skip-generated-proxies` processor in `@cratis/eslint-plugin-arc`, which excludes generated proxies from linting without a hand-maintained path allow-list (#2613)\n- Reference pages for ARCCHR0004 and ARCCHR0006 (#2633, #2632)\n\n## Changed\n\n- A single-instance observable query — `ObserveSingle`/`ObserveById`, on both MongoDB and EF Core — now emits `default` when no document matches: after the document is deleted, after an update moves it out of the filter, and when the initial query finds nothing. It previously held the last known value forever with no signal. Guard with `hasData` / `isReady` on the client. Note a read model with `[RemovedWith]` hard-deletes its document, so \"the read model was removed\" is this case. (#2642)\n- Direct-mode WebSocket and SSE observables forward a null emission instead of dropping it, matching what the multiplexed transport already did (#2642)\n\n## Fixed\n\n- Collection query arguments — `IEnumerable`, `IEnumerable`, and similar — are now bound from the query string. They were classified as injected dependencies, because the DI container reports it can supply any `IEnumerable`, so the query ran with an empty collection and returned an empty answer while reporting success. (#2652)\n- `useIdentity(type)` deserializes with the type it is given, as its documentation already promised. Previously the type was used only to decide which argument was the default and was then discarded, so any `DateOnly`, `Guid`, `ConceptAs`, or nested value on identity details was raw JSON at runtime. (#2583)\n- ARCCHR0004 only flags an `[EventType]` id that repeats the type name. An id that *differs* is the supported way to rename an event record while events already in the log keep resolving; following the old advice to remove it orphaned every stored event of that type. (#2633)\n- ARCCHR0006 no longer flags private helper methods that reactor dispatch would never select, and is suppressed when a `[Replay]` handler is declared for the same event type. Its message now states that `[OnceOnly]` fires once per event source, not once per event — so a handler for an event that recurs on the same source would run only for the first one. (#2632, #2615)\n- The proxy generator no longer deletes hand-written `index.ts` exports that point at a sibling folder or an npm package. It only ever wrote exports for files it generated, so those lines were always hand-written; `index.ts` is a merge target, not generated output. (#2621)\n- A single-instance query proxy's `defaultValue` is typed as the model rather than `{} as any`, which tripped `@typescript-eslint/no-explicit-any` in consuming applications (#2613)\n- A nullable enum query parameter no longer generates a spurious class from `Nullable` alongside the enum (#2571)\n" + }, + { + "repo": "Arc", + "tag": "v22.14.2", + "date": "2026-09-15", + "rules": [ + "Review, verification or provenance note", + "Section order" + ], + "body": "## Fixed\n\n- **`--emit-interfaces` had no MSBuild property**, which left it unusable for the thing it exists for: a package generating its own TypeScript model as part of its build cannot reach a flag only the command line accepts. `CratisProxiesEmitInterfaces` now maps to it, alongside every other option.\n\n## Added\n\n- **Documentation for the mode** — what it emits, when to use it, when not to, and that it removes the decorators and their import but *not* an import of a type Fundamentals genuinely provides, such as `TimeOnly`. Wired into the configuration index and table of contents.\n\n## Changed\n\n- **The library-mode page no longer claims abstract classes are skipped.** They are not collected directly, but a concrete type's base class is followed — so an inheritance chain arrives intact rather than with its roots missing, which is what makes the mode usable for a model with an abstract hierarchy at all. Open generic definitions and their type parameters are the things actually skipped.\n\n---\n\nFollow-up to #2636, which added the flag and the emission mode but stopped at the CLI. Found while trying to adopt it in Cratis/Scene, whose `Cratis.Scene.Model` is exactly the intended consumer: a deliberately dependency-free package that has been hand-maintaining its TypeScript mirror because generation was not available to it.\n\nThe targets file is packed into both `build` and `buildTransitive`, so the single edit covers direct and transitive consumers.\n\nVerified: 51 projects build in Release with 0 errors and 0 warnings.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)" + }, + { + "repo": "Arc", + "tag": "v22.14.1", + "date": "2026-09-14", + "rules": [], + "body": "## Summary\n\n`Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated from `18.1.0` to `18.2.0`, picking up [18.1.1](https://github.com/Cratis/Chronicle/releases/tag/v18.1.1), [18.1.2](https://github.com/Cratis/Chronicle/releases/tag/v18.1.2), [18.1.3](https://github.com/Cratis/Chronicle/releases/tag/v18.1.3), [18.1.4](https://github.com/Cratis/Chronicle/releases/tag/v18.1.4), [18.1.5](https://github.com/Cratis/Chronicle/releases/tag/v18.1.5), [18.1.6](https://github.com/Cratis/Chronicle/releases/tag/v18.1.6) and [18.2.0](https://github.com/Cratis/Chronicle/releases/tag/v18.2.0).\n\n## Added\n\n- Chronicle 18.2.0: Integration specifications cover empty event-sequence, job, failed-partition, webhook, and event-store-subscription results.\n\n## Changed\n\n- `Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated to `18.2.0`\n- Chronicle 18.2.0: Generated gRPC messages initialize non-nullable values and collections at the contract boundary.\n\n## Fixed\n\n- Chronicle 18.1.5: Preserve empty sequence-query collections through protobuf round-trips so .NET callers receive an empty collection rather than null. (cratis/chronicle#4025)\n- Chronicle 18.1.5: Complete a reactor that returns no follow-up events without submitting an invalid empty append.\n- Chronicle 18.1.5: Preserve separator characters in observer partition identifiers when routing a connected observer.\n- Chronicle 18.1.6: Preserve empty query collections after .NET client deserialization (cratis/chronicle#4025)\n- Chronicle 18.1.6: Deserialize populated query collections without throwing (cratis/chronicle#4025)\n- Chronicle 18.1.6: Keep query results independent across successive and concurrent queries (cratis/chronicle#4025)\n- Chronicle 18.2.0: Client APIs now return empty collections or initialized response objects instead of compensating for null wire values in converters.\n" + }, + { + "repo": "Arc", + "tag": "v22.14.0", + "date": "2026-09-10", + "rules": [], + "body": "## Summary\n\nKeep command decisions focused on what should happen by returning command operations. Arc executes the declared work and manages optional compensation without application-written rollback stacks, while protecting already-committed and uncertain business outcomes.\n\nThis is a backend-only addition: HTTP and TypeScript command-result contracts remain unchanged. Initial support covers flat, sequential commands; compensation is best-effort and in-process, not a durable workflow.\n\n## Added\n\n- `ICommandOperation` with dependency-injected `Execute()` and optional `Compensate()`, plus immutable `CommandOperations` for ordered zero-to-many work.\n- Automatic failure handling, reverse-order compensation, independent cleanup cancellation, and backend recovery observations for compatible command boundaries, including Chronicle's deferred event commit.\n- Compiler diagnostics and generated invocation support for operation declarations, with operations excluded from generated client responses.\n- `CommandScenario` cancellation support and assertions over real operation execution and compensation.\n- Dedicated usage, migration, troubleshooting, and testing guides, with runnable examples for partial failure, cancellation, and Chronicle commit rejection.\n- Contextual recommendations throughout Arc's existing command, tutorial, validation, and storage guidance, preserving supported direct calls and durable reactor/workflow boundaries.\n" + }, + { + "repo": "Arc", + "tag": "v22.13.1", + "date": "2026-09-09", + "rules": [], + "body": "## Summary\n\n`Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated from `18.0.0` to `18.1.0`, picking up [18.1.0](https://github.com/Cratis/Chronicle/releases/tag/v18.1.0).\n\n## Added\n\n- Chronicle 18.1.0: Report substituted compliance behavior in read-model scenarios, including strict-fidelity rejection for projections and reducers.\n- Chronicle 18.1.0: Add typed scope enforcement through `IUniqueEventTypesConstraintsStorage.IsAllowedWithinScope`, preserving the original method for existing storage providers.\n\n## Changed\n\n- `Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated to `18.1.0`\n- Chronicle 18.1.0: Built-in providers reject direct calls to the legacy `IsAllowed` method with a non-empty scope key instead of silently ignoring scope. Empty or omitted keys retain unscoped behavior; the engine uses the new typed method. Custom providers implementing only the original method retain their legacy behavior and its scope-key limitations.\n\n## Fixed\n\n- Chronicle 18.1.0: Publish discovered client artifacts only after initialization completes, allow retry after failure, and reject reentrant access to incomplete results.\n- Chronicle 18.1.0: Apply generation-specific PII metadata in event scenarios, share encryption keys within each scenario, and surface compliance-provider activation failures. Read-model sink encryption and erasure remain outside in-process scenario coverage.\n- Chronicle 18.1.0: Preserve dictionary keys, case distinctions, and nested JSON nulls through projection and SQL readback.\n- Chronicle 18.1.0: Preserve explicit compliance subjects through reactor/reducer notifications and event-sequence readback, with an event-source fallback for older servers.\n- Chronicle 18.1.0: Enforce unique event type constraints across typed source/stream scopes without delimiter collisions, including claims and releases within the same append batch.\n- Chronicle 18.1.0: Validate overlapping covered/removal event types against preceding events before releasing their constraint cycle, matching individually appended events.\n- Chronicle 18.1.0: Show resolved, quarantined, and unknown failed-partition states accurately, and keep SQL-backed observation current across storage instances.\n- Chronicle 18.1.0: Honor configured administrator identities during initial setup and report failed password changes instead of returning false success.\n- Chronicle 18.1.0: Deliver required prerelease build outputs reliably and validate generated schemas with real protoc before distributing them.\n\n## Removed\n\n- Chronicle 18.1.0: Remove the unused Workbench dashboard prototype and its stale navigation references.\n\n## Security\n\n- Chronicle 18.1.0: Require antiforgery protection for authenticated cookie mutations while preserving explicit bearer-token access. Cookie-based access requires HTTPS and matching Workbench assets. Custom cookie clients must obtain a token from `/.cratis/antiforgery` and send `X-CSRF-TOKEN` on mutations and logout.\n- Chronicle 18.1.0: Restrict anonymous Identity endpoints to the intended login and refresh surface, and reject authenticated actors without a meaningful subject.\n- Chronicle 18.1.0: Enforce Chronicle audiences for JWT and Data Protection access tokens. Existing OAuth tokens without the required audience must be reacquired; coordinate issuers and validators during rolling upgrades.\n- Chronicle 18.1.0: Prevent repeated initial administrator password setup and duplicate administrator bootstrap events under competing requests.\n" + }, + { + "repo": "Arc", + "tag": "v22.13.0", + "date": "2026-09-09", + "rules": [], + "body": "## Summary\n\nClarify how to use Arc on its own and with Chronicle, with corrected full-stack examples and testing guidance. Add an analyzer warning for ambiguous event-source identity returns without changing command execution or event-source routing.\n\n## Added\n\n- Add `ARCCHR0010` for keyless commands returning a raw `Guid` alongside statically identifiable untargeted events, with an optional compiler-checked code fix when the value is intended as the event-source identity.\n- Add guides showing how direct command specs complement Arc pipeline and Chronicle scenario tests.\n\n## Changed\n\n- Separate standalone Arc behavior from optional Chronicle integration throughout onboarding, backend, and frontend documentation.\n- Use strongly typed domain examples and explain prerequisites, trade-offs, and validation boundaries.\n\n## Fixed\n\n- Correct documentation for authorization, validation, query transports, proxy generation, persistence, and React forms.\n- Preserve familiar topics and compatibility anchors while restoring tutorial composition and navigation.\n- Recognize returned typed event-source identities in either tuple position when checking for ambiguous command identities.\n" + }, + { + "repo": "Arc", + "tag": "v22.12.0", + "date": "2026-09-09", + "rules": [], + "body": "## Summary\n\nHandle failed commands without exposing internal server diagnostics or giving up normal form validation. `CommandForm` now separates user-facing exception feedback from the original diagnostics available to application callbacks.\n\n## Added\n\n- Add `exceptionMessage` for safe, localized exception text and `exceptionDisplayComponent` with exported `ExceptionDisplayProps` to replace the exception panel independently of field-validation rendering. (#2669)\n\n## Changed\n\n- Show “An unexpected error occurred. Please try again.” instead of raw server exception messages by default; preserve original results and diagnostics for failure callbacks. (#2669)\n\n## Fixed\n\n- Honor `showErrors={false}` for both default and custom exception feedback without disabling validation or execution callbacks. (#2669)\n- Display safe feedback when an exception has no diagnostic messages, and clear previous exception feedback when a subsequent result succeeds. (#2669)\n" + }, + { + "repo": "Arc", + "tag": "v22.11.0", + "date": "2026-09-08", + "rules": [], + "body": "## Summary\n\n`Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated from `17.0.4` to `18.0.0`, picking up [17.0.5](https://github.com/Cratis/Chronicle/releases/tag/v17.0.5), [17.1.0](https://github.com/Cratis/Chronicle/releases/tag/v17.1.0), [17.1.1](https://github.com/Cratis/Chronicle/releases/tag/v17.1.1) and [18.0.0](https://github.com/Cratis/Chronicle/releases/tag/v18.0.0).\n\n## Added\n\n- Chronicle 17.1.0: Server view under System showing running server instances with live CPU/memory (cratis/chronicle#3982)\n- Chronicle 17.1.0: Bulk select and delete for Jobs, and bulk select and ignore for Recommendations, in the Workbench (cratis/chronicle#3986)\n- Chronicle 17.1.0: `ClearFailedPartitions` operator command to recover a wedged observer without direct database access (cratis/chronicle#3997)\n\n## Changed\n\n- `Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated to `18.0.0`\n- Chronicle 17.0.5: `Cratis.Fundamentals` and `Cratis.Metrics.Roslyn` move to 7.19.1 (cratis/chronicle#3998)\n- Chronicle 17.1.0: Switching namespace in the Workbench now consistently refreshes the current view, including via URL navigation, browser back/forward, and deep links (cratis/chronicle#3981)\n- Chronicle 17.1.0: Pattern heatmap and causation rendering now recognizes reactors, commands, and ASP.NET requests, and shows identity display names instead of raw subject IDs (cratis/chronicle#3979)\n- Chronicle 17.1.0: \"By Silo\" renamed to \"By Instance\" in the Clients view (cratis/chronicle#3982)\n- Chronicle 17.1.0: `RetryPartition` now reports an honest outcome instead of silently succeeding when it did nothing (cratis/chronicle#3997)\n- Chronicle 17.1.0: Chronicle no longer eagerly creates a namespace's database before it has received any data (cratis/chronicle#3990)\n- Chronicle 18.0.0: **Breaking:** `Content` on `AppendRequest`, `EventToAppend`, `EventForEventSourceId`, and `ReviseRequest` changes from `map` back to a plain JSON string, matching how event content is already represented everywhere else on the wire (`AppendedEventResponse.Content`, `EventRevision.Content`). Any client built directly against the raw gRPC contracts for these four messages must send/receive `Content` as a string, not a nested map.\n\n## Fixed\n\n- Chronicle 17.0.5: The .NET client no longer reads the static type-universe snapshot, so reactor context value providers, compliance metadata providers, reactor side-effect handlers and polymorphic `[DerivedType]` children in assemblies discovered after its first touch are found instead of silently missed (cratis/chronicle#3998)\n- Chronicle 17.1.0: Navigation flakiness in the Workbench sidebar and routing (cratis/chronicle#3980)\n- Chronicle 17.1.0: Projections list not scrolling (cratis/chronicle#3978)\n- Chronicle 17.1.0: Selecting Seed Data under General no longer also highlights the namespace Seed Data (cratis/chronicle#3977)\n- Chronicle 17.1.0: A silo's startup task no longer hard-crashes on a transient `SiloUnavailableException` from a sibling silo mid-restart (cratis/chronicle#3967)\n- Chronicle 17.1.0: PII-encrypted fields now decrypt correctly when previewing a projection in the Workbench, matching Read Models (cratis/chronicle#3983)\n- Chronicle 17.1.0: Ignore no longer silently fails for Recommendations, and its icon no longer matches Perform's (cratis/chronicle#3986)\n- Chronicle 17.1.0: A kernel restart no longer risks a startup-recovery storm that saturates the kernel (cratis/chronicle#3997)\n- Chronicle 17.1.0: Observers can no longer become permanently wedged in a \"preparing catch-up\" state that neither the watchdog nor operator commands could recover (cratis/chronicle#3997)\n- Chronicle 17.1.1: The Publish GitHub Actions workflow now completes successfully for the Kotlin client. (cratis/chronicle#3970)\n- Chronicle 18.0.0: Event append was completely broken — every `Append`, `AppendMany`, `AppendManyForEventSources`, and `Revise` call failed with a serialization error (`No serializer for type System.Text.Json.Nodes.JsonNode`). (cratis/chronicle#4003)\n" + }, + { + "repo": "Arc", + "tag": "v22.10.6", + "date": "2026-09-08", + "rules": [], + "body": "## Changed\n\n- `Cratis.Fundamentals` floor raised to 7.19.1, which shares one type universe per provider set and runs the generated-provider walk inside its `CurrentTypeUniverse()` accessor (#2666)\n\n## Fixed\n\n- Arc no longer captures the type universe before generated type discovery providers register, so controller discovery, the `IArcBuilder` handed to `configureBuilder` and concept type converters see every type the binding-convention assembly walk brings in (#2666)\n- The `ITypes` resolved from the container is now the same instance Arc uses during setup, built from the complete provider set (#2666)\n- `IDerivedTypes` is built from that same complete universe instead of the process-wide snapshot, so polymorphic JSON and the MongoDB discriminator conventions recognize `[DerivedType]` types the assembly walk brings in, and the container resolves the same instance Arc uses (#2668)\n- Compile-time generated query metadata is now reliably registered at startup: Arc loaded project assemblies without running their module initializers, so metadata from an assembly nothing had touched yet was never registered (#2666)\n- `CommandScenario` extenders and discoverable validators, command result assertion policies, the MongoDB builder's class maps and convention packs, and MongoDB read-model command resolution are discovered from the complete type universe instead of the process-wide snapshot (#2666)\n" + }, + { + "repo": "Arc", + "tag": "v22.10.5", + "date": "2026-09-07", + "rules": [], + "body": "## Summary\n\n`Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated from `16.39.1` to `17.0.4`, picking up [16.39.2](https://github.com/Cratis/Chronicle/releases/tag/v16.39.2), [16.40.0](https://github.com/Cratis/Chronicle/releases/tag/v16.40.0), [16.41.0](https://github.com/Cratis/Chronicle/releases/tag/v16.41.0), [16.42.0](https://github.com/Cratis/Chronicle/releases/tag/v16.42.0), [16.42.1](https://github.com/Cratis/Chronicle/releases/tag/v16.42.1), [16.42.2](https://github.com/Cratis/Chronicle/releases/tag/v16.42.2), [16.43.0](https://github.com/Cratis/Chronicle/releases/tag/v16.43.0), [16.44.0](https://github.com/Cratis/Chronicle/releases/tag/v16.44.0), [16.44.1](https://github.com/Cratis/Chronicle/releases/tag/v16.44.1), [16.44.2](https://github.com/Cratis/Chronicle/releases/tag/v16.44.2), [16.45.0](https://github.com/Cratis/Chronicle/releases/tag/v16.45.0), [16.45.1](https://github.com/Cratis/Chronicle/releases/tag/v16.45.1), [16.45.2](https://github.com/Cratis/Chronicle/releases/tag/v16.45.2), [16.45.3](https://github.com/Cratis/Chronicle/releases/tag/v16.45.3), [17.0.1](https://github.com/Cratis/Chronicle/releases/tag/v17.0.1), [17.0.2](https://github.com/Cratis/Chronicle/releases/tag/v17.0.2), [17.0.3](https://github.com/Cratis/Chronicle/releases/tag/v17.0.3) and [17.0.4](https://github.com/Cratis/Chronicle/releases/tag/v17.0.4).\n\n## Added\n\n- Chronicle 16.40.0: `ChronicleOptions.RegistrationRetry` — registering a client's artifacts is now retried with an exponential backoff instead of failing host startup when the Kernel is busy. Five attempts by default, 2s growing to 30s, all configurable (cratis/chronicle#3869)\n- Chronicle 16.40.0: Failed partition attempts carry what kind of failure they were — `Handling`, `Timeout`, `Disconnected` or `Unknown` — surfaced through the client SDK and the Workbench API (cratis/chronicle#3869)\n- Chronicle 16.41.0: Behavior pattern mining across every event flowing through an event store, using a bounded streaming sketch so storage scales with distinct recurring behavior rather than event volume (cratis/chronicle#3857)\n- Chronicle 16.41.0: `IPatterns.GetPatternsAt` on the .NET client — a scope plus an optional moment defaulting to now, with the day and the part of the day derived using the same rule the engine bucketed events with when it mined them. `GetPatterns` remains underneath for a context that is not a moment.\n- Chronicle 16.41.0: `ToTimeBucket()` on `DateTimeOffset`, the engine's own bucketing rule, so an application asking about a moment does not have to carry a second copy of it — a copy that would drift silently, returning nothing rather than failing\n- Chronicle 16.41.0: `IPatterns.GetScopes` on the .NET client and the `Patterns` gRPC service, listing the scopes that hold established patterns — what a browsing surface needs before it can offer one\n- Chronicle 16.41.0: **Behavior patterns** in the Workbench: a pivot over everything established, across scope, command, initiator, day, time of day, aggregate, cause and specificity, with a confidence bar on each card\n- Chronicle 16.41.0: **Pattern heatmap** in the Workbench: a day-by-time-of-day grid shaded by activity on a perceptual color scale, with a panel naming what a scope usually does at a given moment\n- Chronicle 16.41.0: **Backoffice** sample generating half a year of backdated internal business activity — accounts payable, the ledger, procurement, HR and payroll — where everybody holds three or four different jobs at different points in the week, and one actor deliberately has no routine at all\n- Chronicle 16.42.0: `GetUsualActions` on the .NET client and the `Patterns` gRPC service — given a situation, what is usually *done* in it, ranked by confidence and at most one answer per action (cratis/chronicle#3872)\n- Chronicle 16.42.0: `FacetName.IsAction`, `FacetSet.WithoutActions()` and `BehaviorPattern.AnswersFor` — the split between the action a pattern names and the context it was taken in, which mining already relied on privately\n- Chronicle 16.42.1: `PatternDetection.PersistenceInterval` setting controlling how often mined behavior patterns are persisted (default 5 seconds).\n- Chronicle 16.43.0: `MapValues` on `EventTypeMigration` — declare which value in one generation is which value in the other, once, and Chronicle applies the map forward when upcasting and inverted when downcasting. Values the map does not mention are carried across unchanged.\n- Chronicle 16.43.0: `MapValues` on the migration property builder, for a translation that applies to one direction only. A direction that declares its own transformation for a property keeps it, overriding what the value map derives.\n- Chronicle 16.44.0: Time Scrubber, on both the Projections and Read Models views: step a read model instance through its history one event at a time, with the state after each event and a hover describing the event and its properties.\n- Chronicle 16.44.0: Snapshots of a read model instance can be asked for per event instead of per correlation, through a new `Grouping` on the snapshot request. It defaults to grouping by correlation, so existing callers are unaffected.\n- Chronicle 16.44.0: A projection can be generated as client code in TypeScript, Kotlin and Elixir alongside C#, selectable in the Workbench's generated-code panel. Languages whose client has no API for a given projection style are offered only for the styles they support.\n- Chronicle 16.44.0: `ILanguageService.GetLanguagesSupporting` reports which languages can generate a given projection style.\n- Chronicle 16.45.0: ASP.NET Core health check for the client connection, registered automatically by `AddCratisChronicle` under the name `chronicle` with the `chronicle` and `ready` tags. `AddChronicleHealthCheck(HealthStatus)` registers it explicitly and chooses the failure status (cratis/chronicle#3943)\n- Chronicle 16.45.0: `CHR0051` reports a reducer method that declares its current read model as non-nullable (cratis/chronicle#3947)\n- Chronicle 16.45.0: Documentation for the `[Index]` attribute, covering why the declaration lives on the read model and how it survives a replay (cratis/chronicle#3942)\n- Chronicle 16.45.0: Documentation for the client health check (cratis/chronicle#3943)\n- Chronicle 16.45.2: Documentation for addressing a nested property in a migration, and for the case that needs no property operations at all — adding or removing an *optional* property at any depth, where the target generation's schema drives both the filling and the dropping (cratis/chronicle#3949)\n- Chronicle 17.0.1: gRPC wire compatibility gate that runs before anything else builds, checking the contract at HEAD against every released minor of the current major and reporting each one. A major with no declared floor is compared in full. (cratis/chronicle#3873)\n- Chronicle 17.0.1: `CheckCompatibility` on the connection service, so the kernel performs the one compatibility check for every client language instead of each SDK reimplementing it. A refused connection names the client version, the protocol version and the server version.\n- Chronicle 17.0.1: The canonical descriptor set now ships inside the .NET, Kotlin, TypeScript and Elixir contracts packages, so a client hands the kernel exactly what it was built against.\n- Chronicle 17.0.1: `ProtocolVersion` on the contracts, and `ServerVersionInfo.ProtocolVersion` on the server.\n- Chronicle 17.0.1: `AllPatterns` and `AllPatternScopes` queries, so a namespace's behavior patterns can be browsed without choosing a scope first. (cratis/chronicle#3857)\n- Chronicle 17.0.1: Cross-cutting existence checks for event store, application and user: a command carrying one of those concepts is rejected when the thing it names does not exist, with no per-command wiring. A command that *creates* the entity opts its own property out.\n\n## Changed\n\n- `Cratis.Chronicle`, `Cratis.Chronicle.AspNetCore` and `Cratis.Chronicle.Testing` are updated to `17.0.4`\n- Chronicle 16.39.2: `Cratis.Arc` updated to 22.6.0. Events appended through an Arc command now carry the command that produced them, and a command executed from another command carries the one above it.\n- Chronicle 16.39.2: Every `[Command]` declares a public instance `Handle()` rather than an internal one, which is the contract Arc documents and now enforces.\n- Chronicle 16.40.0: When several methods on a reactor or reducer match the same event type, the public one now wins, then the one with the richest signature. A private helper extracted from a handler can no longer take that handler's place (cratis/chronicle#3870)\n- Chronicle 16.40.0: A projection catches up through a single job step walking the sequence in global order, rather than one step per event source (cratis/chronicle#3869)\n- Chronicle 16.40.0: Catch-up and replay bring their job steps up after subscribing has returned, so registering a projection no longer waits for them (cratis/chronicle#3869)\n- Chronicle 16.40.0: `Observers.SubscriberTimeout` is now applied to subscriber calls — it previously had no effect at all. The default is raised from 5 to 30 seconds to match the bound the transport already imposes, so nothing changes for anyone who has not set it, and `0` now means waiting indefinitely rather than falling back to five seconds (cratis/chronicle#3869)\n- Chronicle 16.40.0: A partition whose last attempt timed out no longer counts toward the observer quarantine thresholds, so a congested period cannot take an otherwise healthy observer out of service (cratis/chronicle#3869)\n- Chronicle 16.42.0: `GetPatternsAt` answers with commands rather than with the moment it was handed. It is built on `GetUsualActions` now, which is what its name and its documentation already promised.\n- Chronicle 16.42.0: `GetPatterns` is unchanged and stays the call for the opposite question — given a situation you can describe completely, including the command, which established patterns cover it. That is the check to make before flagging an action as unusual, and it is the only one of the two answerable from a keyed candidate lookup.\n- Chronicle 16.42.2: The pattern miner is a per-event-store-and-namespace grain owning the mining lifecycle — sketches, restore of established patterns, and deferred persistence — with the capture subscriber reduced to extracting features and forwarding each observed batch as a single call to its store's miner.\n- Chronicle 16.43.0: An event type generation whose enum only gained members, or had members renamed, no longer registers as `EventTypeSchemaChanged`. The wider member list replaces the registered schema instead. Removing or renumbering a member is still a schema change that needs a new generation.\n- Chronicle 16.44.0: The Workbench is built on `@cratis/components` 4 and imports from explicit subpaths; Workbench-local components that the library now provides have been removed.\n- Chronicle 16.44.0: Projection code generation is now a seam with one generator per language. `DeclarativeCodeGenerator` and `ModelBoundCodeGenerator` moved into `Cratis.Chronicle.Projections.Engine.DeclarationLanguage.CodeGeneration.CSharp`, and `LanguageService` takes the discovered `IInstancesOf` rather than the two C# generators directly — code that constructs these types itself, rather than resolving them, needs updating.\n- Chronicle 16.44.0: `ILanguageService.GenerateDeclarativeCode` and `GenerateModelBoundCode` take the language to generate for, defaulting to C#, so existing calls are unchanged.\n- Chronicle 16.44.0: Generated C# declarative projections and model-bound records are formatted over multiple lines rather than one long line.\n- Chronicle 16.44.0: The Backoffice sample correlates each generated command separately, so a generated history no longer looks like a single action.\n- Chronicle 16.45.0: A client call made while the connection is down now fails instead of blocking its thread. A connect timeout surfaces as `ConnectionTimedOut`, acquiring the connect lock is bounded by the connect timeout, and a failed attempt starts a back-off during which calls fail immediately with `ConnectionUnavailable`. The watchdog still reconnects in the background, so a kernel that comes back is reachable again without restarting the client (cratis/chronicle#3948)\n- Chronicle 16.45.0: A reducer method whose current read model parameter is non-nullable is now rejected at registration rather than silently excluded from dispatch. The current read model is null for the event that creates an instance, so the declaration can only ever be a mistake. Methods in a `#nullable disable` context are unaffected (cratis/chronicle#3947)\n- Chronicle 16.45.0: An ASP.NET Core host no longer fails to start when the kernel is briefly unreachable; the connection failure is logged and the health check reports it until the connection comes up (cratis/chronicle#3943)\n- Chronicle 16.45.2: The JSON Schema `title` is no longer part of the schema comparison that guards `EventTypeSchemaChanged`. It carries the CLR record name, so comparing it made renaming a record a breaking change despite a pinned `[EventType(\"...\")]` identifier and an unchanged property set. It stays in the stored schema for tooling (cratis/chronicle#3926)\n- Chronicle 16.45.2: An event type migration can now address a property inside a nested object — `pb.DefaultValue(m => m.Price.Description, \"unspecified\")` writes where it belongs, and `pb.RenamedFrom(m => m.Price.Amount, e => e.Amount)` moves a top-level value into the object it now belongs to. Registration validates such a path segment by segment and rejects one that resolves nowhere (cratis/chronicle#3949)\n- Chronicle 16.45.2: A `DefaultValue` whose target the generation's schema does not declare is now rejected at registration instead of skipped. Validation still exempts a default when the target generation has no schema to resolve against (cratis/chronicle#3949)\n- Chronicle 17.0.1: Every gRPC service is now derived from Arc `[Command]`/`[ReadModel]` artifacts in Core rather than hand-written, completing the migration cratis/chronicle#2908 started. `Source/Clients/Api` and `Source/Kernel/Core/Services` are gone; only `Observers` and the streaming services, whose server-to-client lifetime no command or query describes, remain hand-written.\n- Chronicle 17.0.1: Patterns is served by artifacts like everything else. Its five queries — `MatchingPatterns`, `UsualActions`, `PatternsForScope`, `AllPatterns`, `AllPatternScopes` — read storage and the mining engine directly, and the contract is generated from them. (cratis/chronicle#3857)\n- Chronicle 17.0.1: Query parameters that name a concept are declared as that concept rather than as `string`, so the value is validated on the way in instead of being cast inside the method. The wire is unchanged — a concept still travels as its primitive.\n- Chronicle 17.0.1: The two event-sequence lookups that answered with a bare `bool` and `ulong` now answer with `EventSourceEvents` and `EventSequenceTail`. The value travels as the same primitive, one level in.\n- Chronicle 17.0.1: The Workbench's surfaces are served by Arc artifacts in the kernel, with proxies generated beside the features that use them rather than into a separate `Api` folder.\n\n## Fixed\n\n- Chronicle 16.40.0: Adding a projection to an event store with a large event log could take the application down. Registration awaited one job-step grain being created, prepared and started per event source, exceeded its response timeout, failed host startup, and the restart re-registered into the queue it was waiting on (cratis/chronicle#3869)\n- Chronicle 16.40.0: A reactor's handler was silently never invoked when the reactor also had a private helper whose first parameter was the same event type. Nothing was thrown and nothing was logged; only the missing side effects showed it (cratis/chronicle#3870)\n- Chronicle 16.41.0: A constraint registered while its event sequence was already active was ignored for up to a second afterwards, so appends landing in that window bypassed it silently. The sequence now re-reads constraints on its first append after activation. This reaches any application that appends immediately after registering a constraint, not only the observer case it was found through. (cratis/chronicle#3868)\n- Chronicle 16.41.0: Pattern capture subscribed only at server startup, so an event store whose clients connected afterwards mined nothing at all until the next restart. Capture now also subscribes when event types are registered. (cratis/chronicle#3867)\n- Chronicle 16.41.0: `CHR0024` reported a read-model mapping written as `[property: SetFrom]` as having no mapping source. Both that spelling and the bare attribute on the parameter are honored at runtime; the analyzer read only the parameter.\n- Chronicle 16.41.0: The Workbench pattern scope selector showed its placeholder instead of the selected scope, while the views below it were already showing that scope's patterns.\n- Chronicle 16.42.1: Bulk event ingestion no longer overwhelms pattern capture: observing only mines in memory, and the scopes an interval touched are persisted on a cadence — eliminating the subscriber timeouts, failed partitions, and retry-job storms high-rate appends previously caused.\n- Chronicle 16.42.1: Behavior patterns are isolated per event store and per namespace — the same scope name in two stores, or in two tenants' namespaces, no longer counts into one sketch and contaminates both sides' persisted patterns.\n- Chronicle 16.42.1: Established behavior patterns survive a server restart: a scope's persisted patterns are restored into the miner before its first event after a restart is mined, instead of being wiped in favor of whatever happened to occur first.\n- Chronicle 16.42.1: A namespace added while the server is running is subscribed for pattern capture immediately, instead of only after the next restart or event type registration.\n- Chronicle 16.42.1: The Backoffice sample's `generate` is genuinely re-runnable: its history marker now lands on a well-known event source so the guarding uniqueness constraint actually turns a second run away.\n- Chronicle 16.42.2: `FacetSet` now round-trips System.Text.Json — its deserialization constructor previously could not bind, so any behavior pattern crossing a serialization boundary threw.\n- Chronicle 16.44.0: The Time Machine found no snapshots for a read model instance whose projection is keyed by a property rather than the event source.\n- Chronicle 16.44.0: A projection preview returned instances without their key, so opening one in the Time Machine had nothing to look up.\n- Chronicle 16.44.0: The pivot viewer clipped tile titles, lost pinch-to-zoom on trackpads and touch devices, and could leave tiles unreachable after zooming in grouped mode.\n- Chronicle 16.44.0: Selecting a read model instance did not stick, which left the Time Machine disabled; selecting an event in the sequence editor showed no details.\n- Chronicle 16.44.0: A details pane no longer cramps the columns of the grid beside it.\n- Chronicle 16.44.0: Disabled buttons are visibly disabled, and the projection editor's help and source buttons stay inside the editor.\n- Chronicle 16.44.0: Event store cards on the landing page are clickable as a whole, with the icon and title centered.\n- Chronicle 16.44.0: A projection preview shows that it is running rather than appearing to hang.\n- Chronicle 16.44.1: Event details in Sequences now shows the causation entry's own properties when navigating Context → causation → properties, instead of falling back to the root event context\n- Chronicle 16.44.1: Navigating into the second or later causation entry now shows that entry's properties rather than the first entry's\n- Chronicle 16.44.1: The property navigation breadcrumb shows which array element was navigated through, as `[0]`\n- Chronicle 16.44.2: Stopped a shutdown requested during kernel startup from crashing with an unhandled exception; it now stops gracefully and logs the shutdown (cratis/chronicle#3936)\n- Chronicle 16.44.2: Fixed a `NullReferenceException` when an observer activates against an unset projection definition (cratis/chronicle#3934)\n- Chronicle 16.44.2: `ReactorScenario` now surfaces what a reactor handler throws instead of swallowing the exception, so throwing reactors can be specced (cratis/chronicle#3933)\n- Chronicle 16.44.2: In-memory event sequence storage now records the real identity that caused an event instead of hardcoding `Identity.System`, so in-process scenarios can assert event attribution (cratis/chronicle#3928)\n- Chronicle 16.44.2: `ReadModelScenario`'s `GetInstanceById` now returns `null` for an instance that was never seeded instead of throwing (cratis/chronicle#3927)\n- Chronicle 16.44.2: The client now keeps retrying artifact registration in the background after it fails on an otherwise healthy connection, instead of leaving observers disconnected indefinitely until the next reconnect (cratis/chronicle#3935)\n- Chronicle 16.45.0: `[AddFrom]` on a property of a `[ChildrenFrom]` child no longer double-counts the event that creates the child entry. Accumulating child breakdowns produced exactly twice the value on the creating event, and every later event accumulated on top of that (cratis/chronicle#3940)\n- Chronicle 16.45.0: A job left persisted as running with all of its steps already completed is now finalized instead of reported as already running. Such a job stayed wedged forever and its observer could never resubscribe, leaving reactors disconnected indefinitely (cratis/chronicle#3944)\n- Chronicle 16.45.0: A read model's declared indexes are now recreated on the collection a replay fills, so they survive the swap that ends the replay instead of being absent until the sink is rebuilt (cratis/chronicle#3942)\n- Chronicle 16.45.1: `[Index]` on a record's positional constructor parameter now declares an index, as the documentation says it does. Previously only `[property: Index]` worked, and the difference was invisible — nothing reported that a declaration had produced no index. (cratis/chronicle#3954)\n- Chronicle 16.45.2: A migration built from a nested property expression no longer fails registration with `InvalidMigrationPropertyForEventType` naming a property that exists one level down (cratis/chronicle#3949)\n- Chronicle 16.45.2: A `DefaultValue` targeting a nested property no longer writes a top-level key with a dot in its name that the target generation's schema then discards, leaving old events upcasting with the value missing and no error anywhere (cratis/chronicle#3949)\n- Chronicle 16.45.3: Keyed model-bound child projections continue accepting updates after replay when an explicit event key matches the child key property. (cratis/chronicle#3922)\n- Chronicle 17.0.1: `DateTimeOffset` reached the wire as an empty message, so ten fields — job creation times, user timestamps, event query date ranges — carried no value to Kotlin, TypeScript and Elixir clients. .NET was unaffected, which is why it went unseen.\n- Chronicle 17.0.1: The proto generator aborted for 22 of its 23 packages while exiting 0, so most generated contracts had silently stopped regenerating and an entire `SequenceQueries` service had never been emitted at all.\n- Chronicle 17.0.1: Generated proto files and the descriptor set are now deleted and rewritten on every build, so a hand-applied edit to a generated file cannot survive.\n- Chronicle 17.0.1: `AppendMany` was silently dropping the caller-supplied `Occurred` timestamp for every event in the batch.\n- Chronicle 17.0.1: Pattern capture re-subscribes when registering event types introduces something new. A server starting against a store no client had connected to yet captured nothing at all until the next restart. (cratis/chronicle#3857)\n- Chronicle 17.0.1: Failed-partition attempts carry their failure kind again, so a congestion timeout can be told apart from a subscriber that genuinely failed.\n- Chronicle 17.0.1: An optional concept parameter generated an unguarded cast, so an absent value on the wire did not stay absent in the domain.\n- Chronicle 17.0.2: Include `Cratis.Chronicle.Compatibility.dll` in both Chronicle.Testing and Chronicle.XUnit.Integration so each package starts independently beside Arc (cratis/chronicle#3968).\n- Chronicle 17.0.3: Commands whose kernel-side handling depends on `IUnitOfWorkManager`, `IEventLog`, or `IEventTypes` (any consumer combining Arc.Chronicle's `TransactionalCommandScope`/`*CommandResponseValueHandler` types with `Cratis.Chronicle.Testing`) failed to resolve inside the in-process kernel command pipeline used by `EventScenario`, `EventStoreForTesting`, and `TestingServices` - those interfaces fell back to auto-activating the real client implementations, which need a live connection the in-process kernel does not have.\n- Chronicle 17.0.4: Chronicle in-process tests no longer run consumer command execution scopes inside the kernel pipeline; avoids missing EventStoreName resolution and unintended nested consumer transactions. (cratis/chronicle#3973)\n" + }, + { + "repo": "Arc", + "tag": "v22.10.4", + "date": "2026-09-05", + "rules": [], + "body": "## Summary\n\nAlign command-side read-model registration and identity diagnostics with Chronicle's runtime behavior. Child-only models are no longer advertised as independently resolvable through Chronicle; existing fluent projections, reducers, and other storage providers retain their registrations.\n\n## Fixed\n\n- Exclude child and nested model-bound types from standalone Chronicle command read-model registration, matching Chronicle's projection discovery (#2587)\n- Detect ambiguous command identities declared through positional `[Key]` parameters, without counting the same property twice (#2645)\n- Correct read-model failure guidance to distinguish generated creation keys, unusable declared keys, absent state, and missing provider registrations\n" + }, + { + "repo": "Arc", + "tag": "v22.10.3", + "date": "2026-09-05", + "rules": [], + "body": "## Summary\n\nA command that marks its key positionally — `record Foo([Key] Guid Id)` — compiled cleanly but silently failed to resolve, because the Chronicle key attribute lives on the constructor parameter, not the generated property, and nothing looked there. Chronicle fabricated a fresh, unrelated event source id instead, so a DCB read-model dependency injected into `Handle()`/`Provide()`/a validator always resolved to `null` — with no exception and `isSuccess: true` — for any command using this style, including with `WithMongoDB()`-only setups.\n\n## Fixed\n\n- Recognize `[Key]` on a positional record's primary-constructor parameter when resolving a command's event source id, so it behaves the same as `[Key]` on a property (#2645)\n- Register each `ICanResolveReadModelForCommand` provider itself in DI (not just its per-type factories), so `IEnumerable` reflects every registered provider (#2645)\n" + }, + { + "repo": "Arc", + "tag": "v22.10.2", + "date": "2026-09-05", + "rules": [ + "Issue reference position", + "No release notes", + "Section heading level", + "Section without bullets" + ], + "body": "### Fixed\n\nThe `useDialog` wrapper component now correctly accepts no props even when the dialog input type has required properties, matching the documented behavior (#2627).\n\n---\n_Posted by Stagehand (AI) - an autonomous agent, not a person. Review accordingly._" + }, + { + "repo": "Arc", + "tag": "v22.10.1", + "date": "2026-09-02", + "rules": [ + "Review, verification or provenance note" + ], + "body": "## Fixed\n\n- **In source-file grouping mode, two types sharing a short name could have one rewrite the other's imports**, producing TypeScript that does not compile. `BuildImportPathFixups` skipped any type whose file was already named after it — true that it needs no rewrite, but it also took that type out of conflict detection, so the *other* type claimed the short name unopposed and the fixup was applied to every import of either.\n\n---\n\nFound in Cratis Studio, which uses `CratisProxiesUseSourceFileAsOutputFile`. `ScreenTemplate` exists twice there: Scene's model declares one in `ScreenTemplate.cs`, and Studio declares its own in `Projects/Hierarchy/Hierarchy.cs`. Scene's needed no rewrite and so was skipped; Studio's claimed the name; and the generated `SceneApplication.ts` came out with\n\n```ts\nimport { ScreenTemplate } from '../../../Scene/Model/Screens/Hierarchy';\n```\n\npointing at a file that does not exist, while the type had in fact been emitted to `Screens/ScreenTemplate.ts`.\n\nSuch a type now takes part in claiming the name like any other, and entries that would rewrite a name to itself are dropped at the end instead. A short name two types disagree about stays a conflict and gets no fixup — which is what the conflict set was always there to do.\n\nThe regression spec (`and_two_types_share_a_short_name`) fails on both of its assertions without the change and passes with it.\n\nVerified: 51 projects build in Release with 0 errors and 0 warnings, and 1227 ProxyGenerator specs pass.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)" + }, + { + "repo": "Arc", + "tag": "v22.10.0", + "date": "2026-09-02", + "rules": [ + "Review, verification or provenance note" + ], + "body": "## Added\n\n- **`--emit-interfaces`** renders types as plain TypeScript interfaces rather than classes carrying `@field` decorators, with no `@cratis/fundamentals` import for the decorators. A model that is only ever built and read needs neither, and a package that deliberately carries no dependencies cannot take one on. Documentation, base types and nullability are unchanged, so the output differs from the class form only in the ways the mode is for. (#2635)\n\n## Fixed\n\n- **A read-only dictionary is now recognized as a dictionary.** `IsDictionary` only looked for `IDictionary<,>`, and `IReadOnlyDictionary<,>` is its sibling rather than its derivative — so a model exposing its dictionaries read-only, the natural shape for an immutable record, was not seen as holding dictionaries at all. `IReadOnlyDictionary>` now generates as `Record` instead of a `KeyValuePair[]` referencing a type that was never imported. (#2634)\n- **Library mode no longer aborts on a model with two read-only dictionaries.** Those unrecognized dictionaries put two closed instantiations of `KeyValuePair<,>` in one output file, and the duplicate-path guard stopped the entire run. With them recognized, `CollectTypesInvolved` takes only the value type as it always did for the mutable shape, and no `KeyValuePair` is emitted. (#2634)\n- **Library mode no longer aborts on an open generic either.** It collected open definitions such as `CompilationResult`, and walking one reached its own type parameter — which has no full name, so it produced a nameless descriptor landing on the definition's own path. An open definition has no concrete shape to emit and a generic parameter is not a type, so both are skipped. (#2634)\n\n---\n\nVerified against two independent model assemblies. The second one is why the third fix exists: after the dictionary fix I claimed the collision could no longer arise, which was true only of dictionaries — checking `Cratis.Screenplay` surfaced a second, unrelated cause.\n\n| assembly | before | after |\n|---|---|---|\n| `Cratis.Scene.Model` | aborts on the `KeyValuePair` collision | 75 files, no exclusions needed |\n| `Cratis.Screenplay` | aborts on the `CompilationResult` collision | 216 files, 193 types |\n\nWith `--emit-interfaces`, Scene emits zero `@cratis/fundamentals` imports. Screenplay emits one — `TimeOnly`, a value type Fundamentals provides rather than a decorator, which no emission mode can or should remove.\n\nThe `Interface` template already existed but was unused and incomplete — it gained documentation, `extends` and optional properties here rather than being written from scratch.\n\nFour generated files under `TestApps` also change, and not because of anything in this PR: their committed output predates the XML-doc unescaping fix, so ``` becomes a backtick when they are regenerated. Left in rather than reverted, so `main` stops carrying stale generated output.\n\nVerified: 51 projects build in Release with 0 errors and 0 warnings, and 1225 ProxyGenerator specs pass — including 17 new ones covering all three changes.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)" + }, + { + "repo": "Arc", + "tag": "v22.9.0", + "date": "2026-09-01", + "rules": [], + "body": "## Added\n\n- `CommandScenario` implements `IDisposable` and `IAsyncDisposable`, disposing the service provider it builds and any disposable values extension packages place in `Context` (such as the Chronicle extender's `EventScenario`); disposal is idempotent and `Execute`/`Validate` throw `ObjectDisposedException` after disposal (#2629)\n\n## Changed\n\n- `CommandScenario` no longer registers a console logger by default — `ILogger` resolves as a no-op, so no logging processor thread is spawned per scenario; opt in with `scenario.Services.AddLogging(l => l.AddConsole())` before the first `Execute`/`Validate` to see log output (#2629)\n\n## Fixed\n\n- `AddCratisArcMeter` no longer creates and abandons an undisposed `Meter` on every call after the first — the meter is created only when actually registered and is now owned and disposed by the service provider (#2629)\n" + }, + { + "repo": "Arc", + "tag": "v22.8.2", + "date": "2026-09-01", + "rules": [], + "body": "## Summary\n\nA generated proxy is TypeScript and its documentation is a JSDoc comment, not markup, but the templates rendered it through Handlebars' escaping expression. Every apostrophe in a summary reached the proxy as `'`, which is what a reader sees in their editor's tooltip; quotes and ampersands went the same way.\n\nIt surfaced on upgrading a downstream application: regenerating against the current generator rewrote 418 apostrophes across 163 proxy files that had none before.\n\n## Fixed\n\n- Documentation in generated TypeScript proxies keeps apostrophes, quotes and ampersands as written, instead of arriving HTML-escaped as `'`, `"` and `&`\n" + }, + { + "repo": "Arc", + "tag": "v22.8.1", + "date": "2026-09-01", + "rules": [], + "body": "## Summary\n\n`ARCCHR0009` judged a command property by its name alone. Its first contact with real code found two kinds of false positive: properties like `AccessTokenExpiresAt`, which contain the word \"token\" and hold a `DateTimeOffset`, and properties whose secret is wrapped in a concept the application had already marked — which the runtime honors but the analyzer did not, so it reported correct code. Both are fixed, and the documentation now says what to do when the rule is still wrong.\n\n## Changed\n\n- `ARCCHR0009` no longer reports a property whose type cannot hold a secret — a date, a duration, a number, a bool, a `Guid` or an enum — however it is named, so `AccessTokenExpiresAt` is left alone while `AccessToken` is still reported (#2625)\n- `ARCCHR0009` honors `[NotAudited]` and `[PII]` on the property's type, matching what the runtime already withholds, so marking a concept once covers every command that takes one (#2625)\n- `ARCCHR0009` judges a concept by the value it wraps, so a `ConceptAs` holding a token is reported and a `ConceptAs` is not (#2625)\n\n## Fixed\n\n- The `ARCCHR0009` documentation told you to mark a false positive `[NotAudited]`, which silences the warning by withholding the value rather than recording it; it now says to suppress the diagnostic and keep the value (#2625)\n" + }, + { + "repo": "Arc", + "tag": "v22.8.0", + "date": "2026-09-01", + "rules": [ + "Issue reference position", + "Section heading level" + ], + "body": "# Summary\n\nThe causation chain named the command that produced an event but said nothing about what that command was asked to do, so two purchase orders raised by the same command were indistinguishable on it. A command's property values are now recorded alongside its name.\n\nBecause the causation is written into the event log and the event log is immutable, a value recorded there stays there for as long as the events do. Values that must not travel that way are withheld: personal data through Chronicle's existing `[PII]` marking, and secrets through a new `[NotAudited]`. A new analyzer reports a command property whose name reads like a secret and is not marked. (#2625)\n\n## Added\n\n- Command property values are recorded on the causation of every event the command appends, keyed by camel-cased property name, so an event says what the command was asked to do and not only which command it was (#2625)\n- `[NotAudited]` keeps a value off the causation chain — on a property, on a positional parameter, or on the command itself to exclude every property at once (#2625)\n- `ARCCHR0009` reports a command property whose name reads like a secret (`Password`, `Token`, `ApiKey`, `Credential`, `Pin`, `Cvv` and the like) and which is not marked `[NotAudited]` or `[PII]` (#2625)\n- `CommandCausation.PropertiesFor(Type, object)` builds the causation properties for a command instance; the existing `PropertiesFor(Type)` is unchanged (#2625)\n\n## Changed\n\n- Values Chronicle treats as personal data are withheld from the causation chain, honoring `[PII]` on the property, the declaring type, the positional parameter, and the concept — so a concept marked once carries the marking to every command that uses it (#2625)\n- A recorded value is truncated at 1024 characters and marked where it was cut, since the causation travels on every event the command appends (#2625)\n" + }, + { + "repo": "Arc", + "tag": "v22.7.0", + "date": "2026-08-30", + "rules": [], + "body": "## Added\n\n- `RuleFor(...).IgnoreConceptRules()` on the concept-flavored `RuleFor` overload, so a validator can suppress a cross-cutting `ConceptValidator` for one of its own properties.\n\n`ModelGraphValidator` runs a registered `ConceptValidator` against every occurrence of that concept anywhere in a model's graph, which is the point of the mechanism. It also means a command whose own property names the entity it is *creating* cannot coexist with a \"must already exist\" check on that concept type: the nested validator is invoked with a fresh validation context holding only the value, so it has no way to know which command carried it, and the creating command has no way to exempt itself.\n\nThe exclusion is scoped to exactly that property on that model. A sibling of the same concept type still gets checked, and the same concept type on any other model is untouched. It is only reachable from the concept overload — `IConceptRuleBuilder` — so it cannot be called on a plain primitive `RuleFor`.\n\nConsumers with no concept validators are unaffected; nothing changes unless the method is called." + }, + { + "repo": "Arc", + "tag": "v22.6.2", + "date": "2026-08-30", + "rules": [], + "body": "## Fixed\n\n- `useIdentity` now hands back the details it promises. The default details were substituted only when the identity was explicitly unset, so an identity that resolved while carrying no details — `isSet` is true — passed `null` through to callers, even though the declared return type says `details` is a `TDetails`. That is exactly why call sites dereference it unguarded, and one `null` reaching one of them takes the whole page down: a signed-in user got a blank screen with a `TypeError` on the first property read after their backend restarted. The default now stands in whenever there are no details to give, without displacing real ones.\n" + }, + { + "repo": "Arc", + "tag": "v22.6.1", + "date": "2026-08-30", + "rules": [], + "body": "## Fixed\n\n- An observable query now removes documents that stop matching its filter. The change-stream `$match` narrowed every operation type by the observed filter, but that filter is rendered against `fullDocument` — the document *after* the change — so an update that moved a document out of the result set produced an event the pipeline discarded. `HandleChange` compounded it by only ever calling `Add` on an update. The result was an observable that could only grow: an observer watching \"orders still open\" was told when each order opened and never when it closed, and the stale rows survived until the client reconnected. (#2617)\n- `QueryContext.TotalItems` stays correct when a document moves across the observed filter, rather than counting only inserts and deletes.\n" + }, + { + "repo": "Arc", + "tag": "v22.6.0", + "date": "2026-08-29", + "rules": [], + "body": "## Summary\n\nRecords which command is executing on the causation chain, so every event a command appends carries the command that produced it — and, when one command executes another, the command one level up. Also brings every dependency to its latest version, including Cratis.Chronicle 16.39.1.\n\n## Added\n\n- Events appended by a command now carry a `Command` causation naming that command. The chain already said how the work arrived — an HTTP request, a reactor — but not what was asked for, so an appended event could be traced back to a request path and no further. A command executed from another command stacks its link on the outer one's, which is what makes \"the command one level up\" answerable.\n\n## Changed\n\n- Every dependency updated to its latest version, including `Cratis.Chronicle` 16.39.1 and the `Cratis.Fundamentals` 7.18.2 / `Cratis.Metrics.Roslyn` 7.18.2 it requires.\n\n## Fixed\n\n- The `Chronicle Published` automation retried a version conflict for ten minutes and then reported it as a NuGet indexing timeout. A conflict is a decision the workflow cannot make — the new Chronicle wants a dependency newer than the version pinned here — so it now fails immediately with the conflicting pins instead of blaming NuGet. This is what made the 16.39.1 update fail.\n- The same automation merged its update with `--squash`, which collapses the branch's commits into a new one and leaves the originals unreachable. It now merges with a real merge commit.\n" + }, + { + "repo": "Arc", + "tag": "v22.5.0", + "date": "2026-08-28", + "rules": [ + "Heading not allowed", + "Section heading level", + "Section order" + ], + "body": "# Summary\n\nRestore Arc's Roslyn analyzers, code fixes, and source generator on the current .NET/Roslyn toolchain, and make aggregate package references load each analyzer and generator exactly once.\n\n## Fixed\n\n- Prevent duplicate Arc source-generator execution when applications reference `Cratis.Arc`, `Cratis`, or combine those packages with `Cratis.CodeAnalysis`.\n- Preserve ARC and ARCCHR analyzer/code-fix delivery through the existing standalone analyzer package IDs.\n- Keep Workspaces and Composition dependencies in the IDE/tool host rather than application output.\n- Keep proxy generation working when project package identities differ from assembly names or include analyzer-only dependencies.\n\n## Changed\n\n- Arc analyzers and generators now require the .NET SDK declared by `global.json` (`10.0.400` or later in that SDK band) because they target Roslyn 5.9.\n- `Cratis.CodeAnalysis` is a dependency-only umbrella over the ARC and ARCCHR analyzer packages.\n\n## Release effects\n\nThis PR remains `minor` and is expected to create Arc `22.4.0` while `22.3.0` remains the latest release.\n\nIt publishes these 19 NuGet packages:\n\n- `Cratis`\n- `Cratis.Arc`\n- `Cratis.Arc.Chronicle`\n- `Cratis.Arc.Chronicle.CodeAnalysis`\n- `Cratis.Arc.Chronicle.Testing`\n- `Cratis.Arc.Core`\n- `Cratis.Arc.Core.CodeAnalysis`\n- `Cratis.Arc.EntityFrameworkCore`\n- `Cratis.Arc.MongoDB`\n- `Cratis.Arc.OpenApi`\n- `Cratis.Arc.ProxyGenerator`\n- `Cratis.Arc.ProxyGenerator.Build`\n- `Cratis.Arc.ProxyGenerator.Specs.CommandResponseHandlerDependency`\n- `Cratis.Arc.ProxyGenerator.Specs.FakeCommandResponseHandlerDependency`\n- `Cratis.Arc.Screenplay`\n- `Cratis.Arc.Swagger`\n- `Cratis.Arc.Testing`\n- `Cratis.CodeAnalysis`\n- `Cratis.Testing`\n\nIt also publishes five npm packages:\n\n- `@cratis/arc`\n- `@cratis/arc.react`\n- `@cratis/arc.react.mvvm`\n- `@cratis/arc.vite`\n- `@cratis/eslint-plugin-arc`\n\nThe release creates the GitHub tag/release, then dispatches Documentation and Samples updates after successful npm publication. It creates no container image, migration, environment change, or production deployment.\n\nPublication is non-atomic: either registry may contain a published subset if a later package fails, and Documentation/Samples dispatch can follow npm success even if NuGet failed. Recovery requires registry/downstream inventory, containment or unlisting where supported, and a corrected follow-up version; immutable versions are never reused.\n\nAny head, base, label, workflow, latest-version, package-set, destination, or artifact-plan change requires a fresh effect review. Final merge remains blocked until every current-head hosted check is terminal and successful and an authorized human completes product/release review and performs the merge.\n" + }, + { + "repo": "Arc", + "tag": "v22.4.0", + "date": "2026-08-27", + "rules": [], + "body": "## Summary\n\nCompletes generated Screenplay specification recovery across rejection, command/event, read-model, and query scenarios with exact source evidence and atomic fail-closed behavior.\n\n## Added\n\n- Recover generated command success and appended-event expectations with exact predicate values. (Cratis/Screenplay.Generation#25)\n- Recover generated read-model scenarios, inherited properties, and application-proven event placement. (Cratis/Screenplay.Generation#25)\n- Recover Stage-generated query specifications as query/read-model artifacts, `Returns`, StateView placement, and ordered argument/result values. (Cratis/Screenplay.Generation#25)\n\n## Changed\n\n- Adopt the verified Generation `0.15.0` bounded-source evidence release. (Cratis/Screenplay.Generation#25)\n- Reject computed, conditional, repeated, ambiguous, incomplete, lookalike, or unsupported scenario shapes atomically with no partial facts. (Cratis/Screenplay.Generation#25)\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.8", + "date": "2026-10-01", + "rules": [], + "body": "## Fixed\n\n- Registering generated type discovery providers no longer slows down as an application loads more assemblies. Each call now walks only the assemblies that are new since the last call, and resolves references through a lookup instead of asking every loaded assembly for its name. Before, a process that loaded or emitted an assembly between calls repeated the full walk under a process-wide lock. This is common in a spec harness that builds a service provider per spec. Cratis Studio's specs ran in 3½ minutes before this change and in 5 seconds after it.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.7", + "date": "2026-09-30", + "rules": [], + "body": "## Fixed\n\n- A project that references a package with an `[Obsolete]` type no longer fails to build when it treats warnings as errors. Type discovery still lists the type, but listing it no longer reports CS0612 or CS0618 in the generated source, which a project could neither edit nor suppress.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.6", + "date": "2026-09-24", + "rules": [], + "body": "## Fixed\n\n- TypeScript projects that use `\"moduleResolution\": \"NodeNext\"` (Node.js ESM servers and libraries) can now import `@cratis/fundamentals` and its `geospatial`, `json` and `reflection` subpaths with full type checking. The published declarations use resolvable `.js` import paths, and each entry point resolves the ESM or CommonJS declarations that match the module format being loaded.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.5", + "date": "2026-09-24", + "rules": [], + "body": "## Fixed\n\n- `JsonSerializer.deserialize` now fills arrays declared as `@field(Array, { genericArguments: [ItemType] })`, the form standard-decorator code uses. Items are deserialized as `ItemType`, including concepts and nested models; before, such an array came back empty.\n- A concept that declares `static readonly valueType` (for example `Guid`) now receives its value converted to that type, so a `ConceptAs` holds a `Guid` instead of a string.\n- Falsy payload values (`\"\"`, `0`, `false`) are deserialized through their field type, so a concept field holding an empty string or zero is wrapped in its concept like any other value.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.4", + "date": "2026-09-23", + "rules": [], + "body": "## Fixed\n\n- API documentation uses the standard `@public` tag instead of `@export`, and the decorator context types are marked `@inline`.\n- References to `Date` link to its MDN page, so they resolve in the generated API documentation.\n- Remove a stale lint suppression and add the blank lines the markdown lint expects.\n\nVerified with `yarn lint` and `tsc --noEmit` in `Source/JavaScript`." + }, + { + "repo": "Fundamentals", + "tag": "v7.19.3", + "date": "2026-09-17", + "rules": [ + "Closing or linking keyword", + "Heading not allowed", + "No release notes" + ], + "body": "Closes #1123.\n\n## The defect\n\n`DateOnly` and `TimeOnly` both render the ISO-8601 form from `toString()`, but neither defined `toJSON()`. `JSON.stringify` only calls `toJSON()` when it exists, so it fell back to enumerating the instance properties:\n\n```js\nJSON.stringify({ date: DateOnly.from(2026, 8, 30) })\n// before: {\"date\":{\"year\":2026,\"month\":8,\"day\":30}}\n// after: {\"date\":\"2026-08-30\"}\n```\n\n## How it surfaced\n\nArc for Kotlin sends RFC QUERY argument bodies through `JSON.stringify`, so query arguments of these types arrived at a JVM endpoint as component objects where a scalar ISO string was expected, and failed to bind. The GET path was never affected: it goes through `String(value)` and therefore `toString()`.\n\n`Guid` in this same package already defines `toJSON()`, and both new methods follow that precedent by delegating to the existing `toString()`.\n\n## Change\n\n- `Source/JavaScript/DateOnly.ts` — add `toJSON()`\n- `Source/JavaScript/TimeOnly.ts` — add `toJSON()`, preserving the fractional part when present\n- specs for both, covering the direct value, agreement with `toString()`, and the serialized object form\n\nNo behaviour changes for any other path; `toString()` is untouched.\n\n## Verification\n\n`vitest run` in `Source/JavaScript`: 114 files, 468 tests, all passing." + }, + { + "repo": "Fundamentals", + "tag": "v7.19.2", + "date": "2026-09-08", + "rules": [], + "body": "## Fixed\n\n- `RegisterTypeConvertersForConcepts` registers each concept type converter once per process, so a host that configures many containers no longer re-registers every concept per container, stacks TypeDescriptor providers, or serializes on TypeDescriptor's global lock\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.1", + "date": "2026-09-08", + "rules": [], + "body": "## Fixed\n\n- `TypesServiceCollectionExtensions.CurrentTypeUniverse()` and a parameterless `AddTypeDiscovery()` now run the generated-provider walk themselves, so the default type universe is complete without the host calling `GeneratedTypeDiscoveryRegistry.EnsureProvidersRegistered()` first (#1114)\n- `Globals.JsonSerializerOptions` is published only once fully built, so a concurrent first reader can no longer freeze the options before the derived-type converter is added\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.19.0", + "date": "2026-09-08", + "rules": [], + "body": "## Added\n\n- `GeneratedTypeDiscoveryRegistry.EnsureProvidersRegistered()` runs the assembly closure walk that registers generated providers, so a host that builds its type universe before `AddBindingsByConvention`/`AddSelfBindings` can make sure the provider set is complete first (#1114)\n- `TypesServiceCollectionExtensions.CurrentTypeUniverse()` returns the same `ITypes` instance `AddTypeDiscovery()` registers without providers, so a caller can hold the universe its container will resolve (#1114)\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.18.5", + "date": "2026-09-08", + "rules": [], + "body": "## Fixed\n\n- `AddTypeDiscovery()` no longer rebuilds the whole type universe on every call; the default universe is built once per set of registered generated providers and reused (#1112)" + }, + { + "repo": "Fundamentals", + "tag": "v7.18.4", + "date": "2026-09-06", + "rules": [], + "body": "## Summary\n\nThe shared ESLint presets now install with a valid ESLint 10 peer graph without force flags or downgrades. The shipped rule set is unchanged; JSX references continue to be handled by the TypeScript parser and rules.\n\n## Added\n\n- Verify the packed config against exact registry-latest consumer versions with strict peer installation, a complete dependency-tree check, JSX/rule regressions, and a native compiler version assertion (#1109)\n\n## Changed\n\n- Custom configurations enabling additional `react/*` rules must explicitly register their own compatible plugin; disabled React-rule defaults and version detection remain available (#1109)\n- Use the verified current Node/Corepack toolchain for JavaScript CI and publishing (#1109)\n\n## Fixed\n\n- Remove the unused `eslint-plugin-react` dependency that made the shared config's ESLint 10 peer graph invalid (#1109)\n- Keep native TypeScript 7 compilation separate from the official JavaScript API compatibility package used by lint and build tooling (#1109)\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.18.3", + "date": "2026-09-01", + "rules": [], + "body": "## Fixed\n\n- `AddBindingsByConvention` and `AddSelfBindings` no longer re-walk the full assembly reference closure on every call — the walk now runs only when an assembly no previous walk has processed is loaded, while assemblies loaded later still get their module constructors run on the next registration call (#1107)\n- Generated binding replay now guards duplicates with a set of registered service types instead of a linear scan of the service collection per binding, making the replay linear in the number of bindings (#1107)\n- `Types.All` is materialized once at construction instead of re-enumerating and re-hashing the full type universe on every iteration (#1107)\n\nTogether these cut the cost of registering bindings on a second and every later service collection in the same process from ~300 ms to low single-digit milliseconds in large solutions — most visible in test suites that build a container per test." + }, + { + "repo": "Fundamentals", + "tag": "v7.18.2", + "date": "2026-08-28", + "rules": [ + "Heading not allowed", + "No release notes", + "Review, verification or provenance note", + "Summary is prose" + ], + "body": "## Summary\n- `ConceptAsJsonConverter.Read()` left `value` unassigned - silently deserializing the concept to `null` - for any underlying concept value type not covered by its JSON-number branch (e.g. `char`). It now throws `UnsupportedConceptValueType` instead.\n\n## Context\n\nInvestigated as part of [Cratis/Chronicle#3847](https://github.com/Cratis/Chronicle/issues/3847) - a report that a defaulted nullable `ConceptAs`/`EventSourceId` read-model property silently returns `null` via `GetInstanceById`. The specific \"defaulted constructor parameter\" trigger described in that issue could not be reproduced against `System.Text.Json`'s constructor-binding for this converter (it constructs the concept instance directly via `Activator.CreateInstance`, entirely separate from STJ's default-value substitution path). This fix addresses a real, related defect found during that investigation in the same method - silent data loss with no error signal pointing at the cause - rather than the exact reported trigger, which remains unconfirmed.\n\n## Test plan\n- [x] `dotnet build` (Debug + Release, multi-target) - zero warnings, zero errors\n- [x] `dotnet test` across the full solution - all specs pass, including a new spec proving the number-branch failure is now loud instead of silent\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nhttps://claude.ai/code/session_01U35aWUJ4FKMqDiq67mghBo" + }, + { + "repo": "Fundamentals", + "tag": "v7.18.1", + "date": "2026-08-20", + "rules": [ + "Heading not allowed", + "No release notes" + ], + "body": "## Summary\n\nUpgrades Fundamentals to **TypeScript 7.0.2** (the new native compiler), bringing significantly faster type-checking and builds. Also bumps the pinned Yarn release from `4.5.3` to `4.17.1`, which is required to install TypeScript 7 at all.\n\n## What changed\n\n- `typescript` is now `7.0.2` (was `6.0.3`).\n- `packageManager` / `.yarnrc.yml` `yarnPath` bumped to Yarn `4.17.1` (was `4.5.3`) — Yarn 4.5.3's built-in TypeScript compatibility patch tries to rewrite a file (`lib/_tsc.js`) that the TS7 native package no longer ships, which makes install fail outright on anything older than 4.17.1.\n- `tsc -b` (the actual type-check/build step used everywhere in this repo and by consumers) runs on real TypeScript 7 and is unaffected.\n\n### Working around tooling that isn't TS7-ready yet\n\nTypeScript 7.0 ships only the `tsc` binary — it deliberately does not include the legacy programmatic compiler API (`ts.createProgram`, `ts.createDocumentRegistry`, `ts.Extension`, etc.), since a new API isn't planned until 7.1. A few dev-time tools in this repo's pipeline still call into that legacy API directly and don't support TS7 yet:\n\n- `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser`, `typescript-eslint` (typed linting)\n- `rollup-plugin-typescript2` (the rollup bundler plugin used to publish `@cratis/fundamentals`)\n\nRather than blocking the whole upgrade on upstream TS7 support, each of those tools now gets its own private TypeScript 6.0.3 dependency nested underneath it via `.yarnrc.yml` `packageExtensions`, while everything else — most importantly `tsc -b`, the thing that actually type-checks this codebase — uses real TypeScript 7.0.2. This is the same side-by-side pattern [Microsoft documents for `typescript-eslint`](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0) itself.\n\nOne spec (`for_fieldDecorator/when_executing_standard_typescript_decorator_emit.ts`) exercises TypeScript's programmatic compiler API directly (to compile and execute a decorator fixture in-memory as part of the test). It now imports that API from a dedicated nested `typescript-programmatic-api` alias (→ TS 6.0.3) instead of bare `typescript`, so it keeps working without pulling this workspace's real build back onto TS6.\n\n## Verification\n\n- `yarn install` (Yarn 4.17.1) — clean, no errors, from a fully cleared `.yarn/cache` and `yarn.lock`.\n- `tsc --version` → `7.0.2` everywhere `tsc -b` is invoked.\n- `yarn ci` (clean + lint + `tsc -b` + test, across every workspace) — **passes**: 467 tests, 0 failures, lint clean.\n- `yarn build` (the publish path: `tsc -b` + rollup bundling via `rollup-plugin-typescript2`) — **passes**, produces working CJS/ESM output and validates every `package.json` `exports` target resolves.\n\n## Impact for consumers\n\nNo public API changes. This is a build-tooling upgrade only — package consumers are unaffected. Contributors building this repo locally now need Yarn 4.17.1 (picked up automatically via Corepack from `packageManager`).\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.18.0", + "date": "2026-08-16", + "rules": [], + "body": "## Summary\n\nMake Fundamentals decorators work with both legacy and standard decorator emit while restoring clean .NET package builds after automated dependency updates.\n\n## Added\n\n- Added standard TypeScript decorator support for field and derived-type metadata while retaining legacy decorator compatibility. (Cratis/Arc#2440)\n\n## Fixed\n\n- Fixed NuGet restore failures caused by duplicate case-insensitive Handlebars package pins.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.17.1", + "date": "2026-08-05", + "rules": [ + "No release notes", + "Section heading level" + ], + "body": "# Summary\n\nLands the serialization and type-discovery work on `main`. Its packages already shipped as v7.17.0, published from this branch's head before the publish trigger was scoped to `main` — see the v7.17.0 notes for what they contain. Nothing in the packages changes on top of that: the two remaining commits scope the publish and semver-label triggers to `main`, and document `DateOnly` and `TimeOnly`." + }, + { + "repo": "Fundamentals", + "tag": "v7.17.0", + "date": "2026-08-05", + "rules": [], + "body": "## Summary\n\n**These notes have been corrected.** This version was published from the head of an open pull request rather than from `main`, because the Publish workflow cut a release on a merge into any base branch, not only into `main`. It therefore contains the whole of that branch's work, not only the `DateOnly` and `TimeOnly` types the original notes described.\n\nNothing here is withdrawn — the packages are what they are, and the list below is what they actually contain. The trigger has since been scoped to `main` so a release can only come from the branch it is released from.\n\n## Added\n\n- `DateOnly` holds a date with no time and no time zone, with `parse`, `toString`, `toDate`, `from`, `fromDate` and `equals`. A calendar date put through a JavaScript `Date` becomes UTC midnight, which every browser-local getter west of UTC reads back as the day before — an answer that looks right and is wrong only for users in some time zones, which is how it survives development in others\n- `TimeOnly` holds a time of day with no date and no time zone, with `parse`, `toString`, `from` and `equals`. A `Date` needs a date and a time of day has none, so `new Date('14:30:45')` is `Invalid Date` — the value is destroyed outright rather than merely shifted\n- `DateOnlyJsonConverter` and `TimeOnlyJsonConverter`, registered so both deserialize from the ISO-8601 form the server sends and serialize back to it\n- `JsonSerializer.registerConverter` registers a JSON converter for a type, taking the place of any converter already registered for it, so an application can correct how a type crosses the wire without forking the serializer\n- `typeKeyOf`, `declaredTypeKey`, `conceptAsTypeKey` and `valueMapTypeKey` identify a convertible type by the key it declares rather than by its class object. Comparing class objects is what breaks when two copies of this package end up in one realm, and a consuming package recognizing a concept that way has no way to be correct — `typeKeyOf(type) === conceptAsTypeKey` survives the boundary where `type === ConceptAs` cannot\n- `ITypes.DiscoveryMode` reports whether the type universe was built from generated providers, the reflection fallback, or providers supplied by the caller, so a silent fall back to reflection is visible rather than something to infer from behavior\n- `TypeDiscoveryDiagnostics.FindMissingContributors` lists project-referenced libraries that contributed nothing to the type universe, which is what a missing or misconfigured generator looks like from the outside\n- A `Cratis.Fundamentals.TypeDiscovery` event source reports the assemblies behind a type universe, and warns when it reached nothing beyond `Cratis.Fundamentals` — a universe that small is almost always a discovery failure rather than an application with no types\n- `@cratis/fundamentals` warns on load when it has been loaded more than once into the same JavaScript realm. Two copies each bring their own converter registry and their own class objects, so a converter registered on one is invisible to the other and `instanceof` across them is false, all without anything failing outright\n\n## Changed\n\n- A concept in a collection or a value map serializes as its underlying value instead of as `{\"value\": ...}`, matching how a concept serializes everywhere else; the wrapped form was an accident of the container, not a declared shape\n- Types this package converts are recognized across two copies of it loaded into the same JavaScript realm, so a `Guid` or a concept crossing that boundary keeps its declared representation rather than silently serializing as a plain object\n\n## Fixed\n\n- A `ValueMap` with primitive values lost every value on deserialization, returning the keys with nothing attached\n- `@cratis/fundamentals/json` and `@cratis/fundamentals/geospatial` were exported by the package but failed to resolve at runtime, because the barrel files the manifest pointed at were never emitted — TypeScript resolved them from source, so the break only appeared once installed. The build now fails if the manifest names a path it did not produce\n\n## Deprecated\n\n- `JsonConverter.canConvert`, which `JsonSerializer` does not call, is scheduled for removal in the next major version — implementing it has no effect today, so relying on it to exclude a type does not work\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.16.8", + "date": "2026-08-01", + "rules": [], + "body": "## Summary\n\nA collection field that is absent from the payload no longer deserializes to `undefined` behind a type that says it is an array.\n\n## Fixed\n\n- `JsonSerializer` gives a field declared as a collection an empty array when the property is missing from the payload, instead of `undefined`. Producers legitimately omit empty collections, so readers previously needed a null guard on every collection to work around a value that contradicted its own declared type. An explicit `null` still deserializes as `null`.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.16.7", + "date": "2026-07-30", + "rules": [], + "body": "## Summary\n\nObservable queries backed by an in-memory store silently returned nothing. The wrappers in `Cratis.Reactive` subscribed the source into a plain `Subject` before any consumer could attach, so a source that emits *while it is being subscribed* — a `BehaviorSubject` seed, a `ReplaySubject` buffer, anything reading in-memory state — pushed its value into a subject with no observers, and it was lost for good. Sources whose first value arrives asynchronously (a database round-trip) were unaffected, which is why this only ever surfaced against in-memory backing stores.\n\n## Changed\n\n- Subjects returned by `InvokeAndWrapWithSubject` and `InvokeAndWrapWithTransformSubject`, and `TransformingSubject` itself, replay their most recent value to new subscribers. A subscriber attaching after a value has been produced now sees the current state rather than waiting for the next change.\n\n## Fixed\n\n- Observable queries no longer lose the value their source emits synchronously on subscribe. `InvokeAndWrapWithSubject`, `InvokeAndWrapWithTransformSubject`, and `TransformingSubject` now buffer the most recent value, so the consumer that subscribes after wrapping still receives the current state.\n- The source subscription is now disposed when the wrapping subject completes, instead of leaking for the lifetime of the source.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.16.6", + "date": "2026-07-14", + "rules": [], + "body": "## Summary\n\nFollow-up to #1086: makes type discovery tolerate a provider whose type sequence throws while being enumerated.\n\n## Fixed\n\n- The `Types` constructor no longer crashes application startup when a provider's `DefinedTypes` or `SelfBindings` sequence throws `TypeLoadException` during enumeration (for example a generated provider referencing a type that a resolved assembly version no longer contains). The elements collected before the failure are kept and discovery continues.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.16.5", + "date": "2026-07-14", + "rules": [], + "body": "## Summary\n\nMakes assembly/type discovery resilient so a single type that cannot be loaded no longer crashes application startup.\n\n## Fixed\n\n- Type discovery (`ContractToImplementorsMap`) no longer aborts the whole scan — and the process — when inspecting a type whose base or interface types throw `TypeLoadException` (for example when a referenced assembly resolves to a version that no longer contains a referenced type). The unloadable type is skipped and the remaining types are still discovered.\n- `ProjectReferencedAssemblies` now tolerates assemblies whose `DefinedTypes` fail to enumerate, matching the existing behavior of `PackageReferencedAssemblies`.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.16.4", + "date": "2026-07-13", + "rules": [ + "No release notes" + ], + "body": "Add byte[] support to JSON value conversion; fix MA0206 build failures from unnecessary type declaration braces." + }, + { + "repo": "Fundamentals", + "tag": "v7.16.3", + "date": "2026-07-13", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.16.2", + "date": "2026-06-20", + "rules": [], + "body": "## Fixed\n\n- `JsonSerializer` now preserves the `_derivedTypeId` discriminator through both deserialization and serialization. Previously, deserializing a polymorphic value whose concrete type could not be resolved (e.g. its derived type was not yet registered) dropped the discriminator, so the value could never be re-resolved or round-tripped and serialized back with no type information. The discriminator now flows transparently in both directions." + }, + { + "repo": "Fundamentals", + "tag": "v7.16.1", + "date": "2026-06-19", + "rules": [], + "body": "## Fixed\n\n- `JsonSerializer` now populates fields inherited from base types when deserializing a derived (polymorphic) type. Previously `getFieldsForType` only read a type's own field metadata, so a resolved derived type came back with its inherited properties unset." + }, + { + "repo": "Fundamentals", + "tag": "v7.16.0", + "date": "2026-06-11", + "rules": [], + "body": "## Added\n\n- `@cratis/fundamentals/reflection` subpath export, allowing the Reflect metadata polyfill to be side-effect imported directly (#1079)\n\n## Fixed\n\n- `Reflect.metadata` is now provided by the metadata polyfill, so TypeScript's `emitDecoratorMetadata` output records `design:paramtypes` and constructor parameter type reflection works (e.g. with tsyringe) (#1079)" + }, + { + "repo": "Fundamentals", + "tag": "v7.15.3", + "date": "2026-06-11", + "rules": [ + "No release notes" + ], + "body": "> WARNING: Ignore version 7.15.2 - we accidentally published it and it has things that are not supported in it." + }, + { + "repo": "Fundamentals", + "tag": "v7.15.2", + "date": "2026-06-10", + "rules": [], + "body": "## Fixed\n- `PointJsonConverter` now accepts both GeoJSON format `{ type, coordinates }` and the C# default-serialized format `{ longitude, latitude }`, fixing deserialization errors in frontends consuming Arc backends without the geospatial converters registered" + }, + { + "repo": "Fundamentals", + "tag": "v7.15.1", + "date": "2026-06-09", + "rules": [], + "body": "## Summary\n\nReplaces legacy `Coordinate` type with GeoJSON-compliant geospatial types following MongoDB's GeoJSON specification. All types serialize to standard GeoJSON format for interoperability.\n\nImplements a JsonConverter infrastructure in TypeScript similar to System.Text.Json for consistent type serialization across the codebase.\n\n## Added\n\n- `Point`, `LinearRing`, `LineString`, and `Polygon` types in C# and TypeScript\n- GeoJSON converters: `PointJsonConverter`, `LineStringJsonConverter`, `PolygonJsonConverter` in both C# and TypeScript\n- TypeScript: `JsonConverter` base class and converter infrastructure similar to System.Text.Json\n- TypeScript: Separate converter modules in `json/` directory (`DateJsonConverter`, `GuidJsonConverter`, `TimeSpanJsonConverter`, `PointJsonConverter`, `LineStringJsonConverter`, `PolygonJsonConverter`, `ValueMapJsonConverter`)\n- Package.json module exports for `@cratis/fundamentals/geospatial` and `@cratis/fundamentals/json` following Arc's pattern\n- Constructor validation: LinearRing minimum 4 points, LineString minimum 2 points\n- Ring closure validation: first and last points must be identical per GeoJSON spec\n- Comprehensive test coverage: 15 spec files covering read/write operations for all JSON converters (Date, Guid, TimeSpan, Point, LineString, Polygon, ValueMap) - 312 tests total\n- Documentation explaining what each geospatial type represents, with real-world use cases (location tracking, routes, zones, geofencing, agriculture, etc.)\n\n## Changed\n\n- C#: `Coordinate` renamed to `Point` (breaking change)\n- JSON serialization format changed from `{\"longitude\":10.5,\"latitude\":20.3}` to GeoJSON `{\"type\":\"Point\",\"coordinates\":[10.5,20.3]}`\n- TypeScript: Refactored `JsonSerializer` to use centralized converter infrastructure with registered converters\n- TypeScript: All type converters moved from inline implementations to separate converter classes\n- Documentation enhanced to explain what each geospatial type represents (Point, LineString, Polygon) with comprehensive use case examples, not just serialization details\n\n## Fixed\n\n- ValueMapJsonConverter now properly registered and integrated with converter infrastructure (was created but not used)\n- Markdown lint errors (MD031/blanks-around-fences, MD024/no-duplicate-heading) in documentation files\n\n## Removed\n\n- C#: `Coordinate` record and `CoordinateJsonConverter` (breaking change)\n- TypeScript: `Coordinate` class, `CoordinateJsonConverter`, and all related tests (breaking change)\n\n---\n\n**Example Usage:**\n\n```csharp\n// C# - GeoJSON format\nvar point = new Point(10.7522, 59.9139);\nvar route = new LineString([\n new Point(10.5, 20.3),\n new Point(11.2, 21.1)\n]);\nvar park = new Polygon(\n new LinearRing([\n new Point(0, 0),\n new Point(10, 0),\n new Point(10, 10),\n new Point(0, 10),\n new Point(0, 0) // Ring must close\n ]),\n [] // Optional holes\n);\n```\n\n```typescript\n// TypeScript - same GeoJSON format with modular imports\nimport { Point, LineString } from '@cratis/fundamentals/geospatial';\nimport { PointJsonConverter } from '@cratis/fundamentals/json';\n\nconst point = new Point(10.7522, 59.9139);\nconst route = new LineString([\n new Point(10.5, 20.3),\n new Point(11.2, 21.1)\n]);\n```\n\n**Migration:** C# code must update all `Coordinate` references to `Point` and migrate stored JSON data to GeoJSON format. TypeScript code must also update all `Coordinate` references to `Point` - the Coordinate type has been completely removed from TypeScript. TypeScript projects can now import specific modules for better tree-shaking.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.15.0", + "date": "2026-06-08", + "rules": [ + "Heading not allowed", + "Section heading level" + ], + "body": "# Summary\n\nIntroduces a `Coordinate` record type representing geographic locations with longitude and latitude properties. Includes JSON converters for both C# and TypeScript with proper validation and comprehensive documentation.\n\n## Added\n\n- `Coordinate` record in `Cratis.Geospatial` namespace with `Longitude` and `Latitude` properties\n- `CoordinateJsonConverter` for C# JSON serialization with property name conversion and validation\n- TypeScript `Coordinate` class with `@field` decorators and serialization support\n- JSON serializer type converters in TypeScript with validation requiring both coordinate properties\n- Comprehensive test coverage for serialization, deserialization, and error handling in both C# and TypeScript\n- Documentation for C# usage at `Documentation/csharp/serialization/geospatial_coordinate.md`\n- Documentation for TypeScript usage at `Documentation/typescript/coordinate.md`\n\n## Example Usage\n\n**C#:**\n```csharp\nusing Cratis.Geospatial;\nusing System.Text.Json;\n\nvar coordinate = new Coordinate(10.5, 20.3);\nvar json = JsonSerializer.Serialize(coordinate);\n// {\"longitude\":10.5,\"latitude\":20.3}\n```\n\n**TypeScript:**\n```typescript\nimport { Coordinate, JsonSerializer } from '@cratis/fundamentals';\n\nconst coordinate = new Coordinate(10.5, 20.3);\nconst json = JsonSerializer.serialize(coordinate);\n// {\"longitude\":10.5,\"latitude\":20.3}\n```" + }, + { + "repo": "Fundamentals", + "tag": "v7.14.0", + "date": "2026-06-04", + "rules": [ + "Heading not allowed", + "Review, verification or provenance note" + ], + "body": "## Added\n- `@cratis/eslint-config`: a shareable ESLint flat-config package for Cratis projects. It exposes a `consumer` preset for projects that build on Cratis (house TypeScript/React hygiene plus the `for_*` BDD spec relaxations) and an `internal` preset that adds the Cratis MIT license-header rule for use inside the Cratis product repos. The `base` and `specs` building blocks are exported for custom composition.\n\n## Changed\n- Fundamentals now lints its own source through `@cratis/eslint-config`'s `internal` preset instead of a hand-maintained rule set, removing configuration that is otherwise duplicated across the Arc and Components repos. The effective rule set is unchanged: the existing lint gate passes, and the MIT license-header rule still fires on un-headered files.\n\n## Notes\n- The package ships as raw ESM (no build step) and declares only `eslint` as a peer dependency. It is the foundation that `@cratis/arc.eslint` and `@cratis/components.eslint` compose on top of.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)" + }, + { + "repo": "Fundamentals", + "tag": "v7.13.0", + "date": "2026-06-03", + "rules": [], + "body": "## Summary\n\nImplements TypeScript/JavaScript equivalent of C# `ConceptAs` for strongly-typed domain identifiers and value objects. Enables type-safe wrappers around primitives (string, number, boolean) with automatic JSON serialization and union type support for flexible assignment.\n\nThe recommended pattern exports union types from the concept file for convenience and ease of reading:\n\n```typescript\n// UserId.ts\nimport { ConceptAs } from '@cratis/fundamentals';\n\nclass UserIdConcept extends ConceptAs {}\nexport type UserId = UserIdConcept | string;\n\n// OrderCount.ts\nclass OrderCountConcept extends ConceptAs {}\nexport type OrderCount = OrderCountConcept | number;\n\n// Order.ts\nclass Order {\n // @field takes the concept class, type annotation uses the exported union type\n @field(UserIdConcept) userId!: UserId;\n @field(OrderCountConcept) count!: OrderCount;\n}\n\nconst order = new Order();\n// Can assign concept instances\norder.userId = new UserIdConcept('user-123');\norder.count = new OrderCountConcept(42);\n\n// Or assign primitives directly\norder.userId = 'user-456';\norder.count = 99;\n\n// Both serialize to primitive values: {\"userId\":\"user-456\",\"count\":99}\nconst json = JsonSerializer.serialize(order);\n\n// Always deserializes to typed concept instances\nconst restored = JsonSerializer.deserialize(Order, json);\nconsole.log(restored.userId instanceof UserIdConcept); // true\n```\n\n## Added\n\n- ConceptAs abstract class with value property, valueOf(), and toString() methods\n- Union type support with recommended export pattern: concept class with \"Concept\" suffix and clean union type export (e.g., `class UserIdConcept` → `export type UserId = UserIdConcept | string`)\n- Flexible assignment of both ConceptAs instances and primitives through exported union types\n- Recursive serialization pattern following C# implementation (recognize ConceptAs → unwrap → serialize inner value)\n- Prototype chain-based type detection in JsonSerializer for ConceptAs subclasses\n- 216 comprehensive tests covering creation, serialization, deserialization, and union type scenarios\n- Complete documentation at Documentation/typescript/concept_as.md with union type export pattern, examples, and best practices\n- Comparison table highlighting differences between C# and TypeScript implementations\n\n## Changed\n\n- JsonSerializer now follows C# pattern: recognizes ConceptAs types, unwraps to inner value, then recursively serializes using reliable type detection\n- JsonSerializer serializes both ConceptAs instances and primitives to inner values and deserializes JSON primitives back to ConceptAs instances based on @field decorator\n- Updated TypeScript documentation index and table of contents to include ConceptAs\n- Reorganized test structure: moved serialization tests from `for_ConceptAs` to `for_JsonSerializer` to reflect that serialization is a JsonSerializer concern, while keeping ConceptAs creation tests self-contained\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.12.0", + "date": "2026-06-03", + "rules": [], + "body": "## Summary\n\nImplements TypeScript/JavaScript equivalent of C# `ConceptAs` for strongly-typed domain identifiers and value objects. Enables type-safe wrappers around primitives (string, number, boolean) with automatic JSON serialization and union type support for flexible assignment.\n\nThe recommended pattern exports union types from the concept file for convenience and ease of reading:\n\n```typescript\n// UserId.ts\nimport { ConceptAs } from '@cratis/fundamentals';\n\nclass UserIdConcept extends ConceptAs {}\nexport type UserId = UserIdConcept | string;\n\n// OrderCount.ts\nclass OrderCountConcept extends ConceptAs {}\nexport type OrderCount = OrderCountConcept | number;\n\n// Order.ts\nclass Order {\n // @field takes the concept class, type annotation uses the exported union type\n @field(UserIdConcept) userId!: UserId;\n @field(OrderCountConcept) count!: OrderCount;\n}\n\nconst order = new Order();\n// Can assign concept instances\norder.userId = new UserIdConcept('user-123');\norder.count = new OrderCountConcept(42);\n\n// Or assign primitives directly\norder.userId = 'user-456';\norder.count = 99;\n\n// Both serialize to primitive values: {\"userId\":\"user-456\",\"count\":99}\nconst json = JsonSerializer.serialize(order);\n\n// Always deserializes to typed concept instances\nconst restored = JsonSerializer.deserialize(Order, json);\nconsole.log(restored.userId instanceof UserIdConcept); // true\n```\n\n## Added\n\n- ConceptAs abstract class with value property, valueOf(), and toString() methods\n- Union type support with recommended export pattern: concept class with \"Concept\" suffix and clean union type export (e.g., `class UserIdConcept` → `export type UserId = UserIdConcept | string`)\n- Flexible assignment of both ConceptAs instances and primitives through exported union types\n- Recursive serialization pattern following C# implementation (recognize ConceptAs → unwrap → serialize inner value)\n- Prototype chain-based type detection in JsonSerializer for ConceptAs subclasses\n- 216 comprehensive tests covering creation, serialization, deserialization, and union type scenarios\n- Complete documentation at Documentation/typescript/concept_as.md with union type export pattern, examples, and best practices\n- Comparison table highlighting differences between C# and TypeScript implementations\n\n## Changed\n\n- JsonSerializer now follows C# pattern: recognizes ConceptAs types, unwraps to inner value, then recursively serializes using reliable type detection\n- JsonSerializer serializes both ConceptAs instances and primitives to inner values and deserializes JSON primitives back to ConceptAs instances based on @field decorator\n- Updated TypeScript documentation index and table of contents to include ConceptAs\n- Reorganized test structure: moved serialization tests from `for_ConceptAs` to `for_JsonSerializer` to reflect that serialization is a JsonSerializer concern, while keeping ConceptAs creation tests self-contained\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.11.0", + "date": "2026-06-03", + "rules": [], + "body": "## Summary\n\nImplements TypeScript/JavaScript equivalent of C# `ConceptAs` for strongly-typed domain identifiers and value objects. Enables type-safe wrappers around primitives (string, number, boolean) with automatic JSON serialization and union type support for flexible assignment.\n\nThe recommended pattern exports union types from the concept file for convenience and ease of reading:\n\n```typescript\n// UserId.ts\nimport { ConceptAs } from '@cratis/fundamentals';\n\nclass UserIdConcept extends ConceptAs {}\nexport type UserId = UserIdConcept | string;\n\n// OrderCount.ts\nclass OrderCountConcept extends ConceptAs {}\nexport type OrderCount = OrderCountConcept | number;\n\n// Order.ts\nclass Order {\n // @field takes the concept class, type annotation uses the exported union type\n @field(UserIdConcept) userId!: UserId;\n @field(OrderCountConcept) count!: OrderCount;\n}\n\nconst order = new Order();\n// Can assign concept instances\norder.userId = new UserIdConcept('user-123');\norder.count = new OrderCountConcept(42);\n\n// Or assign primitives directly\norder.userId = 'user-456';\norder.count = 99;\n\n// Both serialize to primitive values: {\"userId\":\"user-456\",\"count\":99}\nconst json = JsonSerializer.serialize(order);\n\n// Always deserializes to typed concept instances\nconst restored = JsonSerializer.deserialize(Order, json);\nconsole.log(restored.userId instanceof UserIdConcept); // true\n```\n\n## Added\n\n- ConceptAs abstract class with value property, valueOf(), and toString() methods\n- Union type support with recommended export pattern: concept class with \"Concept\" suffix and clean union type export (e.g., `class UserIdConcept` → `export type UserId = UserIdConcept | string`)\n- Flexible assignment of both ConceptAs instances and primitives through exported union types\n- Recursive serialization pattern following C# implementation (recognize ConceptAs → unwrap → serialize inner value)\n- Prototype chain-based type detection in JsonSerializer for ConceptAs subclasses\n- 216 comprehensive tests covering creation, serialization, deserialization, and union type scenarios\n- Complete documentation at Documentation/typescript/concept_as.md with union type export pattern, examples, and best practices\n- Comparison table highlighting differences between C# and TypeScript implementations\n\n## Changed\n\n- JsonSerializer now follows C# pattern: recognizes ConceptAs types, unwraps to inner value, then recursively serializes using reliable type detection\n- JsonSerializer serializes both ConceptAs instances and primitives to inner values and deserializes JSON primitives back to ConceptAs instances based on @field decorator\n- Updated TypeScript documentation index and table of contents to include ConceptAs\n- Reorganized test structure: moved serialization tests from `for_ConceptAs` to `for_JsonSerializer` to reflect that serialization is a JsonSerializer concern, while keeping ConceptAs creation tests self-contained\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.10.6", + "date": "2026-06-01", + "rules": [], + "body": "## Changed\n- Route named and default diagnostics registrations through dedicated keyed and unkeyed meter/activity source implementations to avoid constructor-selection ambiguity during DI activation.\n- Align related specs and analyzer test sources with concise type references and update local dictionary entries for new diagnostics terminology.\n\n## Fixed\n- Prevent failures resolving `IActivitySource` and `IMeter` when an unkeyed framework `ActivitySource` or `Meter` is present in the service collection.\n- Add regression specs covering named and self-binding diagnostics resolution with unkeyed framework diagnostics registrations." + }, + { + "repo": "Fundamentals", + "tag": "v7.10.5", + "date": "2026-05-29", + "rules": [], + "body": "## Summary\n\nRelease builds were failing on net8.0 and net9.0 because the typed diagnostics wrappers depended on a keyed-DI constructor shape only available in net10, and the .NET build workflow was validating `main` instead of the PR head.\n\n## Changed\n\n- Reworked `Meter` and `ActivitySource` to resolve keyed services via `IServiceProvider` and `[ServiceKey] string?`, while preserving parameterless and explicit-instance constructors.\n- Kept named diagnostics registrations returning the same keyed `Meter` and `ActivitySource` instances for typed wrappers.\n- Updated the .NET build workflow to run on `pull_request` so CI validates PR changes instead of the base branch.\n\n## Fixed\n\n- Restored successful release builds for the diagnostics wrappers across net8.0, net9.0, and net10.0.\n- Fixed CI build validation so the .NET build workflow checks the PR head and reports the actual status of the diagnostics wrapper changes.\n- Fixed the publish workflow failure caused by constructor binding differences between target frameworks.\n\n## Security\n\n- Replaced `pull_request_target` with `pull_request` for the .NET build workflow, reducing privileged workflow execution for PR validation.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.10.4", + "date": "2026-05-25", + "rules": [ + "Section without bullets" + ], + "body": "## Summary\n\nAdds named observability DI extensions for keyed meters and activity sources, with typed resolution through `IMeter` / `IActivitySource`, and documents named registration in the metrics and traces sections.\n\n## Added\n\n- Public `AddNamedMeter(string name)` extension to register a keyed `System.Diagnostics.Metrics.Meter` and keyed open-generic `IMeter`.\n- Public `AddNamedActivitySource(string name)` extension to register a keyed `System.Diagnostics.ActivitySource` and keyed open-generic `IActivitySource`.\n- Focused DI specs for named diagnostics resolution under `for_DiagnosticsServiceCollectionExtensions`.\n- New observability docs pages for named registration:\n - `Documentation/csharp/metrics/named-registration.md`\n - `Documentation/csharp/traces/named-registration.md`\n\n## Changed\n\n- Named meter registration now reuses existing `Meter` instead of a separate `NamedMeter` implementation.\n- Named activity source registration now reuses existing `ActivitySource` instead of a separate `NamedActivitySource` implementation.\n- `Meter` and `ActivitySource` now support keyed resolution via `[FromKeyedServices]` while preserving non-keyed fallback behavior.\n- Named diagnostics registrations use keyed factory-based singleton registration for underlying `Meter` / `ActivitySource`, preserving container-managed lifetime/disposal.\n- Documentation was reorganized so named/keyed observability registration guidance lives under metrics and traces, not dependency injection:\n - Updated `Documentation/csharp/metrics/index.md` and `Documentation/csharp/traces/index.md` to link to named registration pages.\n - Updated `Documentation/csharp/metrics/toc.yml` and `Documentation/csharp/traces/toc.yml` to include named registration pages.\n - Removed named observability registration guidance from `Documentation/csharp/dependency_injection.md`.\n\n## Fixed\n\n- `IMeter` and `IActivitySource` keyed resolutions bind to the correct named underlying `Meter` / `ActivitySource` instance instead of defaulting to type-name instances.\n\n## Removed\n\n- Removed `NamedMeter` in favor of the existing `Meter` implementation.\n- Removed `NamedActivitySource` in favor of the existing `ActivitySource` implementation.\n\n## Security\n\n- No security behavior change.\n\n## Deprecated\n\n- None.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.10.3", + "date": "2026-05-24", + "rules": [], + "body": "## Fixed\n- Type discovery no longer emits `typeof()` references for assemblies loaded from a NuGet package's `tasks/` (MSBuild task helpers such as `Cratis.Arc.ProxyGenerator.Build`) or `analyzers/` folder. Previously, generated `SelfBindings` would throw `FileNotFoundException` at runtime because those DLLs are not copied to the consuming project's output." + }, + { + "repo": "Fundamentals", + "tag": "v7.10.2", + "date": "2026-05-23", + "rules": [], + "body": "## Fixed\n- `CS1705` raised in downstream consumers targeting `net8.0` or `net9.0` because the produced Cratis.Fundamentals assemblies referenced `Microsoft.Extensions.DependencyInjection.Abstractions` 10.x for every target framework. `Microsoft.Extensions.DependencyInjection`, `Microsoft.Extensions.DependencyInjection.Abstractions`, and `Microsoft.Extensions.DependencyModel` are now pinned per TFM (8.0.x for `net8.0`, 9.0.13 for `net9.0`, 10.0.8 for `net10.0`)." + }, + { + "repo": "Fundamentals", + "tag": "v7.10.1", + "date": "2026-05-23", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.10.0", + "date": "2026-05-22", + "rules": [ + "Section without bullets" + ], + "body": "## Summary\n\nThis PR adds trace instrumentation that mirrors the existing metrics model: business code can declare typed `[Span]` partial methods and consume `IActivitySource` / `IActivityScope` instead of raw `ActivitySource` / `Activity`. It also adds analyzer enforcement for undisposed activity scopes, trace documentation under `Documentation/csharp/traces` that now prefers extension-method usage, broader spec coverage across runtime, AOT, generator, and analyzer scenarios, and fixes the markdown lint issues surfaced by the new docs.\n\n```csharp\npublic static partial class OrderTraces\n{\n [Span(\"order.process\", ActivityKind.Server)]\n internal static partial IActivityScope ProcessOrder(\n this IActivitySource source,\n string orderId,\n string customerId);\n}\n\nusing var span = _source.ProcessOrder(order.Id, order.CustomerId);\n```\n\n## Added\n\n- `Cratis.Traces` abstractions and implementations: `IActivitySource`, `IActivityScope`, `ActivitySource`, `ActivityScope`, and `SpanAttribute`\n- Generator support for `[Span]` methods that start activities and map method parameters to tags\n- Analyzer `CRT0001` to require `using` disposal semantics for `IActivityScope` call sites\n- `Documentation/csharp/traces/index.md` and `Documentation/csharp/traces/roslyn.md` for the trace API and generated span support\n- Expanded runtime, AOT, generator, and analyzer specs for trace behavior and diagnostics\n- Roslyn spec coverage for generated trace extension methods, including tag generation\n\n## Changed\n\n- `Cratis.Metrics.Roslyn` generates trace wrappers alongside existing metrics generation\n- Typed `IMeter<>` and `IActivitySource<>` open generics are registered through DI\n- Tag generation supports snake_case trace tags, including acronym-aware conversion\n- The C# documentation table of contents includes the new traces section\n- Trace documentation and examples now prefer extension-method span declarations and call sites on `IActivitySource`\n\n## Fixed\n\n- The gap where trace instrumentation required direct `System.Diagnostics` primitives in application code\n- The failure mode where span scopes could be created without `using`, leaving activities unstopped and parent-child chains incomplete\n- Missing validation coverage for trace generator diagnostics and analyzer failure modes\n- Repository markdown lint violations surfaced while adding the new trace documentation\n\n## Removed\n\n- None\n\n## Security\n\n- Compile-time enforcement for disposing generated activity scopes, reducing the risk of broken trace lifetimes and incomplete span trees\n\n## Deprecated\n\n- None\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.19", + "date": "2026-05-20", + "rules": [], + "body": "## Summary\n\nPrevent the Fundamentals type-discovery generator from emitting contract and DI convention references to types that are only reachable through a non-global extern alias, and fix the root Release test build for the specs projects.\n\n## Added\n\n- Added a regression spec covering a local implementation of an interface from a non-global aliased assembly\n\n## Changed\n\n- Filter generated contract maps and convention bindings to exclude external contracts from non-global aliased references\n- Scoped the ModuleInitializerProbe reference and its probe-backed spec to the `net10.0` target in `Fundamentals.Specs`\n- Made the ModuleInitializerProbe project target framework configuration explicit for Debug and Release builds\n\n## Fixed\n\n- Prevented generated type-discovery metadata from referencing contracts that are only available through a non-global alias\n- Fixed the root `dotnet test --configuration Release` build failure caused by the `Fundamentals.Specs` multi-targeted Release build referencing a `net10.0`-only probe project\n\n## Removed\n\n- No changes\n\n## Security\n\n- No changes\n\n## Deprecated\n\n- No changes\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.18", + "date": "2026-05-19", + "rules": [ + "Section heading level", + "Section without bullets" + ], + "body": "# Summary\n\n`AddBindingsByConvention` / `AddSelfBindings` could miss generated providers when referenced assemblies were loaded but their module initializers had not yet executed, causing late `Unable to resolve service` failures. This change makes generated binding discovery deterministic by forcing provider registration before reading the registry.\n\n## Added\n\n- Added `EnsureGeneratedTypeDiscoveryProvidersAreRegistered()` to load referenced assemblies and execute module constructors prior to generated DI convention lookup.\n- Added a dedicated probe test assembly (`Fundamentals.Specs.ModuleInitializerProbe`) that contributes convention-based bindings via generated type discovery.\n- Added a regression spec covering the “assembly loaded before module initializer runs” scenario.\n\n## Changed\n\n- `TryAddGeneratedBindingsByConvention()` now primes generated discovery providers before enumerating `GeneratedTypeDiscoveryRegistry.Providers`.\n- `TryAddGeneratedSelfBindings()` now uses the same priming flow to avoid the same order-dependent miss.\n- Assembly traversal runs transitively from loaded/entry references and safely ignores non-loadable/non-managed assemblies.\n\n```csharp\n_services.AddBindingsByConvention();\nvar descriptor = _services.Single(_ => _.ServiceType.FullName == \n \"Cratis.Fundamentals.Specs.ModuleInitializerProbe.IModuleInitializerProbeService\");\n```\n\n## Fixed\n\n- Fixed order-dependent omission of convention bindings from referenced assemblies whose `[ModuleInitializer]` had not yet fired at DI registration time.\n- Fixed equivalent gap for generated self-bindings.\n\n## Removed\n\n- None.\n\n## Security\n\n- None.\n\n## Deprecated\n\n- None." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.17", + "date": "2026-05-19", + "rules": [ + "Section heading level", + "Section without bullets" + ], + "body": "# Summary\n\n`AddBindingsByConvention` / `AddSelfBindings` could miss generated providers when referenced assemblies were loaded but their module initializers had not yet executed, causing late `Unable to resolve service` failures. This change makes generated binding discovery deterministic by forcing provider registration before reading the registry.\n\n## Added\n\n- Added `EnsureGeneratedTypeDiscoveryProvidersAreRegistered()` to load referenced assemblies and execute module constructors prior to generated DI convention lookup.\n- Added a dedicated probe test assembly (`Fundamentals.Specs.ModuleInitializerProbe`) that contributes convention-based bindings via generated type discovery.\n- Added a regression spec covering the “assembly loaded before module initializer runs” scenario.\n\n## Changed\n\n- `TryAddGeneratedBindingsByConvention()` now primes generated discovery providers before enumerating `GeneratedTypeDiscoveryRegistry.Providers`.\n- `TryAddGeneratedSelfBindings()` now uses the same priming flow to avoid the same order-dependent miss.\n- Assembly traversal runs transitively from loaded/entry references and safely ignores non-loadable/non-managed assemblies.\n\n```csharp\n_services.AddBindingsByConvention();\nvar descriptor = _services.Single(_ => _.ServiceType.FullName == \n \"Cratis.Fundamentals.Specs.ModuleInitializerProbe.IModuleInitializerProbeService\");\n```\n\n## Fixed\n\n- Fixed order-dependent omission of convention bindings from referenced assemblies whose `[ModuleInitializer]` had not yet fired at DI registration time.\n- Fixed equivalent gap for generated self-bindings.\n\n## Removed\n\n- None.\n\n## Security\n\n- None.\n\n## Deprecated\n\n- None." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.16", + "date": "2026-05-18", + "rules": [], + "body": "## Fixed\n\n- TypeDiscovery generator no longer emits \\`protected\\` nested types as contracts — such types are inaccessible from generated code at namespace level (CS0122).\n- TypeDiscovery generator no longer emits types nested inside a generic outer type — their fully-qualified name includes unresolvable type parameter names (CS0246)." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.15", + "date": "2026-05-16", + "rules": [], + "body": "## Fixed\n- TypeDiscovery source generator no longer emits CS0436 warnings when the current assembly defines a type whose fully-qualified name also exists in a referenced assembly (e.g. `GrainExtensions` vs Orleans, `Program` vs `Cratis.Chronicle.Api`).\n- TypeDiscovery source generator no longer emits CS0433 errors when the same fully-qualified name exists in more than one referenced assembly (e.g. `IPIIManager`, `Constraints`, `AppendResult` duplicated across `Cratis.Chronicle.Core` and `Cratis.Chronicle`)." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.14", + "date": "2026-05-16", + "rules": [], + "body": "## Fixed\n- TypeDiscovery source generator no longer emits CS0436 warnings when the current assembly defines a type whose fully-qualified name also exists in a referenced assembly (e.g. `GrainExtensions` vs Orleans, `Program` vs `Cratis.Chronicle.Api`).\n- TypeDiscovery source generator no longer emits CS0433 errors when the same fully-qualified name exists in more than one referenced assembly (e.g. `IPIIManager`, `Constraints`, `AppendResult` duplicated across `Cratis.Chronicle.Core` and `Cratis.Chronicle`)." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.13", + "date": "2026-05-16", + "rules": [], + "body": "## Fixed\n- TypeDiscovery source generator no longer emits CS0436 warnings caused by types from other source generators (e.g. `GenerateOneOfAttribute` from `OneOf.SourceGenerator`) appearing in the generated type list.\n- TypeDiscovery source generator no longer emits CS0122 errors caused by internal types from external assemblies (e.g. Orleans' `ISystemTargetBase`, `IRingRangeListener`, `IGrainCallCancellationExtension`, `IGrainTimerRegistry`) being included as contract keys in the generated discovery map." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.12", + "date": "2026-05-16", + "rules": [], + "body": "## Fixed\n- TypeDiscovery source generator no longer emits CS0436 warnings caused by types from other source generators (e.g. `GenerateOneOfAttribute` from `OneOf.SourceGenerator`) appearing in the generated type list.\n- TypeDiscovery source generator no longer emits CS0122 errors caused by internal types from external assemblies (e.g. Orleans' `ISystemTargetBase`, `IRingRangeListener`, `IGrainCallCancellationExtension`, `IGrainTimerRegistry`) being included as contract keys in the generated discovery map." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.11", + "date": "2026-05-15", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.10", + "date": "2026-05-15", + "rules": [ + "No release notes" + ], + "body": "Fix TypeDiscovery generator: emit convention bindings from referenced Cratis.* packages, exclude entry-point Program type (CS0436), exclude Roslyn analyzer assemblies from DI scan, fix mixed-provider contract handling" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.9", + "date": "2026-05-15", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.8", + "date": "2026-05-15", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.7", + "date": "2026-05-14", + "rules": [], + "body": "Enables AOT and trimmer analysis for the Fundamentals package by setting `IsAotCompatible=true`, then resolves all resulting IL warnings across all target frameworks (net8.0, net9.0, net10.0).\n\n## Added\n\n- `true` to `Fundamentals.csproj`, enabling IL2026/IL3050/IL2046/IL2057/IL2060/IL2067–IL2075/IL2087/IL2091/IL3051 static analysis\n\n## Changed\n\n- `[RequiresDynamicCode]` / `[RequiresUnreferencedCode]` added to non-virtual public methods that genuinely use `MakeGenericType`, `MakeGenericMethod`, `Type.GetType(string)`, or runtime JSON reflection (`TypeConversion.Convert`, `RegisterTypeConvertersForConcepts(Assembly)`, `CountElements`, `GetKeyValuePairs`)\n- `[UnconditionalSuppressMessage]` applied to `JsonConverter.Read`/`Write` and `JsonConverterFactory.CreateConverter`/`CanConvert` overrides — avoids IL2046/IL3051 cascade since base class signatures carry no matching annotations; both `IL2070` and `IL2071` suppressed since net8.0 emits `IL2070` and net9.0/10.0 emits `IL2071` for the same `MakeGenericType` patterns\n- `[UnconditionalSuppressMessage]` applied to BCL-interface reflection helpers (`ImplementsOpenGeneric`, `GetEnumerableElementType`, `IsDictionary`, `GetKeyType`, `GetValueType`, etc.) where interfaces are always runtime-preserved\n- `[DynamicallyAccessedMembers(PublicConstructors)]` added to `TypeConstructorExtensions` method parameters, consistent with their `PublicConstructors` access pattern\n- `AddNamingPolicy` generic parameter annotated with `[DynamicallyAccessedMembers(PublicConstructors)]` to satisfy DI registration requirements\n- `HasDefaultConstructor` simplified to `type.IsValueType || type.GetConstructors().Any(...)`, removing a `[RequiresDynamicCode]`-tagged call to `GetTypeInfoDetails()` while preserving value-type semantics" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.6", + "date": "2026-05-14", + "rules": [], + "body": "## Fixed\n\n- `Types` and `AddTypeDiscovery` now always include `PackageReferencedAssemblies` alongside any generated type discovery providers, ensuring that NuGet packages without their own source-generated provider (e.g. `Cratis.Arc.*`, `Cratis.Chronicle.*`) are still visible to the type system and DI convention binding works correctly." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.5", + "date": "2026-05-14", + "rules": [], + "body": "## Fixed\n- Open generic types (e.g. `ConceptAs`, `ConceptAsTypeConverter`) were incorrectly included in the generated type-discovery provider as implementors of themselves. At runtime this caused `ArgumentException: The type 'System.Void' may not be used as a type argument` during startup when registering type converters for concepts." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.4", + "date": "2026-05-14", + "rules": [], + "body": "## Fixed\n- Prevented release publishing failures by excluding internal spec projects from NuGet packaging so package generation completes without NU5026 errors.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.3", + "date": "2026-05-14", + "rules": [], + "body": "## Added\n\n- Serialization specs covering base-class derived type lookup and HasDerivatives behavior for known base types.\n- Documentation clarifying backend target resolution and frontend runtime candidate resolution from both field derivatives and runtime derived-type registry.\n\n## Changed\n\n- Register derived types for non-system base classes in addition to the primary target type so polymorphic deserialization can resolve by base class without explicit targetType.\n- Preserve existing validation semantics for ambiguous targets and target mismatches.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.9.2", + "date": "2026-05-12", + "rules": [], + "body": "## Fixed\n- `derivedType` decorator now walks the full prototype chain and registers the decorated class against every ancestor type, enabling `JsonSerializer` to discover derived types without requiring explicit subtype imports (which cause circular dependencies)." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.1", + "date": "2026-05-11", + "rules": [], + "body": "## Fixed\n- Corrected target type resolution logic in `DerivedTypes` so that a known `targetType` is validated with `IsAssignableTo` and returned early, and the interface-based fallback only runs when `targetType` is null.\n- TypeScript `derivedType` decorator now accepts an optional `targetType`, registering the decorated class against its base type for automatic discovery during deserialization.\n- `JsonSerializer` deserialization now resolves derived types from both explicit field derivatives and types registered via `DerivedType.getDerivedTypesFor`, eliminating the need to list every subtype on every field.\n- `JsonSerializer` serialization now writes the `_type` discriminator property automatically when the instance's constructor has a registered derived type identifier, ensuring derived type instances round-trip correctly." + }, + { + "repo": "Fundamentals", + "tag": "v7.9.0", + "date": "2026-05-08", + "rules": [], + "body": "## Added\n\n- A Roslyn type discovery generator that emits precomputed contracts, implementors, and convention bindings for runtime consumption.\n- A generated type discovery registry and provider contracts for supplying precomputed metadata to discovery and DI layers.\n- A dedicated `Fundamentals.TypeDiscovery.Generator.Specs` project with focused coverage for symbol classification and metadata collection.\n- A `Fundamentals.AOT.Specs` project validating generated metadata registration and discovery behavior in AOT-oriented scenarios.\n\n## Changed\n\n- Type discovery and service registration paths now consume generated providers alongside existing discovery mechanisms.\n- The TypeDiscovery sample now demonstrates generated metadata, convention bindings, and AOT-oriented usage.\n- Core and specs projects were wired to include the new generator/specs projects in the solution and test matrix.\n\n## Fixed\n\n- Spec naming guidance now explicitly prevents nested `for_` folders and standardizes the `for_` → `when_` → `and_` structure to avoid future misclassification." + }, + { + "repo": "Fundamentals", + "tag": "v7.8.2", + "date": "2026-05-05", + "rules": [], + "body": "## Fixed\n- Prevented concept-keyed dictionaries from being matched by the enumerable concept converter, avoiding incompatible converter assignment during serialization setup.\n- Added regression specs for converter selection and JSON roundtrip of concept-as-string dictionary keys." + }, + { + "repo": "Fundamentals", + "tag": "v7.8.1", + "date": "2026-05-02", + "rules": [], + "body": "## Fixed\n- `DerivedTypeId` now accepts any arbitrary string instead of requiring a GUID format, preventing a `FormatException` at startup when `[DerivedType]` attributes use descriptive identifiers such as `\"credit-card\"` or `\"paypal\"`." + }, + { + "repo": "Fundamentals", + "tag": "v7.8.0", + "date": "2026-04-29", + "rules": [], + "body": "## Added\n- `ComplexKeyDictionaryJsonConverterFactory` for JSON serialization of `IDictionary` where `TKey` is a complex type, encoding keys as JSON strings\n- `ValueMap` TypeScript type with complex-key equality support using JSON-stringify comparison\n- `genericArguments` option on the `@field()` decorator for typed key/value deserialization of `ValueMap` fields\n- Full serialization/deserialization support for `ValueMap` in `JsonSerializer`\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.11", + "date": "2026-04-28", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.10", + "date": "2026-04-28", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.9", + "date": "2026-04-28", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.8", + "date": "2026-04-28", + "rules": [ + "Heading not allowed", + "No release notes" + ], + "body": "## Fixes\r\n\r\n- Only add the entry assembly to `_assemblies` if it is not already present in the project referenced assemblies list\r\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.7", + "date": "2026-04-28", + "rules": [ + "Heading not allowed", + "No release notes" + ], + "body": "## Fixes\r\n\r\n- Only add the entry assembly to `_assemblies` if it is not already present in the project referenced assemblies list\r\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.6", + "date": "2026-04-27", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.5", + "date": "2026-04-27", + "rules": [ + "No release notes" + ], + "body": "No release notes" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.4", + "date": "2026-04-27", + "rules": [], + "body": "## Fixed\n\n- Adding entry assembly as a project referenced assembly for the discovery.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.3", + "date": "2026-03-10", + "rules": [], + "body": "## Fixed\n\n- Upgrading Humanizer to avoid problems upstream.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.2", + "date": "2026-03-08", + "rules": [ + "Agent transcript", + "Section heading level" + ], + "body": "Both `AddBindingsByConvention` and `AddSelfBindings` should be no-ops for any service type that already has a registration. `AddBindingsByConvention` was correct; `AddSelfBindings` had an inverted condition that made it effectively always register, ignoring pre-existing bindings.\n\n### Fixed\n\n- `AddSelfBindings` had `services.Any(s => s.ServiceType != _)` — logically always `true` when any other service is registered, so it never skipped duplicates. Fixed to `!services.Any(s => s.ServiceType == _)`.\n\n### Added\n\n- Spec: `AddSelfBindings` does not add a duplicate when the concrete type is already registered\n- Spec: `AddBindingsByConvention` does not add a duplicate when the interface is already bound\n\n\n---\n\n✨ Let Copilot coding agent [set things up for you](https://github.com/Cratis/Fundamentals/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.1", + "date": "2026-02-23", + "rules": [ + "No release notes" + ], + "body": "## Summary\nUpdate dependencies" + }, + { + "repo": "Fundamentals", + "tag": "v7.7.0", + "date": "2026-02-22", + "rules": [], + "body": "## Added\n\n- `[Scoped]` attribute for registering scoped services by convention\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.6.0", + "date": "2026-02-14", + "rules": [], + "body": "## Summary\n\nThis update enhances the type conversion capabilities for concepts by improving the `ConceptAsTypeConverter` and `TypesExtensions`.\n\n## Added\n\n- New conversion methods in `ConceptAsTypeConverter` for better handling of concept value types.\n- Extension method on Assembly for adding concept type converters on concept types in the assembly.\n\n## Changed\n\n- Refactored type converter registration logic for clarity and maintainability.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.5.1", + "date": "2026-02-10", + "rules": [], + "body": "## Fixed\n\n- Increasing TypeScript module compatibility by removing `\"type\": \"module\"` and adding a post build step for generating correct package.json for CJS and others.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.5.0", + "date": "2026-01-15", + "rules": [], + "body": "## Added\n\n- Added `IsNullable` extensions for both `PropertyInfo` and `ParameterInfo`.\n\n## Fixed\n\n- Simplifying `HasAttribute` implementation by leveraging the `Attribute.IsDefined` - should be more performant as well.\n" + }, + { + "repo": "Fundamentals", + "tag": "v7.4.0", + "date": "2026-01-02", + "rules": [], + "body": "## Added\n\n- Supporting the use of annoymous types when using `BeginScope()` for the `IMeter<>` type (#264).\n\n## Fixed\n\n- Making the Metrics Roslyn extension use incremental generation (#371).\n- Fixing parsing of `TImeSpan` values from JSON.\n" + }, + { + "repo": "Components", + "tag": "v4.22.0", + "date": "2026-09-30", + "rules": [], + "body": "## Added\n\n- `Message` accepts `live={false}` to render without its `alert`/`status` role, so it can be composed inside an existing live region without being announced twice (#385)\n" + }, + { + "repo": "Components", + "tag": "v4.21.1", + "date": "2026-09-29", + "rules": [], + "body": "The renderer adapters and Conformance now install on Node.js 22, and the migration guide describes how adapter versions pair with `@cratis/components`.\n\n## Fixed\n\n- `@cratis/components.mui`, `@cratis/components.primereact`, `@cratis/components.primereact10` and `@cratis/components.conformance` declared `engines.node >=23.0.0`, so npm and pnpm with `engine-strict` refused to install them on Node.js 22, a maintained LTS release. They now declare `>=22.0.0`. CI installs each adapter on Node.js 22 with `engine-strict` on. (#371)\n- `MIGRATION.md` said the adapters declare a `@cratis/components >=4 <5` peer range. Each published adapter and Conformance release requires exactly the `@cratis/components` version it was released with, so install and upgrade them together. (#370)\n" + }, + { + "repo": "Components", + "tag": "v4.21.0", + "date": "2026-09-29", + "rules": [ + "Closing or linking keyword" + ], + "body": "Toolbars can offer a single Tab stop, chat hosts can add their own content to messages and the sidebar header, and `DataTableCore` can have its sort, filter and search state controlled by the host.\n\n## Added\n\n- `ToolbarFocusMode.SingleTabStop` for `Toolbar` and `ActionMenubar` implements the WAI-ARIA toolbar pattern. The toolbar's own tools share one Tab stop, which starts at the first available tool, moves with the arrow keys and returns to the last focused tool. When that tool is disabled, hidden or removed, the Tab stop moves to the next available tool. Controls you add yourself, widgets such as inputs, tools rendered outside the toolbar's markup (for example through a portal), and tools with an explicit tab index keep their own Tab stop. `Arrows` remains the default until the next major release. (#353)\n- `ChatConversation.renderMessageExtra` renders your content under a message body, for example reactions or a notice that a reply failed. `ChatSidebar` forwards it. (#194)\n- `ChatSidebar.renderHeaderActions` renders your content between the sidebar title and its close button, and receives the open topic. Neither slot adds markup when you leave it unset. (#194)\n- `DataTableCore` accepts `sort` with `onSortChange`, `filters` (reported through the existing `onFilter`), and `globalFilter` with `onGlobalFilterChange`, so the host can own that state. When a value is left undefined, the table keeps managing it as before. Set `rowProcessing={DataTableRowProcessing.None}` to render rows exactly as given when the source has already filtered and sorted them. `DataTableSort`, `DataTableSortDirection` and `DataTableRowProcessing` are exported from `@cratis/components/DataTables`. (#178)\n\nCloses #353, closes #194. Refs #178.\n" + }, + { + "repo": "Components", + "tag": "v4.20.0", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword" + ], + "body": "Chat topic lists can offer per-topic actions, and applications no longer need to install `tsyringe` themselves.\n\n## Added\n\n- `ChatTopicList` and `ChatSidebar` accept `topicActions`: labeled buttons next to each topic, such as Rename, Pin or Archive, with an optional `isAvailable` check per topic. Each button's accessible name includes its topic, the buttons can be reached with the keyboard, and activating one does not open the topic. When an action hides itself or removes its topic, focus moves to that topic, to the topic that took its place, or to New topic, unless your handler already moved focus. `ChatSidebar`'s existing `actions` prop still means message actions. Topics without actions render as before. The `ChatTopicAction` type is exported. (#206)\n\n## Changed\n\n- `tsyringe` is now an optional peer. Components never imports it, and Arc React installs it as its own dependency, so you no longer need to install it yourself. `reflect-metadata` stays a required peer: keep installing it and importing it at your entry point. The package check now fails if a published file imports either package. (#248)\n\nCloses #206, closes #248.\n" + }, + { + "repo": "Components", + "tag": "v4.19.2", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword" + ], + "body": "Published JavaScript and declaration files now ship working source maps.\n\n## Fixed\n\n- Source maps survive the build step that rewrites relative ESM import specifiers. Before, every rewritten `.js` and `.d.ts` file lost its map. JavaScript maps now embed their original sources, so stack traces and debuggers show the component source. Declaration maps keep go-to-definition working wherever the sources are available, such as linked or monorepo checkouts. The package grows from about 723 KB to about 1.0 MB, all of it source maps; production bundlers ignore them. (#213)\n- A packed-package check verifies the maps of every file the build rewrote: the exact source positions after each edit, that no stale map is left, and TypeScript's go-to-definition. (#213)\n\nCloses #213.\n" + }, + { + "repo": "Components", + "tag": "v4.19.1", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword" + ], + "body": "Filter panels stay out from under classic scrollbars, and toolbar icons are vertically centered.\n\n## Fixed\n\n- `FilterPanel` keeps its panel inside the visible area when the page shows classic, non-overlay scrollbars. Before, a panel opened near the right or bottom edge could extend under the scrollbar. Fixed panels now follow the browser's layout viewport, the same way in every browser. Rotated or skewed overlay containers are not supported; translated and scaled ones are. (#356)\n- `ToolbarButton` icons are vertically centered for icon fonts and SVG icons, in regular and list mode, even under an inherited line height. An icon part hidden with the `hidden` attribute through `pt.icon` stays hidden. (#321)\n\nCloses #356, closes #321.\n" + }, + { + "repo": "Components", + "tag": "v4.19.0", + "date": "2026-09-28", + "rules": [ + "Closing or linking keyword" + ], + "body": "Toolbars now support arrow-key navigation, the PivotViewer card area scrolls from the keyboard, the filter panel works inside dialogs, and its accessible names can be localized in one place.\n\n## Added\n\n- `Toolbar` and `ActionMenubar` move focus between tools with the arrow keys and Home/End, following the toolbar's orientation and reading direction. Every tool keeps its own Tab stop, so Tab works as before. Keys inside inputs, sliders, selects and other widgets in a toolbar keep their native behavior, and a handler that calls `preventDefault` still wins. The new `focusMode` prop (`ToolbarFocusMode`) is `Arrows` by default; use `None` to keep the previous behavior. A mode with a single Tab stop for the whole toolbar is planned in #353. (#328)\n- The `PivotViewer` card viewport is a keyboard-focusable, named scroll region. Arrow and Page keys scroll overflowing cards, and Tab moves on to the next control. Use `labels.viewport` to localize its default name, “Card area.” Canvas cards themselves are still not keyboard-selectable. (#349)\n- `CratisComponentsProvider` `messages.filter` localizes the filter panel's accessible name (`label`) and its search inputs' names (`searchAriaLabel`) for the whole application. An explicit prop still wins. For search inputs, the placeholder is used when the provider message is not set, so existing names do not change. `messages.filter.label` also names the `PivotViewer` Filters button when `labels.filters` is not set. The `CratisFilterMessages` type is exported. (#347)\n\n## Fixed\n\n- `FilterPanel` uses the overlay container configured by `CratisComponentsProvider` instead of always portaling to `document.body`. An unavailable container defers the panel. Inside a modal `Dialog`, the panel stays within the modal's focus scope and stays visible to assistive technology, even when the shared overlay container sits outside the modal. (#346)\n- A filter panel stays aligned with its trigger and within reach while a Dialog animates in, inside a transformed overlay container, and after scroll and resize. Inside a dialog that clips its content, the panel keeps every option reachable. (#346)\n\nCloses #328, closes #346, closes #347, closes #349.\n" + }, + { + "repo": "Components", + "tag": "v4.18.0", + "date": "2026-09-27", + "rules": [], + "body": "## Added\n\n- `--cratis-canvas-minimap-grid-color` and `--cratis-canvas-minimap-item-color` theme the Canvas minimap dot grid and items that have no `color` of their own. Both fall back to the current colors, so the minimap looks the same until you set them.\n" + }, + { + "repo": "Components", + "tag": "v4.17.0", + "date": "2026-09-27", + "rules": [], + "body": "## Added\n\n- `controlsIcons` on `Canvas` (`icons` on `CanvasControls`) replaces any of the minimap, zoom-out, zoom-in and help glyphs with your own icons. Unset fields keep the built-in icons, and the accessible names still come from `controlsLabels`.\n- `controlsFollowViewportInsets` on `Canvas` (`followViewportInsets` on `CanvasControls`) turns off the offset by `--canvas-viewport-left`/`--canvas-viewport-right`, for hosts that already move the canvas by that inset. It defaults to `true`, so existing canvases are unchanged.\n- The `--canvas-viewport-left` and `--canvas-viewport-right` custom properties the controls read are now documented.\n" + }, + { + "repo": "Components", + "tag": "v4.16.0", + "date": "2026-09-27", + "rules": [ + "Closing or linking keyword", + "Issue reference position" + ], + "body": "Command dialogs can now ask for confirmation before they execute, Chat shows loading and failure states, and FilterPanel works from the keyboard and with screen readers.\n\n## Added\n\n- `confirmBeforeExecute` on `CommandDialog`, `StepperCommandDialog`, and `CommandStepper` asks a question after validation and `onBeforeExecute`, and before the command runs. Only `true` executes. Any other answer leaves the dialog or stepper open without running the command. If the command values change while the question is open, the command is not executed and you confirm again. Stepper fields are disabled until the confirmed submission settles. Without the prop, command execution is unchanged. The `ConfirmBeforeExecute` type is exported. (#317)\n- `ChatSidebar` accepts `topicsStatus` and `messagesStatus` (the new `ChatStatus` enum), and `ChatSidebarForObservableQueries` sets them from its queries. The topic list and conversation show loading, failure, and unauthorized text from new `labels` keys, and never show server exception text. A failure keeps what is already shown and adds an alert above it. Unauthorized replaces the content and disables the composer and the New topic button. (#327)\n- `PivotViewer` `labels` makes the toolbar text, the item count, and the filter panel's name and search placeholder localizable. The defaults are unchanged. `loadingLabel` still takes precedence over `labels.loading`. (#334)\n- `PivotViewer` `labels.searchGroup` names the search box of each option group in the filter panel. It receives the group's label and defaults to `Search `, so each group search now has its own accessible name. (#334)\n- FilterPanel `aria-label` and `searchAriaLabel`, `FilterDefinition` `searchAriaLabel` and `autoFocus`, and `CheckboxListFilter` `searchAriaLabel` and `autoFocusSearch`. (#328, #244)\n- A `DatePickerInput` reference page, and Common and Dialogs landing pages that help you choose a control. (#260)\n\n## Changed\n\n- `Tooltip` `content` accepts any React node, and `0` now shows. `undefined`, `null`, booleans, and an empty string still disable the tooltip. Keep tooltip content non-interactive. Renderer adapters that implement `common.tooltip` receive nodes and must render them as they are; the Conformance profile now checks this. (#230)\n- FilterPanel is now a named, non-modal dialog. Opening it moves focus into the panel, and Escape closes it and returns focus to the element that opened it. It now renders as a `div` with `role=\"dialog\"` instead of an `