Skip to content

Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0#16

Merged
Powerworks merged 1 commit into
devfrom
dependabot/nuget/code/K9Crush-scaffold/K9Crush/Testcontainers.PostgreSql-4.13.0
Jul 21, 2026
Merged

Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0#16
Powerworks merged 1 commit into
devfrom
dependabot/nuget/code/K9Crush-scaffold/K9Crush/Testcontainers.PostgreSql-4.13.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 21, 2026

Copy link
Copy Markdown
Contributor

Updated Testcontainers.PostgreSql from 3.10.0 to 4.13.0.

Release notes

Sourced from Testcontainers.PostgreSql's releases.

4.13.0

What's Changed

Thank you to everyone who contributed and shared their feedback 🤜🤛.

The NuGet packages for this release have been attested for supply chain security using actions/attest. This confirms the integrity and provenance of the artifacts and helps ensure they can be trusted: #​33686956.

🚀 Features

  • feat: Add Aspire dashboard module (#​1194) @​NikiforovAll
  • feat: Add image name substitution hook (#​1710) @​HofmeisterAn
  • feat(CosmosDb): Add get method AccountEndpoint (#​1707) @​srollinet
  • feat: Improve image build failure messages (#​1700) @​HofmeisterAn

🐛 Bug Fixes

  • fix: Restore tar archive write performance regressed by padding trim (#​1719) @​HofmeisterAn
  • chore: Bump Docker.DotNet from 4.3.2 to 4.3.3 (#​1714) @​HofmeisterAn
  • chore(AspireDashboard): Cover connection string provider (#​1713) @​HofmeisterAn

📖 Documentation

  • docs: Add missing TC languages and reorder docs navigation (#​1711) @​mdelapenya
  • docs: Add note about unsupported BuildKit Dockerfile features (#​1696) @​HofmeisterAn
  • docs: Explain immutable builder behavior (#​1693) @​HofmeisterAn

🧹 Housekeeping

  • chore: Enable Dependabot cooldown (#​1716) @​HofmeisterAn
  • chore: Add nuget.config (#​1715) @​Rob-Hague
  • chore(AspireDashboard): Cover connection string provider (#​1713) @​HofmeisterAn
  • chore: Bump Docker.DotNet from 4.2.0 to 4.3.2 (#​1712) @​HofmeisterAn
  • chore: Bump sshd-docker image from 1.3.0 to 1.4.0 (#​1709) @​HofmeisterAn
  • chore: Rename runtime label and add buildkit and stale labels (#​1703) @​HofmeisterAn
  • fix: Guard expensive argument evaluation when logging (#​1702) @​HofmeisterAn
  • chore: Defer container ID truncation in logging (#​1701) @​HofmeisterAn
  • chore: Migrate to LoggerMessageAttribute (#​1697) @​HofmeisterAn

📦 Dependency Updates

  • chore(deps): Bump the actions group with 2 updates (#​1721) @dependabot[bot]
  • chore(deps): Bump the actions group with 7 updates (#​1717) @dependabot[bot]
  • chore: Bump Docker.DotNet from 4.3.2 to 4.3.3 (#​1714) @​HofmeisterAn
  • chore: Bump Docker.DotNet from 4.2.0 to 4.3.2 (#​1712) @​HofmeisterAn
  • chore(deps): Bump the actions group with 4 updates (#​1698) @dependabot[bot]

4.12.0

What's Changed

Thanks to all contributors 👏.

The NuGet packages for this release have been attested for supply chain security using actions/attest. This confirms the integrity and provenance of the artifacts and helps ensure they can be trusted: #​28009236.

⚠️ Breaking Changes

  • chore(deps): Bump Docker.DotNet from 3.131.1 to 4.0.2 (#​1665) @​HofmeisterAn

🚀 Features

  • feat: Add Floci module (#​1690) @​object
  • feat: Ignore port-forwarding extra host in reuse hash (#​1689) @​HofmeisterAn
  • feat: Allow devs to override the reuse hash calculation (#​1688) @​HofmeisterAn
  • feat: Add connect to network API (#​1672) @​HofmeisterAn
  • feat(LocalStack): Require auth token for 4.15 and onwards (#​1667) @​HofmeisterAn
  • chore(deps): Bump Docker.DotNet from 3.131.1 to 4.0.2 (#​1665) @​HofmeisterAn

🐛 Bug Fixes

  • fix: Trim tar record padding to avoid broken-pipe failure on Podman (#​1684) @​artiomchi
  • fix(Nats): Use healthz API for readiness probe (#​1679) @​eriblo01
  • fix: Remove KeepAlive socket option (#​1671) @​Angelinsky7

📖 Documentation

  • docs: Extend WithCommand(params string[]) documentation (#​1685) @​HofmeisterAn

🧹 Housekeeping

  • feat: Prepare next release cycle (4.12.0) (#​1664) @​HofmeisterAn

📦 Dependency Updates

  • chore(deps): Bump the actions group with 5 updates (#​1687) @dependabot[bot]
  • chore(deps): Bump Docker.DotNet from 4.1.0 to 4.2.0 (#​1686) @​HofmeisterAn
  • chore(deps): Bump the actions group with 5 updates (#​1676) @dependabot[bot]
  • chore(deps): Bump Docker.DotNet from 4.0.2 to 4.1.0 (#​1674) @​HofmeisterAn
  • chore(deps): Bump Docker.DotNet from 3.131.1 to 4.0.2 (#​1665) @​HofmeisterAn

4.11.0

What's Changed

Thanks to all contributors. Once again, really great contributions from everyone 🤝.

The NuGet packages for this release have been attested for supply chain security using actions/attest. This confirms the integrity and provenance of the artifacts and helps ensure they can be trusted: #​21198535.

Please be aware that we have changed the supported and underlying image used for the Cosmos DB module. The latest tag only supports certain environments and provides a limited set of features. Microsoft has introduced a new implementation, vnext-preview, which receives more updates and features. Due to the limitations of the latest tag, we decided to replace it with vnext-preview. You find more information about the image here: https://github.com/Azure/azure-cosmos-db-emulator-docker.

⚠️ Breaking Changes

  • feat(CosmosDb): Update base image from latest to vnext-preview (#​1324) @​NelsonBN

🚀 Features

  • feat(CosmosDb): Update base image from latest to vnext-preview (#​1324) @​NelsonBN
  • feat: Add typed WithResourceMapping(...) overloads (#​1497) @​cimnine
  • feat: Add Seq module (#​1276) @​montanehamilton
  • feat(PostgreSql): Add WithSsl builder API (#​1529) @​ozkanpakdil
  • feat: Add Temporal module (#​1635) @​bgener
  • feat: Add module connection string provider (#​1632) @​HofmeisterAn
  • fead: Add default container connection string provider (#​1630) @​HofmeisterAn
  • feat(ServiceBus): Add method to get HTTP connection string (#​1622) @​NelsonBN

🐛 Bug Fixes

  • fix(MongoDb): Wait for post-init startup readiness before replica set initiation (#​1656) @​HofmeisterAn
  • fix(Seq): Assert connection string provider (#​1645) @​HofmeisterAn
  • fix(EventHubs): Ignore runtime property to support reuse (#​1644) @​franciscosamuel
  • fix(ServiceBus): Ignore runtime property to support reuse (#​1643) @​franciscosamuel
  • fix(ResourceReaper): Set wait strategy (#​1634) @​HofmeisterAn
  • fix(ServiceBus): Workaround health API timeout (#​1625) @​HofmeisterAn
  • fix: Do not set console buffer width (ConsoleLogger) (#​1623) @​HofmeisterAn
  • fix(EventHubs): Workaround health API timeout (#​1624) @​HofmeisterAn

📖 Documentation

  • docs(CosmosDb): Replace unsupported tag 'latest' with 'vnext-preview' (#​1660) @​HofmeisterAn
  • docs(Redis): Add example (#​1641) @​HofmeisterAn

🧹 Housekeeping

  • chore(examples): Enable NuGet restore lock mode (#​1659) @​HofmeisterAn
  • chore: Pin image digest (#​1658) @​HofmeisterAn
  • fix: Run OpenSSF Scorecard only on default branch (#​1657) @​HofmeisterAn
  • chore: Remove Git LFS tracking for .snk (#​1655) @​HofmeisterAn
  • feat: Enable Dependabot for NuGet (repo) (#​1654) @​HofmeisterAn
  • feat: Enable Dependabot for NuGet (src) (#​1653) @​HofmeisterAn
  • fix: Replace branch protection with ruleset (#​1652) @​HofmeisterAn
  • fix: Do not enforce policies for admins (#​1651) @​HofmeisterAn
  • fix: Remove missing labels from Dependabot (#​1650) @​HofmeisterAn
    ... (truncated)

4.10.0

What's Changed

Happy New Year, everyone! 🎉

Please note that going forward, we expect developers to explicitly pin the image version (testcontainers/testcontainers-dotnet#1470). We consider this a best practice and it aligns with other language implementations.

Also, due to the recent Docker Engine v29 release, TC for .NET pins the Docker Engine API version to 1.44 (see the previous release notes). You can override this default and set it to the version you're using, ideally 1.52, which corresponds to v29, if you're already running it.

⚠️ Breaking Changes

  • feat: Add Docker Engine v29 support (#​1609) @​HofmeisterAn
  • chore: Remove EventStoreDb module (#​1599) @​HofmeisterAn

🚀 Features

  • feat: Require explicit container image in Testcontainers.Xunit (#​1612) @​0xced
  • feat: Add Platform property to IImage interface (#​1610) @​HofmeisterAn
  • feat: Add Docker Engine v29 support (#​1609) @​HofmeisterAn
  • feat: Require explicit container image when creating container builder (#​1584) @​digital88
  • feat: Add connection string provider (#​1588) @​HofmeisterAn

🐛 Bug Fixes

  • fix(Kafka): Bump image version to prevent container crash on startup (#​1604) @​HofmeisterAn
  • fix(Elasticsearch): Use HTTP wait strategy (#​1593) @​digital88
  • fix(Milvus): Use healthcheck wait strategy (#​1585) @​verdie-g

📖 Documentation

  • docs: Pin the image version explicitly (#​1605) @​HofmeisterAn

🧹 Housekeeping

  • chore: Remove Sonar findings (#​1611) @​HofmeisterAn
  • chore: Pin Docker Engine API for GH workflow to 1.47 (#​1608) @​HofmeisterAn
  • chore: Set remaining container image explicit (#​1606) @​digital88
  • fix(Kafka): Bump image version to prevent container crash on startup (#​1604) @​HofmeisterAn
  • chore: Remove EventStoreDb test project from SLNX file (#​1603) @​0xced
  • chore: Skip unnecessary work for empty or null sequences (#​1601) @​HofmeisterAn
  • chore: Delegate container builder ctor string to IImage (#​1600) @​HofmeisterAn
  • chore: Remove EventStoreDb module (#​1599) @​HofmeisterAn
  • feat: Prepare next release cycle (4.10.0) (#​1586) @​HofmeisterAn

4.9.0

What's Changed

This release adds a new configuration (DOCKER_API_VERSION) that lets you pin and downgrade the Docker Engine API version. This was needed because Docker Engine v29 introduced breaking changes that affect Docker.DotNet and Testcontainers for .NET. This release pins the API version to 1.44. So far, no issues or negative side effects have been observed.

I am also working on updating Docker.DotNet to make it fully compatible with Docker Engine v29. There is already a work-in-progress PR.

Thanks to all the contributors who helped with this release 👍.

⚠️ Breaking Changes

  • feat: Add KurrentDb module (#​1583) @​diegosasw

🚀 Features

  • feat: Add KurrentDb module (#​1583) @​diegosasw
  • chore: Bump NuGet dependencies (#​1578) @​HofmeisterAn
  • feat: Add .NET 10 support (#​1572) @​HofmeisterAn
  • feat: Support configuring Docker API version (#​1576) @​HofmeisterAn
  • feat: Add Mosquitto module (#​1522) @​EtherZa
  • feat: Add Toxiproxy module (#​1454) @​iltertaha
  • feat: Add Grafana module (#​1509) @​thomhurst
  • feat: Add Playwright module (#​1288) @​alimahboubi

🐛 Bug Fixes

  • fix(Milvus): Set DEPLOY_MODE=STANDALONE (necessary for v2.6+) (#​1569) @​verdie-g
  • fix: Set Kusto wait strategy encoding to UTF-8 (#​1567) @​MattKotsenas
  • fix: Split ALL_CHANGED_FILES on any whitespace (#​1566) @​HofmeisterAn

📖 Documentation

  • docs: Use correct comment characters for C# language (#​1564) @​HofmeisterAn

🧹 Housekeeping

  • feat: Add SLNX file (#​1579) @​HofmeisterAn
  • chore: Update Toxiproxy NuGet dependency to a .NET compatible version (#​1568) @​HofmeisterAn
  • chore: Add script to detect which tests to run in CI (#​1563) @​HofmeisterAn
  • chore: Make the continuous delivery job fork-friendly (#​1559) @​0xced
  • feat: Prepare next release cycle (4.9.0) (#​1561) @​HofmeisterAn

4.8.1

What's Changed

🐛 Bug Fixes

  • fix: Compute correct relative Dockerfile file path (#​1558) @​HofmeisterAn

4.8.0

What's Changed

Thank you to all the contributors 🙌.

In version 4.7.0, we noticed that the reuse hash could change depending on the order of dictionary values. This has been fixed to ensure that dictionary values are processed in a consistent order when generating the reuse hash. As a result, the reuse hash will likely change again with 4.8.0.

Wait strategies now default to the Running mode. This mode expects the container to remain running throughout startup. If the container exits unexpectedly, Testcontainers will throw a ContainerNotRunningException that includes the exit code and container logs.

The container startup callback now includes an additional overload that provides the actual container configuration. If you implement IContainerBuilder<TBuilderEntity, TContainerEntity>, you need to add the container configuration (e.g., IContainerConfiguration) as a third generic type constraint to IContainerBuilder.

IContainerBuilder.WithResourceMapping and IContainer.CopyAsync now include two new optional arguments: uid and gid. If you do not need to specify those, use named arguments for the existing parameters: fileMode or ct.

⚠️ Breaking Changes

  • fix: Generate consistent reuse hashes by sorting dictionary keys (#​1554) @​0xced
  • feat: Throw if container not running (#​1550) @​HofmeisterAn
  • feat: Add startup callback overload with configuration type (#​1547) @​HofmeisterAn
  • feat: Support UID/GID when copying files (#​1531) @​HofmeisterAn

🚀 Features

  • feat: Throw if container not running (#​1550) @​HofmeisterAn
  • feat: Add startup callback overload with configuration type (#​1547) @​HofmeisterAn
  • feat: Add Docker build context (#​1536) @​HofmeisterAn
  • feat: Add WithTarget(string) to image builder (#​1534) @​HofmeisterAn
  • feat: Resolve Dockerfile ARGs pulling base images (#​1532) @​HofmeisterAn
  • feat: Support UID/GID when copying files (#​1531) @​HofmeisterAn
  • feat(Keycloak): Add API to import a realm configuration file (#​1526) @​VladislavAntonyuk

🐛 Bug Fixes

  • fix: Generate consistent reuse hashes by sorting dictionary keys (#​1554) @​0xced
  • fix(MongoDb): Use db.runCommand({hello:1}) do detect readiness (#​1548) @​HofmeisterAn
  • fix(Papercut): Pin version 7.0 and set new HTTP and SMTP ports (#​1549) @​HofmeisterAn
  • fix(Pulsar): Wait for default namespace (#​1539) @​HofmeisterAn
  • fix(Keycloak): Wait until user created (#​1535) @​HofmeisterAn

📖 Documentation

  • docs: Remove obsolete UntilOperationIsSucceeded wait strategy example (#​1551) @​ascott18
  • docs: Fix link to Ryuk in IContainerBuilder.WithAutoRemove code comment (#​1546) @​hojmark

🧹 Housekeeping

  • chore: Don't create a static field in a generic class (#​1555) @​HofmeisterAn
  • chore: Add test certificates to common project (#​1545) @​HofmeisterAn
  • chore: Update CI workflow to ubuntu-24.04 (#​1544) @​HofmeisterAn
  • chore: Collect test projects at CI runtime (#​1543) @​HofmeisterAn
  • chore: Fix grammar (#​1542) @​Smoothengineer
  • chore: Bump Ryuk (#​1537) @​HofmeisterAn
    ... (truncated)

4.7.0

What's Changed

This release doesn't introduce breaking changes to the public API, but it isn't binary compatible due to necessary internal changes. Make sure to update all related packages (Testcontainers modules) to the same version. See more details here. Thanks to all contributors 👏.

⚠️ Breaking Changes

  • feat(Kafka): Add KRaft support (#​1353) @​SukharevAndrey
  • feat: Add ability to override enumerable builder values (#​1506) @​HofmeisterAn

🚀 Features

  • feat: Add wait strategy to check external (TCP) port availability (#​1495) @​WhiteTomX
  • feat(Kafka): Add KRaft support (#​1353) @​SukharevAndrey
  • feat: Relax Base64 auth provider and ignore path segments in Docker registry URLs (#​1516) @​HofmeisterAn
  • feat: Add ability to override enumerable builder values (#​1506) @​HofmeisterAn
  • feat(Elasticsearch): Return HTTP connection string if security is disabled (#​1494) @​HofmeisterAn
  • fix: Add tooling to inherit XML docs (#​1493) @​HofmeisterAn
  • feat: Resolve .slnx (solution) file in common directory paths (#​1492) @​alexander-jesner-AP
  • feat: Support getting all mapped ports (#​1485) @​HofmeisterAn
  • feat: Add named pipe connection timeout custom configuration (#​1480) @​HofmeisterAn

🐛 Bug Fixes

  • feat(Kafka): Add KRaft support (#​1353) @​SukharevAndrey
  • fix: Send valid HTTP test responses (#​1505) @​HofmeisterAn
  • fix: Use null-conditional operator to access FinishedAt (#​1499) @​HofmeisterAn
  • fix: Remove timeout that kills PID 1 when stopping a container (#​1481) @​HofmeisterAn
  • fix(Pulsar): Wait until the consumer becomes connected (#​1467) @​HofmeisterAn
  • fix(ServiceBus): Remove container lifecycle overrides (#​1465) @​HofmeisterAn

📖 Documentation

  • docs: Explain how to substitute the Docker Hub registry (#​1503) @​HofmeisterAn
  • docs: Extend example copying files to a container (#​1487) @​cimnine

🧹 Housekeeping

  • chore: Change Kafka vendor configuration from class to interface (#​1519) @​HofmeisterAn
  • chore: Bump Docker.DotNet version to 3.128.5 (#​1511) @​HofmeisterAn
  • chore: Build a single project instead of the whole solution (#​1502) @​HofmeisterAn

4.6.0

What's Changed

This is a patch release (but the minor version was already set). It fixes a bug in our Docker.DotNet fork where a wrong HTTP Connection header break Podman and possibly other environments. Shipped a quick fix. Thanks again to @​ahaeber and @​victor-lambret for the help.

🐛 Bug Fixes

  • chore: Bump Docker.DotNet version to 3.128.3 (#​1462) @​HofmeisterAn

🧹 Housekeeping

  • chore: Bump Docker.DotNet version to 3.128.3 (#​1462) @​HofmeisterAn

4.5.0

What's Changed

Big thanks to everyone who contributed to this release 🤜🤛.

🚀 Features

  • feat: Add OpenSearch module (#​1395) @​digital88
  • feat: Add Typesense module (#​1446) @​brainded
  • feat: Add Task<ExecResult> extension method ThrowOnFailure (#​1448) @​HofmeisterAn
  • feat: Throw DockerUnavailableException when Docker is not available (#​1308) @​0xced
  • feat: Improve error reporting when loading the Docker configuration file (#​1263) @​0xced
  • feat: Add a wait strategy that waits until the ADO.NET database is available (#​1401) @​0xced
  • feat: Add Ollama module (#​1099) @​frankhaugen
  • feat: Allow canceling container start in xUnit.net v3 fixtures (#​1431) @​TheConstructor
  • fix(EventHubs): Support default consumer group name (#​1432) @​scrocquesel-ml150
  • feat: Add Lowkey Vault module (#​1344) @​Xor-el
  • feat(ServiceBus): Add builder API to upload config file (#​1424) @​chasewallis

🐛 Bug Fixes

  • fix: Set container created, started, stopped time from inspect response (#​1455) @​HofmeisterAn
  • fix(EventHubs): Change predicate that it does not always evaluate to true (#​1433) @​HofmeisterAn
  • fix(EventHubs): Support default consumer group name (#​1432) @​scrocquesel-ml150

📖 Documentation

  • docs(ClickHouse): Add example (#​1421) @​digital88
  • docs: Mention the xUnit.net v3 module (package) (#​1442) @​TheConstructor

🧹 Housekeeping

  • chore: Add ExecResult serializable test data (#​1456) @​HofmeisterAn
  • chore: Remove Sonar findings (#​1450) @​HofmeisterAn
  • chore: Replace Cake .NET Tool with Cake Frosting (#​1437) @​0xced
  • chore: Update test-framework to xUnit.net v3 (#​1441) @​TheConstructor
  • chore: Bump Docker.DotNet version to 3.128.1 (#​1443) @​HofmeisterAn
  • chore: Add xUnit.net v3 tests (#​1430) @​TheConstructor
  • chore: Bump CI .NET SDK and Cake version (#​1434) @​HofmeisterAn

4.4.0

What's Changed

Time for a new release! Big thanks to everyone who helped out with this one. It's got some awesome new features and fixes 😎. Seriously, I can't thank you enough. This wouldn't be possible without your ongoing support 🙏.

🚀 Features

  • feat: Add Socat container implementation (#​1416) @​eddumelendez
  • feat(Keycloak): Support admin bootstrapping for version 26 onwards (#​1400) @​paulomorgado
  • feat: Add Qdrant module (#​1149) @​russcam
  • feat(EventHubs, ServiceBus): Replace wait strategy with HTTP health check (#​1398) @​scrocquesel-ml150
  • fix: Reflect Unix file mode when building tarball from .NET 7 onwards (#​1397) @​kfarnung

🐛 Bug Fixes

  • fix(Pulsar): Set memory limits (#​1420) @​HofmeisterAn
  • fix: Add Docker Hub image name prefix to image builder API WithName(IImage) (#​1413) @​HofmeisterAn
  • fix: Reflect Unix file mode when building tarball from .NET 7 onwards (#​1397) @​kfarnung
  • fix: Retrieve logs since CreatedTime for UntilMessageIsLogged wait strategy (#​1384) @​0xced

📖 Documentation

  • docs: Bump testcontainers/helloworld image in docs to version 1.2.0 (#​1419) @​VishnuJin
  • docs(ActiveMq): Add example (#​1415) @​digital88
  • docs: Add binding redirects doc for .NET framework (#​1414) @​HofmeisterAn
  • docs: Add Bitbucket Pipelines configuration (#​1402) @​boeckie-xenopz
  • docs(EventHubs, ServiceBus): Add example (#​1396) @​HofmeisterAn
  • docs(Cassandra): Add example (#​1382) @​gavindekock

🧹 Housekeeping

  • fix(Pulsar): Set memory limits (#​1420) @​HofmeisterAn
  • chore: Add wrapper module for Google Cloud modules (#​1411) @​digital88
  • chore: Use Docker Compose to host documentation locally (#​1410) @​digital88
  • feat(EventHubs, ServiceBus): Replace wait strategy with HTTP health check (#​1398) @​scrocquesel-ml150

4.3.0

What's Changed

Thank you to all the contributors 🙌.

🚀 Features

  • feat: Add Apache Cassandra module (#​1367) @​gavindekock
  • feat(Oracle): Add support for WithDatabase(string) for Oracle 18 and onwards (#​1321) @​0xced
  • feat: Add EventHubs module (#​1373) @​HofmeisterAn
  • feat: Add Db2 module (#​1237) @​kevin0x90

🐛 Bug Fixes

  • fix(MongoDb): Evaluate replica set status in wait strategy (#​1369) @​HofmeisterAn

🧹 Housekeeping

  • chore: Update examples implementations (#​1379) @​HofmeisterAn
  • chore: Remove SharedContainerInstance and use ContainerFixture instead (#​1378) @​0xced
  • chore: Remove code smells and bump dependencies (#​1374) @​HofmeisterAn
  • chore: Add support for macOS in Testcontainers.Db2.Tests (#​1371) @​0xced
  • chore: Add WithAcceptLicenseAgreement(bool) to container builder (#​1370) @​HofmeisterAn
  • chore: Set PackageLicenseExpression property to MIT (#​1368) @​ax-meyer

4.2.0

What's Changed

Thanks for all the contributions. Great first release of the year ❤️.

🚀 Features

  • feat: Add SFTP module (#​1362) @​wim07101993
  • feat(ServiceBus): Add support to use existing MSSQL container instances (#​1335) @​lgcmotta
  • feat: Add Weaviate module (#​1356) @​roji
  • fix(Pulsar): Support Apache Pulsar 4 (#​1355) @​entvex
  • feat: Add network support to the Kafka container (#​1316) @​SebastienDegodez
  • feat: Add pausing and unpausing container (#​1315) @​stefannikolei
  • feat: Add NAT driver network configuration (#​1328) @​HofmeisterAn

🐛 Bug Fixes

  • fix(Redpanda): Add missing command-line arguments to the startup script (smp, memory) (#​1364) @​RobotechUSA
  • chore: Remove binding of ports to IPv4 only (#​1363) @​daviian
  • feat: Add network support to the Kafka container (#​1316) @​SebastienDegodez
  • fix: Load PKCS#​12 correctly for mTLS on Windows in .NET 9 (#​1320) @​jvmdc

🧹 Housekeeping

  • fix(Redpanda): Add missing command-line arguments to the startup script (smp, memory) (#​1364) @​RobotechUSA
  • chore: Remove binding of ports to IPv4 only (#​1363) @​daviian
  • chore: Algin module configurations (#​1357) @​HofmeisterAn
  • fix: Make Testcontainers.XunitV3 publishable (#​1317) @​0xced

4.1.0

What's Changed

This is likely the last release for this year, unless urgent fixes become necessary.

I want to mention a change introduced in the last major release, where we refactored the IImage interface. As part of this update, we removed obsolete properties and overloaded constructors. If you previously used an overload other than DockerImage(string), please ensure you pass the correct values to the constructor.

Additionally, we replaced Docker.DotNet with our own fork to keep the Docker Engine API up to date. Our goal is to improve reliability and performance while offering a more developer-friendly and convenient client. If you depend on a Docker client for .NET, we encourage you to contribute to this project ❤️.

On a personal note, I wish everyone a peaceful and lovely holiday season. Take this time to rest, connect with loved ones, and recharge for the new year. Take care.

🚀 Features

  • feat: Add Azure Service Bus module (#​1309) @​HofmeisterAn
  • feat: Set ryuk.container.privileged default value to true (#​1313) @​HofmeisterAn
  • feat: Remove net6.0 and add net9.0 TFM (#​1311) @​HofmeisterAn
  • feat: Introduce a new Testcontainers.Xunit package (#​1165) @​0xced

🐛 Bug Fixes

  • fix: Postpone exception in DbContainerFixture to match the behavior of ContainerFixture (#​1310) @​0xced
  • fix: Do not pre-pull scratch image (#​1304) @​HofmeisterAn
  • fix: Add HTTP wait strategy to prevent race-condition in WaitUntilHttpRequestIsSucceededTest (#​1299) @​HofmeisterAn
  • fix: Add HTTP wait strategy to prevent race-condition in TarOutputMemoryStreamTest (#​1297) @​HofmeisterAn
  • fix: Prepend Docker Hub namespace to repository (#​1287) @​HofmeisterAn

📖 Documentation

  • docs: Add low level API access (#​1300) @​HofmeisterAn

🧹 Housekeeping

  • feat: Add Respawn example (#​1307) @​HofmeisterAn
  • chore: Add test to ensure that all test projects are configured for CI (#​1305) @​0xced
  • chore: Run each test project on a separate runner (#​1295) @​HofmeisterAn
  • chore: Replace Docker.DotNet with the Testcontainers organization's build (#​1292) @​HofmeisterAn

4.0.0

What's Changed

Thanks so much for all the effort you put into this release 🙌. We are continuing to align Testcontainers for .NET with other language implementations and working on improving the domain-specific language.

We have updated the IImage interface to follow the domain-specific language with the correct property names. The table below shows the changes. We tried to make the transition as smooth as possible, but be aware that relying on the Repository property might cause issues since it now resolves a different value than before. We have kept the old properties, marked them as obsolete, and provided guidance on how to migrate.

Actual Expected (New)
Registry - myregistryhost:5000
Repository myregistryhost:5000/fedora fedora/httpd
Name httpd -
Tag version1.0 version1.0
Digest - sha256:37a3b014d320...
GetHostname() myregistryhost:5000 myregistryhost:5000

⚠️ Breaking Changes

  • fix: Bump MSSQL image version, remove Azure SQL Edge and Papercut module (#​1265) @​HofmeisterAn
  • feat: Align IImage properties with Docker DSL (#​1256) @​HofmeisterAn

🚀 Features

  • feat: Use exec to replace current shell with the Kafka process (as ENTRYPOINT) (#​1282) @​jasoncouture
  • feat: Add getter for Azurite blob, queue and table endpoint (#​1278) @​pregress
  • feat: Reintroduce Papercut module (#​1268) @​TechLiam
  • feat: Add Neo4j Enterprise Edition support (WithEnterpriseEdition(bool)) (#​1269) @​Sossenbinder
  • feat: Support digest in IImage, DockerImage and in the WithImage(string) implementation (#​1249) @​Kielek
  • feat: Align IImage properties with Docker DSL (#​1256) @​HofmeisterAn
  • feat: Change visibility of AccountName and AccountKey to public, move to AzuriteBuilder (#​1258) @​mikecole
  • feat: Get Docker endpoint from Docker context (#​1235) @​0xced

🐛 Bug Fixes

  • fix: Bump MSSQL image version, remove Azure SQL Edge and Papercut module (#​1265) @​HofmeisterAn
  • fix: Throw exception if Docker resource does not exist instead of silently ignoring it (#​1254) @​0xced

🧹 Housekeeping

  • fix: Do not run reusable resource tests in parallel (#​1267) @​HofmeisterAn

Commits viewable in compare view.

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

---
updated-dependencies:
- dependency-name: Testcontainers.PostgreSql
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Jul 21, 2026
@Powerworks
Powerworks merged commit a44bb64 into dev Jul 21, 2026
2 checks passed
@dependabot
dependabot Bot deleted the dependabot/nuget/code/K9Crush-scaffold/K9Crush/Testcontainers.PostgreSql-4.13.0 branch July 21, 2026 21:40
Powerworks added a commit that referenced this pull request Jul 22, 2026
* Wire up dotnet user-secrets for Api.Host local development

Local Supabase/Postgres secrets no longer need to round-trip through
appsettings.Development.json (which stays permanently as CHANGE_ME
placeholders) - real values live in the per-project user-secrets store
outside the repo instead, merged in automatically by ASP.NET Core in
Development.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add public dog-listing browsing to ShelterAdoption

GetAdoptionListings and GetDogListingDetails let a member discover an
adoptable dog and its full detail across every active shelter, with no
ownership restriction (unlike the shelter's own management views). This
closes TheWouldBeAdopter's journey end-to-end: a member can now browse,
view, and submit an application entirely through discovered ids rather
than needing a pre-known dogListingId.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add the drafts feature and a 4-layer automated testing strategy

The drafts feature (StartDraftApplication/EditApplicationDetails/
ResumeDraftApplication/GetDraftApplications, plus SubmitApplicationHandler's
draft-graduation branch) had only ever been checked manually, so it's
committed alongside real coverage for it instead: TestingApproach.md lays
out domain unit tests, mocked-handler tests (NSubstitute), Testcontainers-
based Postgres integration tests, and NetArchTest architecture fitness
tests - the last of which also mechanizes two real bugs found earlier this
session (the DogProfile serialization gap and the *Handler naming
requirement for Wolverine discovery) so they can't silently reappear.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Rename PawMatch to K9Crush and add the UndoLastSwipe automation slice

The scaffold, docs, and every project/namespace under code/PawMatch-scaffold
move to code/K9Crush-scaffold/K9Crush (K9Crush.* naming throughout, including
the .sln). Alongside the rename, Discovery gets an UndoLastSwipe command that
lets a member reverse their most recent swipe, with handler, domain state,
and unit/integration test coverage.

Also brings in the eventmodelers build-kit tooling (.claude/skills,
build-kit/, build-kit-dotnet/) used to scaffold slices from the event model
board, with runtime/scratch state and credentials gitignored.

* Delete PawMatch-scaffold/PawMatch directory

* Add top-level and build-kit READMEs

Orients a newcomer to the repo layout (the real K9Crush .NET app vs. the
design docs vs. the eventmodelers.ai slice-codegen tooling), and clarifies
that build-kit/ is the untouched Node/Express reference scaffold rather
than anything K9Crush is actually built on - build-kit-dotnet/ is the
adapted, in-use version.

* feat: Mark/Close Stale Application

Adds the ShelterReviewsApplication chapter's time-based automation pair
(staleAfterDays: 15, closesAfterDays: 30), using Wolverine scheduled
messages as the first time-based automation in the codebase (ADR-026).
RequestAdditionalDetailsHandler now schedules the 15-day stale check;
MarkApplicationStaleHandler schedules the 30-day close check in turn.
Both automations re-check current Application status before acting, so a
meanwhile-submitted response or redelivered message is a safe no-op.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: AddDogProfile wizard

Replaces the old single-shot CreateDogProfile (which published a dog to
Discovery immediately on creation, with no photo requirement) with the
emlang yaml's actual four-step wizard: Start -> Add Details -> Add Photo
-> Publish. DogProfile gains a Draft/Published status; DogProfileCreatedV1
now fires on Publish instead of on creation, and publishing without a
photo is blocked ("Publish Blocked: Photo Required"), matching the yaml's
explicit rule that CreateDogProfileHandler skipped entirely.

Adds a new K9Crush.Modules.Profiles.Tests project (Layer 2, didn't exist
yet) and a Profiles Testcontainers fixture in K9Crush.IntegrationTests
(Layer 3, for StartDogProfileHandler's maxDogProfiles=10 cap).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: TheWindowShopper (Discovery guest browsing)

Builds the Guest-facing browsing/gating flow: Preview Nearby Dogs (extends
GetDiscoveryFeed with name/matchType/distanceMiles), Block Match Attempt
(anonymous gate a Guest hits trying to act on a dog before signing up),
and Flag Dog Of Interest / Claim Saved Match (two thin entry points over
the same DogOfInterest bookmark mechanism, guarded on the dog still
existing).

No server-side guest-session tracking: the client remembers a dogId from
before signup and passes it back to Claim Saved Match once the member's
profile is confirmed, rather than this codebase inventing an anonymous
identity concept it doesn't otherwise have.

matchType is "dog_to_dog" only for now - "shelter_dog" needs
ShelterAdoption to publish an integration event when a listing is added,
which it doesn't yet; flagged in GetDiscoveryFeed's doc comment rather
than silently left out.

DogProfileCreatedV1 gains a Name field (needed for the preview view),
threaded through PublishDogProfileHandler and DogProfileCreatedProjector.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: stand up the Notifications module

First increment (ADR-027): module skeleton, ManagingNotificationPreferences
chapter (View/Update, lazy-created default-all-enabled NotificationPreference
per owner), and NotifyOnMatch - the one automation ready to build today,
since MatchCreatedV1 is already published by Discovery's
DetectMutualMatchHandler. Deferred: the 4 ShelterAdoption automations
(need ShelterAdoption to start publishing events it doesn't yet) and
NotifyOnMessage/NotifyDogOwner/NotifyWaitlistOfOpening (need Chat/
TheUrgentSearch/Scheduling modules that don't exist).

Real SMTP send via MailKit, pointed at the smtp4dev dev container that
was already provisioned in docker-compose.yml but nothing talked to.
Production email provider (SendGrid/Postmark/SES) stays an open decision -
ISmtpNotificationSender only knows how to speak SMTP, not a specific
provider's API. Verified live: ran the same MailKitSmtpNotificationSender
code path against a real smtp4dev container and confirmed the message
landed via its API.

Two contract extensions needed along the way: MatchCreatedV1 gained
OwnerAId/OwnerBId (DetectMutualMatchHandler now resolves them from its
own DiscoveryFeedItem - the event's own doc comment already said
"alerts both owners" but never carried one), and a new OwnerContact
read model/projector consumes Identity's OwnerRegisteredV1 to get an
actual email address to send to (Notifications can only see other
modules' Contracts, never their Domain).

NotificationType adds a fifth value, Matches, beyond the four the emlang
yaml's ManagingNotificationPreferences chapter names - the yaml is silent
on match notifications but HLD/blueprint both name NotifyOnMatch as the
headline example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: Send Rejection Reason / Send Approval Notification

Unblocks two of the four ShelterAdoption automations deferred pending a
Notifications module. RejectApplicationHandler/ApproveApplicationHandler
now cascade ApplicationRejectedV1/ApplicationApprovedV1 (same tuple-return
pattern already used by CreateShelterAccountHandler), consumed by two new
Notifications automations via NotificationType.ApplicationStatus - one of
the four categories the emlang yaml's own preferences chapter actually
names, no invented category needed this time.

Extracted NotificationDispatcher (check preference, check contact,
send-or-suppress, log) out of NotifyOnMatchHandler once a third call site
needed the identical shape.

Still deferred: CancelApplicationsForRemovedListing + NotifyApplicantsOfCancellation
and NotifyApplicantOfListingChange (ShelterManagingListings) - these chain
within ShelterAdoption itself before reaching Notifications, which needs a
same-module cascade pattern this codebase doesn't have a precedent for yet
(everything published so far goes straight to a different module).

Fixed a real non-determinism bug surfaced while testing this: the new
DetectMutualMatchIntegrationTests test asserted DogAId/OwnerAId by
position, but DetectMutualMatchState assigns Dog A/B by sorting the pair,
not by which side of the test's setup was called "dogA" - fixed to an
order-independent comparison.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: bring GETTING_STARTED.md up to date

Rewritten against current reality instead of the original scaffold-era
draft: the solution actually builds/runs/tests now (~160 passing tests),
so the "never been restored, built, or run" framing and the "expect
floating-version restore failures" section are gone. The smoke test
posted to a single-shot CreateDogProfile endpoint that no longer exists -
replaced with a working walkthrough of the actual 4-step wizard (Start ->
Add Details -> Add Photo -> Publish) plus a full swipe-to-match-to-email
path using smtp4dev.

Added the two pieces this doc was missing that block anyone from actually
getting through a demo: how to configure Supabase's Database Webhooks so
Identity ever learns about a signup, and how to bootstrap the first Admin
account (there's no API for it - OwnerAccount.PromoteToShelter()'s doc
comment already pointed here for that instruction, which didn't exist
until now).

Section 6 (deliberately-not-done) and Section 7 (likely-to-break) rewritten
to drop resolved items (ArchitectureTests, role lookup, Notifications
plumbing, most of the "unverified API surface" risk - all real now) and
state the gaps that are still actually true (no UI, no Admin bootstrap
API, no local Supabase mock).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: bootstrap the first Admin via API

Closes the gap flagged in OwnerAccount.PromoteToShelter()'s doc comment
and worked around with a manual Postgres edit in GETTING_STARTED.md:
POST /api/v1/identity/me/bootstrap-admin self-promotes the calling
VerifiedOwner to Admin, but only while zero Admins exist anywhere -
once the first one is created, this permanently 409s for everyone,
including whoever just bootstrapped. Always "me" from the JWT, never an
arbitrary target, so a malicious first-mover can't promote someone else's
account during the bootstrap window.

GETTING_STARTED.md's Admin section now points at this endpoint instead of
a raw jsonb_set against Marten's internal table shape.

Surfaced a real test-isolation bug while writing this: unlike every other
handler's Layer 3 tests (which scope to random per-test ids that never
collide), this handler's "does any Admin exist" check is genuinely global
state - four tests sharing one Postgres container (the usual pattern)
meant whichever ran first permanently poisoned the others' "no Admin yet"
precondition. Fixed by giving BootstrapAdminIntegrationTests its own
fresh container per test method instead of the usual shared collection
fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: Notify Applicants Of Cancellation / Listing Change

Closes out the last two ShelterAdoption automations deferred pending
Notifications: the listing-removal cascade (Cancel Applications For
Removed Listing -> Notify Applicants Of Cancellation) and the
significant-edit notification (Notify Applicant Of Listing Change).

Introduces ADR-028: this is the first same-module command cascade in the
codebase (ShelterAdoption reacting to its own published event, not a
different module). RemoveDogListingHandler/EditDogListingHandler cascade
DogListingRemovedV1/DogListingSignificantlyEditedV1; ShelterAdoption now
sets IntegrationEventQueueName for the first time (previously null - it
had only ever published) to receive its own events back through the same
shared exchange every cross-module event already goes through - there's
no separate "local-only" pub/sub mechanism in this codebase. Verified
safe by reading Wolverine's actual RabbitMQ transport source before
building rather than assuming: the shared exchange is fanout by default
(every bound queue gets every message) and a message type with no local
handler is a graceful no-op (NoHandlerContinuation acks it), not an
error - so ShelterAdoption's queue quietly absorbing every other module's
irrelevant events is the same behavior every other module's queue
already relies on.

CancelApplicationsForRemovedListingHandler and
NotifyApplicantsOfListingChangeHandler (both ShelterAdoption automations)
react to those same-module events, cascading ApplicationCancelledV1/
ApplicationListingChangedV1 per affected applicant to two new
Notifications automations. Application gains CancelDogNoLongerAvailable()
- the open-application counterpart to the existing Draft-only
CloseDraftDogNoLongerAvailable(), reusing the same status.

EditDogListingRequest gains a SignificantChange field (the yaml's own
prop name) - caller-supplied, not auto-detected from a field diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: ShelterConfiguresNotificationTemplates

Notification template CRUD-with-locking for Shelter Staff: View/Edit/Save,
with "Template Edit Blocked" when someone else already has it locked. The
last emlang chapter that fit inside an existing module without standing
up a new one.

The yaml is sparse here - no actual content field on the template (added
Subject/Body, since a template with no content wouldn't do anything) and
no create/seed step at all, so ViewNotificationTemplatesHandler just
returns whatever's actually been created (possibly nothing - there's no
create endpoint, deliberately not invented). Edit submits new content and
acquires the lock in one step (the yaml shows no separate "start
editing" action); Save releases it. appliesToAlreadyQueuedNotifications
is accepted and echoed back but has no functional effect - nothing in
this codebase queues notifications for later delivery yet.

Deliberately does NOT wire these templates into the actual send path -
NotifyOnMatchHandler and the other Notify* automations still use their
own inline subject/body. That would be a separate, larger change across
every existing automation, not specified by this chapter.

Fixed the same test-isolation pattern as BootstrapAdminIntegrationTests:
ViewNotificationTemplatesHandler's query is unscoped (every template, no
per-test filter), so its "empty list" test can't safely share a
container with a test that seeds templates - given its own per-test
Postgres container instead of the usual shared collection fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: stand up the Chat module

First increment: the docs' own MVP slice table (event-sourced, matching
Discovery's pattern) - CreateConversationOnMatch (consumes MatchCreatedV1,
already live), SendMessage, GetConversationHistory, MarkAsRead - plus
GetMyConversations, a necessary addition beyond the 4 named slices since
nothing else lets a caller discover a conversationId after the
match-triggered automation creates one asynchronously.

Deliberately scoped narrow per this session's discussion: no SignalR
ChatHub yet (REST-only for now - persist + respond, no live push;
real-time is a genuinely separate follow-up), no manual message-request/
accept-gated conversations or group chat (the emlang yaml's fuller
MessagingDirectGroup chapter), and no Report Message -> Content Flagged
(needs a Moderation module that doesn't exist at all yet - zero code
anywhere).

Conversation streams are keyed directly by Discovery's MatchId rather
than a second pair-derived hash, since MatchCreatedV1 already provides a
stable unique key for the pair - avoids redundant derivation Discovery's
own MatchStream.IdFor needed for a different reason (no other shared key
existed there before a match forms).

Surfaced two real test-tooling issues while building this:
- FluentAssertions can't evaluate an `is` pattern inside a predicate it
  treats as an expression tree - fixed by filtering with OfType<T>() first.
- Marten's JasperFx.Events.SourceGenerator.AggregateEvolverGenerator needs
  AggregateStreamAsync<T> state types to be at least internally visible,
  not private nested classes.
Also hit the same cross-test-class isolation problem as
BootstrapAdminIntegrationTests/ViewNotificationTemplatesIntegrationTests -
sharing one container across Chat's test classes broke unrelated
LoadAsync calls after another class used AggregateStreamAsync with its
own state type. Root cause not fully isolated; fixed with the same
proven per-test-class dedicated container pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: build the AccountProfileSettings chapter

View/edit profile settings, request+confirm account deletion with a
recoverable 30-day grace period (ADR-026 scheduled-message pattern),
account recovery on next login, and feedback submission (Identity
module). Cross-module: ShelterAdoption reacts to the new
AccountDeletionRequestedV1 integration event to silently withdraw the
deleting owner's open applications, reusing Application's existing
Withdraw() method.

Deliberately deferred: the yaml's "Open Items Flagged" count display
and the shelter-side "Flag Active Listings" branch - both need either
a forbidden cross-module query or extra machinery nothing yet acts on.

New K9Crush.Modules.Identity.Tests project for Layer 1/2 coverage;
Layer 3 coverage for the cross-module withdrawal automation added to
K9Crush.IntegrationTests.

* feat: stand up the Admin module

Covers the emlang yaml's HandlingGeneralFeedbackSupport chapter -
Feedback Inbox/Detail views, Respond To Feedback, Resolve Feedback
(guarded to only be valid once responded). Fed by Identity's new
FeedbackSubmittedV1 cross-module event (SubmitFeedbackHandler now
cascades it) rather than a direct query, keeping module isolation
intact - Admin builds its own FeedbackInboxItem read model instead of
reading Identity's Feedback document.

Deliberately deferred: ModeratingFlaggedContentUserReports (no
flagged-content producers exist in any module yet) and
AdminDashboardLandingView (its "Platform Health Snapshot" link-out has
no infra-monitoring model in this codebase).

New K9Crush.Modules.Admin.Tests project (Layers 1-2) plus Layer 3
coverage in K9Crush.IntegrationTests/Admin; architecture-fitness test
assembly lists updated for the new module.

* feat: stand up the Media module

Covers the emlang yaml's UploadShareRemovePhotosAndVideos chapter -
Upload/Share/Remove Media plus Report Media -> Content Flagged. This
is the first real entity backing what was previously just an opaque
MediaAssetId Guid that Profiles.Api's AddDogProfilePhotoHandler already
accepted (built before this module existed): a caller now calls
UploadMedia first and passes the resulting id into AddDogProfilePhoto.

"Invalid file" rejection is enforced via IValidatableObject (extension
vs declared MediaType) rather than a separate command, consistent with
this codebase's validation convention. Report Media cascades a new
MediaContentFlaggedV1 integration event - nothing consumes it yet since
no Moderation module exists, same "publish now, consumer later"
pattern as Identity's FeedbackSubmittedV1 before Admin existed.

New K9Crush.Modules.Media.Tests project (Layers 1-2 only - no handler
here uses session.Query<T>(), so no Layer 3 integration tests are
needed this time); architecture-fitness test assembly lists updated.

* ci: add build/test/coverage workflow, CodeQL scanning, and Dependabot

- .github/workflows/ci.yml: restores, builds, and runs the full test
  suite (dotnet test collects coverage via coverlet.collector), then
  generates an HTML + GitHub-flavored markdown coverage report via
  ReportGenerator and publishes it to the workflow's job summary plus
  a downloadable artifact. Triggers on PRs and pushes to dev/main.
- .github/workflows/codeql.yml: CodeQL SAST scanning for C#, same
  triggers plus a weekly schedule.
- .github/dependabot.yml: weekly NuGet + GitHub Actions dependency
  update PRs.
- tests/Directory.Build.props: adds coverlet.collector to every test
  project in one place rather than nine separate csproj edits -
  explicitly re-imports the root Directory.Build.props since the SDK's
  auto-discovery only picks up the closest one, not every level.

Verified locally end-to-end (build, full test run with coverage
collection, report generation) before pushing - all 241 tests pass.

* ci: bump codeql-action to v4 ahead of v3's Dec 2026 deprecation

* build(deps): bump actions/upload-artifact from 4 to 7 (#1)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* build(deps): bump actions/checkout from 4 to 7 (#2)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* build(deps): bump actions/setup-dotnet from 4 to 6 (#3)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](actions/setup-dotnet@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* Bump Marten and Marten.AspNetCore (#7)

Bumps Marten from 9.15.0 to 9.17.1
Bumps Marten.AspNetCore from 9.15.0 to 9.17.1

---
updated-dependencies:
- dependency-name: Marten
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: Marten.AspNetCore
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* test: fill remaining coverage gaps in Identity/Profiles/Discovery/ShelterAdoption

These four modules predate the 4-layer testing strategy, so their
earliest slices never got automated coverage. Adds tests for 23
previously-untested handlers:

- Identity (4): the Supabase webhook automations (Provision/Verify),
  PromoteOwnerToShelterOnAccountCreated, and OwnerAccountView. First
  tests in the codebase to mock HttpRequest/IConfiguration (NSubstitute
  proxies HttpRequest fine despite it being an abstract class, not an
  interface).
- Profiles (1): GetDogProfileHandler.
- Discovery (1): SwipeOnDogHandler - unlike UndoLastSwipeHandler, its
  success path never touches AggregateStreamAsync, so it's fully
  Layer-2-testable by mocking session.Events (IEventStoreOperations)
  directly rather than needing Testcontainers.
- ShelterAdoption (17): the full shelter onboarding chain (Request/
  Verify/Create/FlagVerificationIssues/Resubmit/Reject/Approve), the
  remaining application actions (AddDogListing/Withdraw/Review/
  SubmitAdditionalDetails), and 6 read-models - split across Layer 2
  (GetApplicationStatus/GetDogListingDetails, LoadAsync-only) and
  Layer 3 (GetShelterDogListings/GetPendingApplicationsQueue/
  GetDraftApplications/GetAdoptionListings, all using Query<T>()).
  GetAdoptionListingsHandler queries with no filter at all (the public
  marketplace browse), so its test gets its own dedicated container -
  same fix as the recurring shared-container isolation gotcha.

All 340 tests pass (up from 264), verified with two clean full runs of
K9Crush.IntegrationTests to rule out cross-test pollution in the newly
shared-container ShelterAdoption tests.

* feat: stand up the Moderation module

Covers the emlang yaml's ModeratingFlaggedContentUserReports chapter -
Moderation Queue/Flagged Content Detail views, Dismiss Flag, Remove
Content, and the Warn/Suspend/Ban User escalation ladder (each gated on
the yaml's own GWT preconditions: suspend requires a prior warning, ban
requires both a prior warning and suspension).

Fed by Media's MediaContentFlaggedV1, which now also carries
ContentOwnerId (the uploader, who Warn/Suspend/Ban target) alongside
the original ReporterOwnerId - the original shape only supported
building a queue, not actually acting against anyone. "Remove Content"
cascades a new ContentRemovalRequestedV1 event back to Media (Moderation
doesn't own the underlying content, so it can't delete it directly) -
Media's new RemoveMediaOnContentRemovalRequestedHandler reacts to it.

Deliberately does not enforce Suspend/Ban anywhere - UserModerationRecord
records the decision only. Actually blocking a suspended/banned owner
would mean every module's authorization checks consulting this record,
a cross-cutting change far bigger than this one chapter - a real,
separately-scoped follow-up.

New K9Crush.Modules.Moderation.Tests project (Layers 1-2) plus Layer 3
coverage for GetModerationQueueHandler (its "every open flag" query has
no per-test scoping, so it gets its own dedicated container, same as
GetFeedbackInboxIntegrationTests/GetAdoptionListingsIntegrationTests);
architecture-fitness test assembly lists updated for the new module.
Verified with two clean runs of K9Crush.IntegrationTests.

* feat: stand up the Places module

Covers the emlang yaml's LeaveAReviewRestaurantOrDogPark chapter -
Write/Publish/Edit/Remove Review, Respond To Review (gated by Place
ownership), and Report Review -> Content Flagged (Places' second real
Moderation producer, after Media).

Includes a disclosed gap-fill: CreatePlaceListing. The yaml's own
ClaimABusinessListing chapter is entirely about claiming an "existing
listing" - it never shows how one first comes into existence (implying
listings are meant to be seeded/imported externally). Without a create
command there'd be nothing for reviews to attach to, so this adds one
directly, deliberately NOT wired through ClaimABusinessListing's
request/verify/admin-override/ownership-transfer workflow - that's a
separate, larger, not-yet-built feature. Same for "Respond to Review"'s
ownership gate: checks Place.OwnerId directly rather than a verified
business-owner role, since that verification workflow doesn't exist.

Moderation's ContentType enum gains a Review member (appended, not
inserted - see FlaggedContent's own doc comment on why) plus a new
ReviewContentFlaggedProjectorHandler, confirming the "one moderation
queue, many distinctly-named per-module producers" design actually
generalizes to a second producer.

Deliberately deferred: ManagingSavedDogsSpots (entangled with
dog-to-dog matching/video-chat concepts that don't exist anywhere in
this codebase).

New K9Crush.Modules.Places.Tests project (Layers 1-2 only - no handler
uses session.Query<T>(), same as Media); architecture-fitness test
assembly lists updated for the new module. All 486 tests pass.

* docs: add v2 emlang spec reverse-engineered from the real implementation

Spec/K9CRUSH.emlang.v2.yaml captures the actual event model of the 10
built modules (Identity, Profiles, Discovery, ShelterAdoption,
Notifications, Chat, Admin, Media, Moderation, Places) as they exist in
code today, for re-import onto an eventmodelers board:

- 15 chapters rewritten to match real command/event names, props, and
  GWT tests, with inline DEVIATION comments explaining every place the
  build diverged from the original board (consolidated read-models,
  corrected actor swimlanes, deferred sub-flows).
- 3 new chapters with no v1 equivalent, covering real slices the
  original 35-chapter board never modeled: Discovery's actual
  swipe/match mechanic, Chat's real match-triggered messaging design,
  and Identity's Supabase-webhook + admin-bootstrap plumbing.
- 20 not-yet-built chapters carried over byte-identical to v1 (verified
  programmatically), so the remaining plan isn't lost.

v1 (K9CRUSH.emlang.yaml) is untouched and remains the record of what
was originally envisioned.

* Bump Microsoft.NET.Test.Sdk from 17.11.1 to 18.8.1 (#12)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.8.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Microsoft.AspNetCore.Authentication.JwtBearer from 10.0.9 to 10.0.10 (#9)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Authentication.JwtBearer
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0 (#16)

---
updated-dependencies:
- dependency-name: Testcontainers.PostgreSql
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump NSubstitute from 5.3.0 to 6.0.0 (#13)

---
updated-dependencies:
- dependency-name: NSubstitute
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Serilog.AspNetCore from 8.0.3 to 10.0.0 (#14)

---
updated-dependencies:
- dependency-name: Serilog.AspNetCore
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Microsoft.Extensions.Caching.StackExchangeRedis from 10.0.9 to 10.0.10 (#11)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Caching.StackExchangeRedis
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Swashbuckle.AspNetCore from 6.9.0 to 10.2.3 (#15)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-version: 10.2.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Powerworks added a commit that referenced this pull request Jul 22, 2026
* Wire up dotnet user-secrets for Api.Host local development

Local Supabase/Postgres secrets no longer need to round-trip through
appsettings.Development.json (which stays permanently as CHANGE_ME
placeholders) - real values live in the per-project user-secrets store
outside the repo instead, merged in automatically by ASP.NET Core in
Development.



* Add public dog-listing browsing to ShelterAdoption

GetAdoptionListings and GetDogListingDetails let a member discover an
adoptable dog and its full detail across every active shelter, with no
ownership restriction (unlike the shelter's own management views). This
closes TheWouldBeAdopter's journey end-to-end: a member can now browse,
view, and submit an application entirely through discovered ids rather
than needing a pre-known dogListingId.



* Add the drafts feature and a 4-layer automated testing strategy

The drafts feature (StartDraftApplication/EditApplicationDetails/
ResumeDraftApplication/GetDraftApplications, plus SubmitApplicationHandler's
draft-graduation branch) had only ever been checked manually, so it's
committed alongside real coverage for it instead: TestingApproach.md lays
out domain unit tests, mocked-handler tests (NSubstitute), Testcontainers-
based Postgres integration tests, and NetArchTest architecture fitness
tests - the last of which also mechanizes two real bugs found earlier this
session (the DogProfile serialization gap and the *Handler naming
requirement for Wolverine discovery) so they can't silently reappear.



* Rename PawMatch to K9Crush and add the UndoLastSwipe automation slice

The scaffold, docs, and every project/namespace under code/PawMatch-scaffold
move to code/K9Crush-scaffold/K9Crush (K9Crush.* naming throughout, including
the .sln). Alongside the rename, Discovery gets an UndoLastSwipe command that
lets a member reverse their most recent swipe, with handler, domain state,
and unit/integration test coverage.

Also brings in the eventmodelers build-kit tooling (.claude/skills,
build-kit/, build-kit-dotnet/) used to scaffold slices from the event model
board, with runtime/scratch state and credentials gitignored.

* Delete PawMatch-scaffold/PawMatch directory

* Add top-level and build-kit READMEs

Orients a newcomer to the repo layout (the real K9Crush .NET app vs. the
design docs vs. the eventmodelers.ai slice-codegen tooling), and clarifies
that build-kit/ is the untouched Node/Express reference scaffold rather
than anything K9Crush is actually built on - build-kit-dotnet/ is the
adapted, in-use version.

* feat: Mark/Close Stale Application

Adds the ShelterReviewsApplication chapter's time-based automation pair
(staleAfterDays: 15, closesAfterDays: 30), using Wolverine scheduled
messages as the first time-based automation in the codebase (ADR-026).
RequestAdditionalDetailsHandler now schedules the 15-day stale check;
MarkApplicationStaleHandler schedules the 30-day close check in turn.
Both automations re-check current Application status before acting, so a
meanwhile-submitted response or redelivered message is a safe no-op.



* feat: AddDogProfile wizard

Replaces the old single-shot CreateDogProfile (which published a dog to
Discovery immediately on creation, with no photo requirement) with the
emlang yaml's actual four-step wizard: Start -> Add Details -> Add Photo
-> Publish. DogProfile gains a Draft/Published status; DogProfileCreatedV1
now fires on Publish instead of on creation, and publishing without a
photo is blocked ("Publish Blocked: Photo Required"), matching the yaml's
explicit rule that CreateDogProfileHandler skipped entirely.

Adds a new K9Crush.Modules.Profiles.Tests project (Layer 2, didn't exist
yet) and a Profiles Testcontainers fixture in K9Crush.IntegrationTests
(Layer 3, for StartDogProfileHandler's maxDogProfiles=10 cap).



* feat: TheWindowShopper (Discovery guest browsing)

Builds the Guest-facing browsing/gating flow: Preview Nearby Dogs (extends
GetDiscoveryFeed with name/matchType/distanceMiles), Block Match Attempt
(anonymous gate a Guest hits trying to act on a dog before signing up),
and Flag Dog Of Interest / Claim Saved Match (two thin entry points over
the same DogOfInterest bookmark mechanism, guarded on the dog still
existing).

No server-side guest-session tracking: the client remembers a dogId from
before signup and passes it back to Claim Saved Match once the member's
profile is confirmed, rather than this codebase inventing an anonymous
identity concept it doesn't otherwise have.

matchType is "dog_to_dog" only for now - "shelter_dog" needs
ShelterAdoption to publish an integration event when a listing is added,
which it doesn't yet; flagged in GetDiscoveryFeed's doc comment rather
than silently left out.

DogProfileCreatedV1 gains a Name field (needed for the preview view),
threaded through PublishDogProfileHandler and DogProfileCreatedProjector.



* feat: stand up the Notifications module

First increment (ADR-027): module skeleton, ManagingNotificationPreferences
chapter (View/Update, lazy-created default-all-enabled NotificationPreference
per owner), and NotifyOnMatch - the one automation ready to build today,
since MatchCreatedV1 is already published by Discovery's
DetectMutualMatchHandler. Deferred: the 4 ShelterAdoption automations
(need ShelterAdoption to start publishing events it doesn't yet) and
NotifyOnMessage/NotifyDogOwner/NotifyWaitlistOfOpening (need Chat/
TheUrgentSearch/Scheduling modules that don't exist).

Real SMTP send via MailKit, pointed at the smtp4dev dev container that
was already provisioned in docker-compose.yml but nothing talked to.
Production email provider (SendGrid/Postmark/SES) stays an open decision -
ISmtpNotificationSender only knows how to speak SMTP, not a specific
provider's API. Verified live: ran the same MailKitSmtpNotificationSender
code path against a real smtp4dev container and confirmed the message
landed via its API.

Two contract extensions needed along the way: MatchCreatedV1 gained
OwnerAId/OwnerBId (DetectMutualMatchHandler now resolves them from its
own DiscoveryFeedItem - the event's own doc comment already said
"alerts both owners" but never carried one), and a new OwnerContact
read model/projector consumes Identity's OwnerRegisteredV1 to get an
actual email address to send to (Notifications can only see other
modules' Contracts, never their Domain).

NotificationType adds a fifth value, Matches, beyond the four the emlang
yaml's ManagingNotificationPreferences chapter names - the yaml is silent
on match notifications but HLD/blueprint both name NotifyOnMatch as the
headline example.



* feat: Send Rejection Reason / Send Approval Notification

Unblocks two of the four ShelterAdoption automations deferred pending a
Notifications module. RejectApplicationHandler/ApproveApplicationHandler
now cascade ApplicationRejectedV1/ApplicationApprovedV1 (same tuple-return
pattern already used by CreateShelterAccountHandler), consumed by two new
Notifications automations via NotificationType.ApplicationStatus - one of
the four categories the emlang yaml's own preferences chapter actually
names, no invented category needed this time.

Extracted NotificationDispatcher (check preference, check contact,
send-or-suppress, log) out of NotifyOnMatchHandler once a third call site
needed the identical shape.

Still deferred: CancelApplicationsForRemovedListing + NotifyApplicantsOfCancellation
and NotifyApplicantOfListingChange (ShelterManagingListings) - these chain
within ShelterAdoption itself before reaching Notifications, which needs a
same-module cascade pattern this codebase doesn't have a precedent for yet
(everything published so far goes straight to a different module).

Fixed a real non-determinism bug surfaced while testing this: the new
DetectMutualMatchIntegrationTests test asserted DogAId/OwnerAId by
position, but DetectMutualMatchState assigns Dog A/B by sorting the pair,
not by which side of the test's setup was called "dogA" - fixed to an
order-independent comparison.



* docs: bring GETTING_STARTED.md up to date

Rewritten against current reality instead of the original scaffold-era
draft: the solution actually builds/runs/tests now (~160 passing tests),
so the "never been restored, built, or run" framing and the "expect
floating-version restore failures" section are gone. The smoke test
posted to a single-shot CreateDogProfile endpoint that no longer exists -
replaced with a working walkthrough of the actual 4-step wizard (Start ->
Add Details -> Add Photo -> Publish) plus a full swipe-to-match-to-email
path using smtp4dev.

Added the two pieces this doc was missing that block anyone from actually
getting through a demo: how to configure Supabase's Database Webhooks so
Identity ever learns about a signup, and how to bootstrap the first Admin
account (there's no API for it - OwnerAccount.PromoteToShelter()'s doc
comment already pointed here for that instruction, which didn't exist
until now).

Section 6 (deliberately-not-done) and Section 7 (likely-to-break) rewritten
to drop resolved items (ArchitectureTests, role lookup, Notifications
plumbing, most of the "unverified API surface" risk - all real now) and
state the gaps that are still actually true (no UI, no Admin bootstrap
API, no local Supabase mock).



* feat: bootstrap the first Admin via API

Closes the gap flagged in OwnerAccount.PromoteToShelter()'s doc comment
and worked around with a manual Postgres edit in GETTING_STARTED.md:
POST /api/v1/identity/me/bootstrap-admin self-promotes the calling
VerifiedOwner to Admin, but only while zero Admins exist anywhere -
once the first one is created, this permanently 409s for everyone,
including whoever just bootstrapped. Always "me" from the JWT, never an
arbitrary target, so a malicious first-mover can't promote someone else's
account during the bootstrap window.

GETTING_STARTED.md's Admin section now points at this endpoint instead of
a raw jsonb_set against Marten's internal table shape.

Surfaced a real test-isolation bug while writing this: unlike every other
handler's Layer 3 tests (which scope to random per-test ids that never
collide), this handler's "does any Admin exist" check is genuinely global
state - four tests sharing one Postgres container (the usual pattern)
meant whichever ran first permanently poisoned the others' "no Admin yet"
precondition. Fixed by giving BootstrapAdminIntegrationTests its own
fresh container per test method instead of the usual shared collection
fixture.



* feat: Notify Applicants Of Cancellation / Listing Change

Closes out the last two ShelterAdoption automations deferred pending
Notifications: the listing-removal cascade (Cancel Applications For
Removed Listing -> Notify Applicants Of Cancellation) and the
significant-edit notification (Notify Applicant Of Listing Change).

Introduces ADR-028: this is the first same-module command cascade in the
codebase (ShelterAdoption reacting to its own published event, not a
different module). RemoveDogListingHandler/EditDogListingHandler cascade
DogListingRemovedV1/DogListingSignificantlyEditedV1; ShelterAdoption now
sets IntegrationEventQueueName for the first time (previously null - it
had only ever published) to receive its own events back through the same
shared exchange every cross-module event already goes through - there's
no separate "local-only" pub/sub mechanism in this codebase. Verified
safe by reading Wolverine's actual RabbitMQ transport source before
building rather than assuming: the shared exchange is fanout by default
(every bound queue gets every message) and a message type with no local
handler is a graceful no-op (NoHandlerContinuation acks it), not an
error - so ShelterAdoption's queue quietly absorbing every other module's
irrelevant events is the same behavior every other module's queue
already relies on.

CancelApplicationsForRemovedListingHandler and
NotifyApplicantsOfListingChangeHandler (both ShelterAdoption automations)
react to those same-module events, cascading ApplicationCancelledV1/
ApplicationListingChangedV1 per affected applicant to two new
Notifications automations. Application gains CancelDogNoLongerAvailable()
- the open-application counterpart to the existing Draft-only
CloseDraftDogNoLongerAvailable(), reusing the same status.

EditDogListingRequest gains a SignificantChange field (the yaml's own
prop name) - caller-supplied, not auto-detected from a field diff.



* feat: ShelterConfiguresNotificationTemplates

Notification template CRUD-with-locking for Shelter Staff: View/Edit/Save,
with "Template Edit Blocked" when someone else already has it locked. The
last emlang chapter that fit inside an existing module without standing
up a new one.

The yaml is sparse here - no actual content field on the template (added
Subject/Body, since a template with no content wouldn't do anything) and
no create/seed step at all, so ViewNotificationTemplatesHandler just
returns whatever's actually been created (possibly nothing - there's no
create endpoint, deliberately not invented). Edit submits new content and
acquires the lock in one step (the yaml shows no separate "start
editing" action); Save releases it. appliesToAlreadyQueuedNotifications
is accepted and echoed back but has no functional effect - nothing in
this codebase queues notifications for later delivery yet.

Deliberately does NOT wire these templates into the actual send path -
NotifyOnMatchHandler and the other Notify* automations still use their
own inline subject/body. That would be a separate, larger change across
every existing automation, not specified by this chapter.

Fixed the same test-isolation pattern as BootstrapAdminIntegrationTests:
ViewNotificationTemplatesHandler's query is unscoped (every template, no
per-test filter), so its "empty list" test can't safely share a
container with a test that seeds templates - given its own per-test
Postgres container instead of the usual shared collection fixture.



* feat: stand up the Chat module

First increment: the docs' own MVP slice table (event-sourced, matching
Discovery's pattern) - CreateConversationOnMatch (consumes MatchCreatedV1,
already live), SendMessage, GetConversationHistory, MarkAsRead - plus
GetMyConversations, a necessary addition beyond the 4 named slices since
nothing else lets a caller discover a conversationId after the
match-triggered automation creates one asynchronously.

Deliberately scoped narrow per this session's discussion: no SignalR
ChatHub yet (REST-only for now - persist + respond, no live push;
real-time is a genuinely separate follow-up), no manual message-request/
accept-gated conversations or group chat (the emlang yaml's fuller
MessagingDirectGroup chapter), and no Report Message -> Content Flagged
(needs a Moderation module that doesn't exist at all yet - zero code
anywhere).

Conversation streams are keyed directly by Discovery's MatchId rather
than a second pair-derived hash, since MatchCreatedV1 already provides a
stable unique key for the pair - avoids redundant derivation Discovery's
own MatchStream.IdFor needed for a different reason (no other shared key
existed there before a match forms).

Surfaced two real test-tooling issues while building this:
- FluentAssertions can't evaluate an `is` pattern inside a predicate it
  treats as an expression tree - fixed by filtering with OfType<T>() first.
- Marten's JasperFx.Events.SourceGenerator.AggregateEvolverGenerator needs
  AggregateStreamAsync<T> state types to be at least internally visible,
  not private nested classes.
Also hit the same cross-test-class isolation problem as
BootstrapAdminIntegrationTests/ViewNotificationTemplatesIntegrationTests -
sharing one container across Chat's test classes broke unrelated
LoadAsync calls after another class used AggregateStreamAsync with its
own state type. Root cause not fully isolated; fixed with the same
proven per-test-class dedicated container pattern.



* feat: build the AccountProfileSettings chapter

View/edit profile settings, request+confirm account deletion with a
recoverable 30-day grace period (ADR-026 scheduled-message pattern),
account recovery on next login, and feedback submission (Identity
module). Cross-module: ShelterAdoption reacts to the new
AccountDeletionRequestedV1 integration event to silently withdraw the
deleting owner's open applications, reusing Application's existing
Withdraw() method.

Deliberately deferred: the yaml's "Open Items Flagged" count display
and the shelter-side "Flag Active Listings" branch - both need either
a forbidden cross-module query or extra machinery nothing yet acts on.

New K9Crush.Modules.Identity.Tests project for Layer 1/2 coverage;
Layer 3 coverage for the cross-module withdrawal automation added to
K9Crush.IntegrationTests.

* feat: stand up the Admin module

Covers the emlang yaml's HandlingGeneralFeedbackSupport chapter -
Feedback Inbox/Detail views, Respond To Feedback, Resolve Feedback
(guarded to only be valid once responded). Fed by Identity's new
FeedbackSubmittedV1 cross-module event (SubmitFeedbackHandler now
cascades it) rather than a direct query, keeping module isolation
intact - Admin builds its own FeedbackInboxItem read model instead of
reading Identity's Feedback document.

Deliberately deferred: ModeratingFlaggedContentUserReports (no
flagged-content producers exist in any module yet) and
AdminDashboardLandingView (its "Platform Health Snapshot" link-out has
no infra-monitoring model in this codebase).

New K9Crush.Modules.Admin.Tests project (Layers 1-2) plus Layer 3
coverage in K9Crush.IntegrationTests/Admin; architecture-fitness test
assembly lists updated for the new module.

* feat: stand up the Media module

Covers the emlang yaml's UploadShareRemovePhotosAndVideos chapter -
Upload/Share/Remove Media plus Report Media -> Content Flagged. This
is the first real entity backing what was previously just an opaque
MediaAssetId Guid that Profiles.Api's AddDogProfilePhotoHandler already
accepted (built before this module existed): a caller now calls
UploadMedia first and passes the resulting id into AddDogProfilePhoto.

"Invalid file" rejection is enforced via IValidatableObject (extension
vs declared MediaType) rather than a separate command, consistent with
this codebase's validation convention. Report Media cascades a new
MediaContentFlaggedV1 integration event - nothing consumes it yet since
no Moderation module exists, same "publish now, consumer later"
pattern as Identity's FeedbackSubmittedV1 before Admin existed.

New K9Crush.Modules.Media.Tests project (Layers 1-2 only - no handler
here uses session.Query<T>(), so no Layer 3 integration tests are
needed this time); architecture-fitness test assembly lists updated.

* ci: add build/test/coverage workflow, CodeQL scanning, and Dependabot

- .github/workflows/ci.yml: restores, builds, and runs the full test
  suite (dotnet test collects coverage via coverlet.collector), then
  generates an HTML + GitHub-flavored markdown coverage report via
  ReportGenerator and publishes it to the workflow's job summary plus
  a downloadable artifact. Triggers on PRs and pushes to dev/main.
- .github/workflows/codeql.yml: CodeQL SAST scanning for C#, same
  triggers plus a weekly schedule.
- .github/dependabot.yml: weekly NuGet + GitHub Actions dependency
  update PRs.
- tests/Directory.Build.props: adds coverlet.collector to every test
  project in one place rather than nine separate csproj edits -
  explicitly re-imports the root Directory.Build.props since the SDK's
  auto-discovery only picks up the closest one, not every level.

Verified locally end-to-end (build, full test run with coverage
collection, report generation) before pushing - all 241 tests pass.

* ci: bump codeql-action to v4 ahead of v3's Dec 2026 deprecation

* build(deps): bump actions/upload-artifact from 4 to 7 (#1)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...





* build(deps): bump actions/checkout from 4 to 7 (#2)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...





* build(deps): bump actions/setup-dotnet from 4 to 6 (#3)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](actions/setup-dotnet@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...





* Bump Marten and Marten.AspNetCore (#7)

Bumps Marten from 9.15.0 to 9.17.1
Bumps Marten.AspNetCore from 9.15.0 to 9.17.1

---
updated-dependencies:
- dependency-name: Marten
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: Marten.AspNetCore
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...





* test: fill remaining coverage gaps in Identity/Profiles/Discovery/ShelterAdoption

These four modules predate the 4-layer testing strategy, so their
earliest slices never got automated coverage. Adds tests for 23
previously-untested handlers:

- Identity (4): the Supabase webhook automations (Provision/Verify),
  PromoteOwnerToShelterOnAccountCreated, and OwnerAccountView. First
  tests in the codebase to mock HttpRequest/IConfiguration (NSubstitute
  proxies HttpRequest fine despite it being an abstract class, not an
  interface).
- Profiles (1): GetDogProfileHandler.
- Discovery (1): SwipeOnDogHandler - unlike UndoLastSwipeHandler, its
  success path never touches AggregateStreamAsync, so it's fully
  Layer-2-testable by mocking session.Events (IEventStoreOperations)
  directly rather than needing Testcontainers.
- ShelterAdoption (17): the full shelter onboarding chain (Request/
  Verify/Create/FlagVerificationIssues/Resubmit/Reject/Approve), the
  remaining application actions (AddDogListing/Withdraw/Review/
  SubmitAdditionalDetails), and 6 read-models - split across Layer 2
  (GetApplicationStatus/GetDogListingDetails, LoadAsync-only) and
  Layer 3 (GetShelterDogListings/GetPendingApplicationsQueue/
  GetDraftApplications/GetAdoptionListings, all using Query<T>()).
  GetAdoptionListingsHandler queries with no filter at all (the public
  marketplace browse), so its test gets its own dedicated container -
  same fix as the recurring shared-container isolation gotcha.

All 340 tests pass (up from 264), verified with two clean full runs of
K9Crush.IntegrationTests to rule out cross-test pollution in the newly
shared-container ShelterAdoption tests.

* feat: stand up the Moderation module

Covers the emlang yaml's ModeratingFlaggedContentUserReports chapter -
Moderation Queue/Flagged Content Detail views, Dismiss Flag, Remove
Content, and the Warn/Suspend/Ban User escalation ladder (each gated on
the yaml's own GWT preconditions: suspend requires a prior warning, ban
requires both a prior warning and suspension).

Fed by Media's MediaContentFlaggedV1, which now also carries
ContentOwnerId (the uploader, who Warn/Suspend/Ban target) alongside
the original ReporterOwnerId - the original shape only supported
building a queue, not actually acting against anyone. "Remove Content"
cascades a new ContentRemovalRequestedV1 event back to Media (Moderation
doesn't own the underlying content, so it can't delete it directly) -
Media's new RemoveMediaOnContentRemovalRequestedHandler reacts to it.

Deliberately does not enforce Suspend/Ban anywhere - UserModerationRecord
records the decision only. Actually blocking a suspended/banned owner
would mean every module's authorization checks consulting this record,
a cross-cutting change far bigger than this one chapter - a real,
separately-scoped follow-up.

New K9Crush.Modules.Moderation.Tests project (Layers 1-2) plus Layer 3
coverage for GetModerationQueueHandler (its "every open flag" query has
no per-test scoping, so it gets its own dedicated container, same as
GetFeedbackInboxIntegrationTests/GetAdoptionListingsIntegrationTests);
architecture-fitness test assembly lists updated for the new module.
Verified with two clean runs of K9Crush.IntegrationTests.

* feat: stand up the Places module

Covers the emlang yaml's LeaveAReviewRestaurantOrDogPark chapter -
Write/Publish/Edit/Remove Review, Respond To Review (gated by Place
ownership), and Report Review -> Content Flagged (Places' second real
Moderation producer, after Media).

Includes a disclosed gap-fill: CreatePlaceListing. The yaml's own
ClaimABusinessListing chapter is entirely about claiming an "existing
listing" - it never shows how one first comes into existence (implying
listings are meant to be seeded/imported externally). Without a create
command there'd be nothing for reviews to attach to, so this adds one
directly, deliberately NOT wired through ClaimABusinessListing's
request/verify/admin-override/ownership-transfer workflow - that's a
separate, larger, not-yet-built feature. Same for "Respond to Review"'s
ownership gate: checks Place.OwnerId directly rather than a verified
business-owner role, since that verification workflow doesn't exist.

Moderation's ContentType enum gains a Review member (appended, not
inserted - see FlaggedContent's own doc comment on why) plus a new
ReviewContentFlaggedProjectorHandler, confirming the "one moderation
queue, many distinctly-named per-module producers" design actually
generalizes to a second producer.

Deliberately deferred: ManagingSavedDogsSpots (entangled with
dog-to-dog matching/video-chat concepts that don't exist anywhere in
this codebase).

New K9Crush.Modules.Places.Tests project (Layers 1-2 only - no handler
uses session.Query<T>(), same as Media); architecture-fitness test
assembly lists updated for the new module. All 486 tests pass.

* docs: add v2 emlang spec reverse-engineered from the real implementation

Spec/K9CRUSH.emlang.v2.yaml captures the actual event model of the 10
built modules (Identity, Profiles, Discovery, ShelterAdoption,
Notifications, Chat, Admin, Media, Moderation, Places) as they exist in
code today, for re-import onto an eventmodelers board:

- 15 chapters rewritten to match real command/event names, props, and
  GWT tests, with inline DEVIATION comments explaining every place the
  build diverged from the original board (consolidated read-models,
  corrected actor swimlanes, deferred sub-flows).
- 3 new chapters with no v1 equivalent, covering real slices the
  original 35-chapter board never modeled: Discovery's actual
  swipe/match mechanic, Chat's real match-triggered messaging design,
  and Identity's Supabase-webhook + admin-bootstrap plumbing.
- 20 not-yet-built chapters carried over byte-identical to v1 (verified
  programmatically), so the remaining plan isn't lost.

v1 (K9CRUSH.emlang.yaml) is untouched and remains the record of what
was originally envisioned.

* Bump Microsoft.NET.Test.Sdk from 17.11.1 to 18.8.1 (#12)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.8.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump Microsoft.AspNetCore.Authentication.JwtBearer from 10.0.9 to 10.0.10 (#9)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Authentication.JwtBearer
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...




* Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0 (#16)

---
updated-dependencies:
- dependency-name: Testcontainers.PostgreSql
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump NSubstitute from 5.3.0 to 6.0.0 (#13)

---
updated-dependencies:
- dependency-name: NSubstitute
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump Serilog.AspNetCore from 8.0.3 to 10.0.0 (#14)

---
updated-dependencies:
- dependency-name: Serilog.AspNetCore
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump Microsoft.Extensions.Caching.StackExchangeRedis from 10.0.9 to 10.0.10 (#11)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Caching.StackExchangeRedis
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...




* Bump Swashbuckle.AspNetCore from 6.9.0 to 10.2.3 (#15)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-version: 10.2.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...




---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Powerworks added a commit that referenced this pull request Jul 22, 2026
* Wire up dotnet user-secrets for Api.Host local development

Local Supabase/Postgres secrets no longer need to round-trip through
appsettings.Development.json (which stays permanently as CHANGE_ME
placeholders) - real values live in the per-project user-secrets store
outside the repo instead, merged in automatically by ASP.NET Core in
Development.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add public dog-listing browsing to ShelterAdoption

GetAdoptionListings and GetDogListingDetails let a member discover an
adoptable dog and its full detail across every active shelter, with no
ownership restriction (unlike the shelter's own management views). This
closes TheWouldBeAdopter's journey end-to-end: a member can now browse,
view, and submit an application entirely through discovered ids rather
than needing a pre-known dogListingId.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add the drafts feature and a 4-layer automated testing strategy

The drafts feature (StartDraftApplication/EditApplicationDetails/
ResumeDraftApplication/GetDraftApplications, plus SubmitApplicationHandler's
draft-graduation branch) had only ever been checked manually, so it's
committed alongside real coverage for it instead: TestingApproach.md lays
out domain unit tests, mocked-handler tests (NSubstitute), Testcontainers-
based Postgres integration tests, and NetArchTest architecture fitness
tests - the last of which also mechanizes two real bugs found earlier this
session (the DogProfile serialization gap and the *Handler naming
requirement for Wolverine discovery) so they can't silently reappear.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Rename PawMatch to K9Crush and add the UndoLastSwipe automation slice

The scaffold, docs, and every project/namespace under code/PawMatch-scaffold
move to code/K9Crush-scaffold/K9Crush (K9Crush.* naming throughout, including
the .sln). Alongside the rename, Discovery gets an UndoLastSwipe command that
lets a member reverse their most recent swipe, with handler, domain state,
and unit/integration test coverage.

Also brings in the eventmodelers build-kit tooling (.claude/skills,
build-kit/, build-kit-dotnet/) used to scaffold slices from the event model
board, with runtime/scratch state and credentials gitignored.

* Delete PawMatch-scaffold/PawMatch directory

* Add top-level and build-kit READMEs

Orients a newcomer to the repo layout (the real K9Crush .NET app vs. the
design docs vs. the eventmodelers.ai slice-codegen tooling), and clarifies
that build-kit/ is the untouched Node/Express reference scaffold rather
than anything K9Crush is actually built on - build-kit-dotnet/ is the
adapted, in-use version.

* feat: Mark/Close Stale Application

Adds the ShelterReviewsApplication chapter's time-based automation pair
(staleAfterDays: 15, closesAfterDays: 30), using Wolverine scheduled
messages as the first time-based automation in the codebase (ADR-026).
RequestAdditionalDetailsHandler now schedules the 15-day stale check;
MarkApplicationStaleHandler schedules the 30-day close check in turn.
Both automations re-check current Application status before acting, so a
meanwhile-submitted response or redelivered message is a safe no-op.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: AddDogProfile wizard

Replaces the old single-shot CreateDogProfile (which published a dog to
Discovery immediately on creation, with no photo requirement) with the
emlang yaml's actual four-step wizard: Start -> Add Details -> Add Photo
-> Publish. DogProfile gains a Draft/Published status; DogProfileCreatedV1
now fires on Publish instead of on creation, and publishing without a
photo is blocked ("Publish Blocked: Photo Required"), matching the yaml's
explicit rule that CreateDogProfileHandler skipped entirely.

Adds a new K9Crush.Modules.Profiles.Tests project (Layer 2, didn't exist
yet) and a Profiles Testcontainers fixture in K9Crush.IntegrationTests
(Layer 3, for StartDogProfileHandler's maxDogProfiles=10 cap).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: TheWindowShopper (Discovery guest browsing)

Builds the Guest-facing browsing/gating flow: Preview Nearby Dogs (extends
GetDiscoveryFeed with name/matchType/distanceMiles), Block Match Attempt
(anonymous gate a Guest hits trying to act on a dog before signing up),
and Flag Dog Of Interest / Claim Saved Match (two thin entry points over
the same DogOfInterest bookmark mechanism, guarded on the dog still
existing).

No server-side guest-session tracking: the client remembers a dogId from
before signup and passes it back to Claim Saved Match once the member's
profile is confirmed, rather than this codebase inventing an anonymous
identity concept it doesn't otherwise have.

matchType is "dog_to_dog" only for now - "shelter_dog" needs
ShelterAdoption to publish an integration event when a listing is added,
which it doesn't yet; flagged in GetDiscoveryFeed's doc comment rather
than silently left out.

DogProfileCreatedV1 gains a Name field (needed for the preview view),
threaded through PublishDogProfileHandler and DogProfileCreatedProjector.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: stand up the Notifications module

First increment (ADR-027): module skeleton, ManagingNotificationPreferences
chapter (View/Update, lazy-created default-all-enabled NotificationPreference
per owner), and NotifyOnMatch - the one automation ready to build today,
since MatchCreatedV1 is already published by Discovery's
DetectMutualMatchHandler. Deferred: the 4 ShelterAdoption automations
(need ShelterAdoption to start publishing events it doesn't yet) and
NotifyOnMessage/NotifyDogOwner/NotifyWaitlistOfOpening (need Chat/
TheUrgentSearch/Scheduling modules that don't exist).

Real SMTP send via MailKit, pointed at the smtp4dev dev container that
was already provisioned in docker-compose.yml but nothing talked to.
Production email provider (SendGrid/Postmark/SES) stays an open decision -
ISmtpNotificationSender only knows how to speak SMTP, not a specific
provider's API. Verified live: ran the same MailKitSmtpNotificationSender
code path against a real smtp4dev container and confirmed the message
landed via its API.

Two contract extensions needed along the way: MatchCreatedV1 gained
OwnerAId/OwnerBId (DetectMutualMatchHandler now resolves them from its
own DiscoveryFeedItem - the event's own doc comment already said
"alerts both owners" but never carried one), and a new OwnerContact
read model/projector consumes Identity's OwnerRegisteredV1 to get an
actual email address to send to (Notifications can only see other
modules' Contracts, never their Domain).

NotificationType adds a fifth value, Matches, beyond the four the emlang
yaml's ManagingNotificationPreferences chapter names - the yaml is silent
on match notifications but HLD/blueprint both name NotifyOnMatch as the
headline example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: Send Rejection Reason / Send Approval Notification

Unblocks two of the four ShelterAdoption automations deferred pending a
Notifications module. RejectApplicationHandler/ApproveApplicationHandler
now cascade ApplicationRejectedV1/ApplicationApprovedV1 (same tuple-return
pattern already used by CreateShelterAccountHandler), consumed by two new
Notifications automations via NotificationType.ApplicationStatus - one of
the four categories the emlang yaml's own preferences chapter actually
names, no invented category needed this time.

Extracted NotificationDispatcher (check preference, check contact,
send-or-suppress, log) out of NotifyOnMatchHandler once a third call site
needed the identical shape.

Still deferred: CancelApplicationsForRemovedListing + NotifyApplicantsOfCancellation
and NotifyApplicantOfListingChange (ShelterManagingListings) - these chain
within ShelterAdoption itself before reaching Notifications, which needs a
same-module cascade pattern this codebase doesn't have a precedent for yet
(everything published so far goes straight to a different module).

Fixed a real non-determinism bug surfaced while testing this: the new
DetectMutualMatchIntegrationTests test asserted DogAId/OwnerAId by
position, but DetectMutualMatchState assigns Dog A/B by sorting the pair,
not by which side of the test's setup was called "dogA" - fixed to an
order-independent comparison.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: bring GETTING_STARTED.md up to date

Rewritten against current reality instead of the original scaffold-era
draft: the solution actually builds/runs/tests now (~160 passing tests),
so the "never been restored, built, or run" framing and the "expect
floating-version restore failures" section are gone. The smoke test
posted to a single-shot CreateDogProfile endpoint that no longer exists -
replaced with a working walkthrough of the actual 4-step wizard (Start ->
Add Details -> Add Photo -> Publish) plus a full swipe-to-match-to-email
path using smtp4dev.

Added the two pieces this doc was missing that block anyone from actually
getting through a demo: how to configure Supabase's Database Webhooks so
Identity ever learns about a signup, and how to bootstrap the first Admin
account (there's no API for it - OwnerAccount.PromoteToShelter()'s doc
comment already pointed here for that instruction, which didn't exist
until now).

Section 6 (deliberately-not-done) and Section 7 (likely-to-break) rewritten
to drop resolved items (ArchitectureTests, role lookup, Notifications
plumbing, most of the "unverified API surface" risk - all real now) and
state the gaps that are still actually true (no UI, no Admin bootstrap
API, no local Supabase mock).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: bootstrap the first Admin via API

Closes the gap flagged in OwnerAccount.PromoteToShelter()'s doc comment
and worked around with a manual Postgres edit in GETTING_STARTED.md:
POST /api/v1/identity/me/bootstrap-admin self-promotes the calling
VerifiedOwner to Admin, but only while zero Admins exist anywhere -
once the first one is created, this permanently 409s for everyone,
including whoever just bootstrapped. Always "me" from the JWT, never an
arbitrary target, so a malicious first-mover can't promote someone else's
account during the bootstrap window.

GETTING_STARTED.md's Admin section now points at this endpoint instead of
a raw jsonb_set against Marten's internal table shape.

Surfaced a real test-isolation bug while writing this: unlike every other
handler's Layer 3 tests (which scope to random per-test ids that never
collide), this handler's "does any Admin exist" check is genuinely global
state - four tests sharing one Postgres container (the usual pattern)
meant whichever ran first permanently poisoned the others' "no Admin yet"
precondition. Fixed by giving BootstrapAdminIntegrationTests its own
fresh container per test method instead of the usual shared collection
fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: Notify Applicants Of Cancellation / Listing Change

Closes out the last two ShelterAdoption automations deferred pending
Notifications: the listing-removal cascade (Cancel Applications For
Removed Listing -> Notify Applicants Of Cancellation) and the
significant-edit notification (Notify Applicant Of Listing Change).

Introduces ADR-028: this is the first same-module command cascade in the
codebase (ShelterAdoption reacting to its own published event, not a
different module). RemoveDogListingHandler/EditDogListingHandler cascade
DogListingRemovedV1/DogListingSignificantlyEditedV1; ShelterAdoption now
sets IntegrationEventQueueName for the first time (previously null - it
had only ever published) to receive its own events back through the same
shared exchange every cross-module event already goes through - there's
no separate "local-only" pub/sub mechanism in this codebase. Verified
safe by reading Wolverine's actual RabbitMQ transport source before
building rather than assuming: the shared exchange is fanout by default
(every bound queue gets every message) and a message type with no local
handler is a graceful no-op (NoHandlerContinuation acks it), not an
error - so ShelterAdoption's queue quietly absorbing every other module's
irrelevant events is the same behavior every other module's queue
already relies on.

CancelApplicationsForRemovedListingHandler and
NotifyApplicantsOfListingChangeHandler (both ShelterAdoption automations)
react to those same-module events, cascading ApplicationCancelledV1/
ApplicationListingChangedV1 per affected applicant to two new
Notifications automations. Application gains CancelDogNoLongerAvailable()
- the open-application counterpart to the existing Draft-only
CloseDraftDogNoLongerAvailable(), reusing the same status.

EditDogListingRequest gains a SignificantChange field (the yaml's own
prop name) - caller-supplied, not auto-detected from a field diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: ShelterConfiguresNotificationTemplates

Notification template CRUD-with-locking for Shelter Staff: View/Edit/Save,
with "Template Edit Blocked" when someone else already has it locked. The
last emlang chapter that fit inside an existing module without standing
up a new one.

The yaml is sparse here - no actual content field on the template (added
Subject/Body, since a template with no content wouldn't do anything) and
no create/seed step at all, so ViewNotificationTemplatesHandler just
returns whatever's actually been created (possibly nothing - there's no
create endpoint, deliberately not invented). Edit submits new content and
acquires the lock in one step (the yaml shows no separate "start
editing" action); Save releases it. appliesToAlreadyQueuedNotifications
is accepted and echoed back but has no functional effect - nothing in
this codebase queues notifications for later delivery yet.

Deliberately does NOT wire these templates into the actual send path -
NotifyOnMatchHandler and the other Notify* automations still use their
own inline subject/body. That would be a separate, larger change across
every existing automation, not specified by this chapter.

Fixed the same test-isolation pattern as BootstrapAdminIntegrationTests:
ViewNotificationTemplatesHandler's query is unscoped (every template, no
per-test filter), so its "empty list" test can't safely share a
container with a test that seeds templates - given its own per-test
Postgres container instead of the usual shared collection fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: stand up the Chat module

First increment: the docs' own MVP slice table (event-sourced, matching
Discovery's pattern) - CreateConversationOnMatch (consumes MatchCreatedV1,
already live), SendMessage, GetConversationHistory, MarkAsRead - plus
GetMyConversations, a necessary addition beyond the 4 named slices since
nothing else lets a caller discover a conversationId after the
match-triggered automation creates one asynchronously.

Deliberately scoped narrow per this session's discussion: no SignalR
ChatHub yet (REST-only for now - persist + respond, no live push;
real-time is a genuinely separate follow-up), no manual message-request/
accept-gated conversations or group chat (the emlang yaml's fuller
MessagingDirectGroup chapter), and no Report Message -> Content Flagged
(needs a Moderation module that doesn't exist at all yet - zero code
anywhere).

Conversation streams are keyed directly by Discovery's MatchId rather
than a second pair-derived hash, since MatchCreatedV1 already provides a
stable unique key for the pair - avoids redundant derivation Discovery's
own MatchStream.IdFor needed for a different reason (no other shared key
existed there before a match forms).

Surfaced two real test-tooling issues while building this:
- FluentAssertions can't evaluate an `is` pattern inside a predicate it
  treats as an expression tree - fixed by filtering with OfType<T>() first.
- Marten's JasperFx.Events.SourceGenerator.AggregateEvolverGenerator needs
  AggregateStreamAsync<T> state types to be at least internally visible,
  not private nested classes.
Also hit the same cross-test-class isolation problem as
BootstrapAdminIntegrationTests/ViewNotificationTemplatesIntegrationTests -
sharing one container across Chat's test classes broke unrelated
LoadAsync calls after another class used AggregateStreamAsync with its
own state type. Root cause not fully isolated; fixed with the same
proven per-test-class dedicated container pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: build the AccountProfileSettings chapter

View/edit profile settings, request+confirm account deletion with a
recoverable 30-day grace period (ADR-026 scheduled-message pattern),
account recovery on next login, and feedback submission (Identity
module). Cross-module: ShelterAdoption reacts to the new
AccountDeletionRequestedV1 integration event to silently withdraw the
deleting owner's open applications, reusing Application's existing
Withdraw() method.

Deliberately deferred: the yaml's "Open Items Flagged" count display
and the shelter-side "Flag Active Listings" branch - both need either
a forbidden cross-module query or extra machinery nothing yet acts on.

New K9Crush.Modules.Identity.Tests project for Layer 1/2 coverage;
Layer 3 coverage for the cross-module withdrawal automation added to
K9Crush.IntegrationTests.

* feat: stand up the Admin module

Covers the emlang yaml's HandlingGeneralFeedbackSupport chapter -
Feedback Inbox/Detail views, Respond To Feedback, Resolve Feedback
(guarded to only be valid once responded). Fed by Identity's new
FeedbackSubmittedV1 cross-module event (SubmitFeedbackHandler now
cascades it) rather than a direct query, keeping module isolation
intact - Admin builds its own FeedbackInboxItem read model instead of
reading Identity's Feedback document.

Deliberately deferred: ModeratingFlaggedContentUserReports (no
flagged-content producers exist in any module yet) and
AdminDashboardLandingView (its "Platform Health Snapshot" link-out has
no infra-monitoring model in this codebase).

New K9Crush.Modules.Admin.Tests project (Layers 1-2) plus Layer 3
coverage in K9Crush.IntegrationTests/Admin; architecture-fitness test
assembly lists updated for the new module.

* feat: stand up the Media module

Covers the emlang yaml's UploadShareRemovePhotosAndVideos chapter -
Upload/Share/Remove Media plus Report Media -> Content Flagged. This
is the first real entity backing what was previously just an opaque
MediaAssetId Guid that Profiles.Api's AddDogProfilePhotoHandler already
accepted (built before this module existed): a caller now calls
UploadMedia first and passes the resulting id into AddDogProfilePhoto.

"Invalid file" rejection is enforced via IValidatableObject (extension
vs declared MediaType) rather than a separate command, consistent with
this codebase's validation convention. Report Media cascades a new
MediaContentFlaggedV1 integration event - nothing consumes it yet since
no Moderation module exists, same "publish now, consumer later"
pattern as Identity's FeedbackSubmittedV1 before Admin existed.

New K9Crush.Modules.Media.Tests project (Layers 1-2 only - no handler
here uses session.Query<T>(), so no Layer 3 integration tests are
needed this time); architecture-fitness test assembly lists updated.

* ci: add build/test/coverage workflow, CodeQL scanning, and Dependabot

- .github/workflows/ci.yml: restores, builds, and runs the full test
  suite (dotnet test collects coverage via coverlet.collector), then
  generates an HTML + GitHub-flavored markdown coverage report via
  ReportGenerator and publishes it to the workflow's job summary plus
  a downloadable artifact. Triggers on PRs and pushes to dev/main.
- .github/workflows/codeql.yml: CodeQL SAST scanning for C#, same
  triggers plus a weekly schedule.
- .github/dependabot.yml: weekly NuGet + GitHub Actions dependency
  update PRs.
- tests/Directory.Build.props: adds coverlet.collector to every test
  project in one place rather than nine separate csproj edits -
  explicitly re-imports the root Directory.Build.props since the SDK's
  auto-discovery only picks up the closest one, not every level.

Verified locally end-to-end (build, full test run with coverage
collection, report generation) before pushing - all 241 tests pass.

* ci: bump codeql-action to v4 ahead of v3's Dec 2026 deprecation

* build(deps): bump actions/upload-artifact from 4 to 7 (#1)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* build(deps): bump actions/checkout from 4 to 7 (#2)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* build(deps): bump actions/setup-dotnet from 4 to 6 (#3)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* Bump Marten and Marten.AspNetCore (#7)

Bumps Marten from 9.15.0 to 9.17.1
Bumps Marten.AspNetCore from 9.15.0 to 9.17.1

---
updated-dependencies:
- dependency-name: Marten
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: Marten.AspNetCore
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Power <8481638+Powerworks@users.noreply.github.com>

* test: fill remaining coverage gaps in Identity/Profiles/Discovery/ShelterAdoption

These four modules predate the 4-layer testing strategy, so their
earliest slices never got automated coverage. Adds tests for 23
previously-untested handlers:

- Identity (4): the Supabase webhook automations (Provision/Verify),
  PromoteOwnerToShelterOnAccountCreated, and OwnerAccountView. First
  tests in the codebase to mock HttpRequest/IConfiguration (NSubstitute
  proxies HttpRequest fine despite it being an abstract class, not an
  interface).
- Profiles (1): GetDogProfileHandler.
- Discovery (1): SwipeOnDogHandler - unlike UndoLastSwipeHandler, its
  success path never touches AggregateStreamAsync, so it's fully
  Layer-2-testable by mocking session.Events (IEventStoreOperations)
  directly rather than needing Testcontainers.
- ShelterAdoption (17): the full shelter onboarding chain (Request/
  Verify/Create/FlagVerificationIssues/Resubmit/Reject/Approve), the
  remaining application actions (AddDogListing/Withdraw/Review/
  SubmitAdditionalDetails), and 6 read-models - split across Layer 2
  (GetApplicationStatus/GetDogListingDetails, LoadAsync-only) and
  Layer 3 (GetShelterDogListings/GetPendingApplicationsQueue/
  GetDraftApplications/GetAdoptionListings, all using Query<T>()).
  GetAdoptionListingsHandler queries with no filter at all (the public
  marketplace browse), so its test gets its own dedicated container -
  same fix as the recurring shared-container isolation gotcha.

All 340 tests pass (up from 264), verified with two clean full runs of
K9Crush.IntegrationTests to rule out cross-test pollution in the newly
shared-container ShelterAdoption tests.

* feat: stand up the Moderation module

Covers the emlang yaml's ModeratingFlaggedContentUserReports chapter -
Moderation Queue/Flagged Content Detail views, Dismiss Flag, Remove
Content, and the Warn/Suspend/Ban User escalation ladder (each gated on
the yaml's own GWT preconditions: suspend requires a prior warning, ban
requires both a prior warning and suspension).

Fed by Media's MediaContentFlaggedV1, which now also carries
ContentOwnerId (the uploader, who Warn/Suspend/Ban target) alongside
the original ReporterOwnerId - the original shape only supported
building a queue, not actually acting against anyone. "Remove Content"
cascades a new ContentRemovalRequestedV1 event back to Media (Moderation
doesn't own the underlying content, so it can't delete it directly) -
Media's new RemoveMediaOnContentRemovalRequestedHandler reacts to it.

Deliberately does not enforce Suspend/Ban anywhere - UserModerationRecord
records the decision only. Actually blocking a suspended/banned owner
would mean every module's authorization checks consulting this record,
a cross-cutting change far bigger than this one chapter - a real,
separately-scoped follow-up.

New K9Crush.Modules.Moderation.Tests project (Layers 1-2) plus Layer 3
coverage for GetModerationQueueHandler (its "every open flag" query has
no per-test scoping, so it gets its own dedicated container, same as
GetFeedbackInboxIntegrationTests/GetAdoptionListingsIntegrationTests);
architecture-fitness test assembly lists updated for the new module.
Verified with two clean runs of K9Crush.IntegrationTests.

* feat: stand up the Places module

Covers the emlang yaml's LeaveAReviewRestaurantOrDogPark chapter -
Write/Publish/Edit/Remove Review, Respond To Review (gated by Place
ownership), and Report Review -> Content Flagged (Places' second real
Moderation producer, after Media).

Includes a disclosed gap-fill: CreatePlaceListing. The yaml's own
ClaimABusinessListing chapter is entirely about claiming an "existing
listing" - it never shows how one first comes into existence (implying
listings are meant to be seeded/imported externally). Without a create
command there'd be nothing for reviews to attach to, so this adds one
directly, deliberately NOT wired through ClaimABusinessListing's
request/verify/admin-override/ownership-transfer workflow - that's a
separate, larger, not-yet-built feature. Same for "Respond to Review"'s
ownership gate: checks Place.OwnerId directly rather than a verified
business-owner role, since that verification workflow doesn't exist.

Moderation's ContentType enum gains a Review member (appended, not
inserted - see FlaggedContent's own doc comment on why) plus a new
ReviewContentFlaggedProjectorHandler, confirming the "one moderation
queue, many distinctly-named per-module producers" design actually
generalizes to a second producer.

Deliberately deferred: ManagingSavedDogsSpots (entangled with
dog-to-dog matching/video-chat concepts that don't exist anywhere in
this codebase).

New K9Crush.Modules.Places.Tests project (Layers 1-2 only - no handler
uses session.Query<T>(), same as Media); architecture-fitness test
assembly lists updated for the new module. All 486 tests pass.

* docs: add v2 emlang spec reverse-engineered from the real implementation

Spec/K9CRUSH.emlang.v2.yaml captures the actual event model of the 10
built modules (Identity, Profiles, Discovery, ShelterAdoption,
Notifications, Chat, Admin, Media, Moderation, Places) as they exist in
code today, for re-import onto an eventmodelers board:

- 15 chapters rewritten to match real command/event names, props, and
  GWT tests, with inline DEVIATION comments explaining every place the
  build diverged from the original board (consolidated read-models,
  corrected actor swimlanes, deferred sub-flows).
- 3 new chapters with no v1 equivalent, covering real slices the
  original 35-chapter board never modeled: Discovery's actual
  swipe/match mechanic, Chat's real match-triggered messaging design,
  and Identity's Supabase-webhook + admin-bootstrap plumbing.
- 20 not-yet-built chapters carried over byte-identical to v1 (verified
  programmatically), so the remaining plan isn't lost.

v1 (K9CRUSH.emlang.yaml) is untouched and remains the record of what
was originally envisioned.

* Bump Microsoft.NET.Test.Sdk from 17.11.1 to 18.8.1 (#12)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.8.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Microsoft.AspNetCore.Authentication.JwtBearer from 10.0.9 to 10.0.10 (#9)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Authentication.JwtBearer
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0 (#16)

---
updated-dependencies:
- dependency-name: Testcontainers.PostgreSql
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump NSubstitute from 5.3.0 to 6.0.0 (#13)

---
updated-dependencies:
- dependency-name: NSubstitute
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Serilog.AspNetCore from 8.0.3 to 10.0.0 (#14)

---
updated-dependencies:
- dependency-name: Serilog.AspNetCore
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Microsoft.Extensions.Caching.StackExchangeRedis from 10.0.9 to 10.0.10 (#11)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Caching.StackExchangeRedis
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Swashbuckle.AspNetCore from 6.9.0 to 10.2.3 (#15)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-version: 10.2.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* catch up with dev (#19) (#20)

* Wire up dotnet user-secrets for Api.Host local development

Local Supabase/Postgres secrets no longer need to round-trip through
appsettings.Development.json (which stays permanently as CHANGE_ME
placeholders) - real values live in the per-project user-secrets store
outside the repo instead, merged in automatically by ASP.NET Core in
Development.



* Add public dog-listing browsing to ShelterAdoption

GetAdoptionListings and GetDogListingDetails let a member discover an
adoptable dog and its full detail across every active shelter, with no
ownership restriction (unlike the shelter's own management views). This
closes TheWouldBeAdopter's journey end-to-end: a member can now browse,
view, and submit an application entirely through discovered ids rather
than needing a pre-known dogListingId.



* Add the drafts feature and a 4-layer automated testing strategy

The drafts feature (StartDraftApplication/EditApplicationDetails/
ResumeDraftApplication/GetDraftApplications, plus SubmitApplicationHandler's
draft-graduation branch) had only ever been checked manually, so it's
committed alongside real coverage for it instead: TestingApproach.md lays
out domain unit tests, mocked-handler tests (NSubstitute), Testcontainers-
based Postgres integration tests, and NetArchTest architecture fitness
tests - the last of which also mechanizes two real bugs found earlier this
session (the DogProfile serialization gap and the *Handler naming
requirement for Wolverine discovery) so they can't silently reappear.



* Rename PawMatch to K9Crush and add the UndoLastSwipe automation slice

The scaffold, docs, and every project/namespace under code/PawMatch-scaffold
move to code/K9Crush-scaffold/K9Crush (K9Crush.* naming throughout, including
the .sln). Alongside the rename, Discovery gets an UndoLastSwipe command that
lets a member reverse their most recent swipe, with handler, domain state,
and unit/integration test coverage.

Also brings in the eventmodelers build-kit tooling (.claude/skills,
build-kit/, build-kit-dotnet/) used to scaffold slices from the event model
board, with runtime/scratch state and credentials gitignored.

* Delete PawMatch-scaffold/PawMatch directory

* Add top-level and build-kit READMEs

Orients a newcomer to the repo layout (the real K9Crush .NET app vs. the
design docs vs. the eventmodelers.ai slice-codegen tooling), and clarifies
that build-kit/ is the untouched Node/Express reference scaffold rather
than anything K9Crush is actually built on - build-kit-dotnet/ is the
adapted, in-use version.

* feat: Mark/Close Stale Application

Adds the ShelterReviewsApplication chapter's time-based automation pair
(staleAfterDays: 15, closesAfterDays: 30), using Wolverine scheduled
messages as the first time-based automation in the codebase (ADR-026).
RequestAdditionalDetailsHandler now schedules the 15-day stale check;
MarkApplicationStaleHandler schedules the 30-day close check in turn.
Both automations re-check current Application status before acting, so a
meanwhile-submitted response or redelivered message is a safe no-op.



* feat: AddDogProfile wizard

Replaces the old single-shot CreateDogProfile (which published a dog to
Discovery immediately on creation, with no photo requirement) with the
emlang yaml's actual four-step wizard: Start -> Add Details -> Add Photo
-> Publish. DogProfile gains a Draft/Published status; DogProfileCreatedV1
now fires on Publish instead of on creation, and publishing without a
photo is blocked ("Publish Blocked: Photo Required"), matching the yaml's
explicit rule that CreateDogProfileHandler skipped entirely.

Adds a new K9Crush.Modules.Profiles.Tests project (Layer 2, didn't exist
yet) and a Profiles Testcontainers fixture in K9Crush.IntegrationTests
(Layer 3, for StartDogProfileHandler's maxDogProfiles=10 cap).



* feat: TheWindowShopper (Discovery guest browsing)

Builds the Guest-facing browsing/gating flow: Preview Nearby Dogs (extends
GetDiscoveryFeed with name/matchType/distanceMiles), Block Match Attempt
(anonymous gate a Guest hits trying to act on a dog before signing up),
and Flag Dog Of Interest / Claim Saved Match (two thin entry points over
the same DogOfInterest bookmark mechanism, guarded on the dog still
existing).

No server-side guest-session tracking: the client remembers a dogId from
before signup and passes it back to Claim Saved Match once the member's
profile is confirmed, rather than this codebase inventing an anonymous
identity concept it doesn't otherwise have.

matchType is "dog_to_dog" only for now - "shelter_dog" needs
ShelterAdoption to publish an integration event when a listing is added,
which it doesn't yet; flagged in GetDiscoveryFeed's doc comment rather
than silently left out.

DogProfileCreatedV1 gains a Name field (needed for the preview view),
threaded through PublishDogProfileHandler and DogProfileCreatedProjector.



* feat: stand up the Notifications module

First increment (ADR-027): module skeleton, ManagingNotificationPreferences
chapter (View/Update, lazy-created default-all-enabled NotificationPreference
per owner), and NotifyOnMatch - the one automation ready to build today,
since MatchCreatedV1 is already published by Discovery's
DetectMutualMatchHandler. Deferred: the 4 ShelterAdoption automations
(need ShelterAdoption to start publishing events it doesn't yet) and
NotifyOnMessage/NotifyDogOwner/NotifyWaitlistOfOpening (need Chat/
TheUrgentSearch/Scheduling modules that don't exist).

Real SMTP send via MailKit, pointed at the smtp4dev dev container that
was already provisioned in docker-compose.yml but nothing talked to.
Production email provider (SendGrid/Postmark/SES) stays an open decision -
ISmtpNotificationSender only knows how to speak SMTP, not a specific
provider's API. Verified live: ran the same MailKitSmtpNotificationSender
code path against a real smtp4dev container and confirmed the message
landed via its API.

Two contract extensions needed along the way: MatchCreatedV1 gained
OwnerAId/OwnerBId (DetectMutualMatchHandler now resolves them from its
own DiscoveryFeedItem - the event's own doc comment already said
"alerts both owners" but never carried one), and a new OwnerContact
read model/projector consumes Identity's OwnerRegisteredV1 to get an
actual email address to send to (Notifications can only see other
modules' Contracts, never their Domain).

NotificationType adds a fifth value, Matches, beyond the four the emlang
yaml's ManagingNotificationPreferences chapter names - the yaml is silent
on match notifications but HLD/blueprint both name NotifyOnMatch as the
headline example.



* feat: Send Rejection Reason / Send Approval Notification

Unblocks two of the four ShelterAdoption automations deferred pending a
Notifications module. RejectApplicationHandler/ApproveApplicationHandler
now cascade ApplicationRejectedV1/ApplicationApprovedV1 (same tuple-return
pattern already used by CreateShelterAccountHandler), consumed by two new
Notifications automations via NotificationType.ApplicationStatus - one of
the four categories the emlang yaml's own preferences chapter actually
names, no invented category needed this time.

Extracted NotificationDispatcher (check preference, check contact,
send-or-suppress, log) out of NotifyOnMatchHandler once a third call site
needed the identical shape.

Still deferred: CancelApplicationsForRemovedListing + NotifyApplicantsOfCancellation
and NotifyApplicantOfListingChange (ShelterManagingListings) - these chain
within ShelterAdoption itself before reaching Notifications, which needs a
same-module cascade pattern this codebase doesn't have a precedent for yet
(everything published so far goes straight to a different module).

Fixed a real non-determinism bug surfaced while testing this: the new
DetectMutualMatchIntegrationTests test asserted DogAId/OwnerAId by
position, but DetectMutualMatchState assigns Dog A/B by sorting the pair,
not by which side of the test's setup was called "dogA" - fixed to an
order-independent comparison.



* docs: bring GETTING_STARTED.md up to date

Rewritten against current reality instead of the original scaffold-era
draft: the solution actually builds/runs/tests now (~160 passing tests),
so the "never been restored, built, or run" framing and the "expect
floating-version restore failures" section are gone. The smoke test
posted to a single-shot CreateDogProfile endpoint that no longer exists -
replaced with a working walkthrough of the actual 4-step wizard (Start ->
Add Details -> Add Photo -> Publish) plus a full swipe-to-match-to-email
path using smtp4dev.

Added the two pieces this doc was missing that block anyone from actually
getting through a demo: how to configure Supabase's Database Webhooks so
Identity ever learns about a signup, and how to bootstrap the first Admin
account (there's no API for it - OwnerAccount.PromoteToShelter()'s doc
comment already pointed here for that instruction, which didn't exist
until now).

Section 6 (deliberately-not-done) and Section 7 (likely-to-break) rewritten
to drop resolved items (ArchitectureTests, role lookup, Notifications
plumbing, most of the "unverified API surface" risk - all real now) and
state the gaps that are still actually true (no UI, no Admin bootstrap
API, no local Supabase mock).



* feat: bootstrap the first Admin via API

Closes the gap flagged in OwnerAccount.PromoteToShelter()'s doc comment
and worked around with a manual Postgres edit in GETTING_STARTED.md:
POST /api/v1/identity/me/bootstrap-admin self-promotes the calling
VerifiedOwner to Admin, but only while zero Admins exist anywhere -
once the first one is created, this permanently 409s for everyone,
including whoever just bootstrapped. Always "me" from the JWT, never an
arbitrary target, so a malicious first-mover can't promote someone else's
account during the bootstrap window.

GETTING_STARTED.md's Admin section now points at this endpoint instead of
a raw jsonb_set against Marten's internal table shape.

Surfaced a real test-isolation bug while writing this: unlike every other
handler's Layer 3 tests (which scope to random per-test ids that never
collide), this handler's "does any Admin exist" check is genuinely global
state - four tests sharing one Postgres container (the usual pattern)
meant whichever ran first permanently poisoned the others' "no Admin yet"
precondition. Fixed by giving BootstrapAdminIntegrationTests its own
fresh container per test method instead of the usual shared collection
fixture.



* feat: Notify Applicants Of Cancellation / Listing Change

Closes out the last two ShelterAdoption automations deferred pending
Notifications: the listing-removal cascade (Cancel Applications For
Removed Listing -> Notify Applicants Of Cancellation) and the
significant-edit notification (Notify Applicant Of Listing Change).

Introduces ADR-028: this is the first same-module command cascade in the
codebase (ShelterAdoption reacting to its own published event, not a
different module). RemoveDogListingHandler/EditDogListingHandler cascade
DogListingRemovedV1/DogListingSignificantlyEditedV1; ShelterAdoption now
sets IntegrationEventQueueName for the first time (previously null - it
had only ever published) to receive its own events back through the same
shared exchange every cross-module event already goes through - there's
no separate "local-only" pub/sub mechanism in this codebase. Verified
safe by reading Wolverine's actual RabbitMQ transport source before
building rather than assuming: the shared exchange is fanout by default
(every bound queue gets every message) and a message type with no local
handler is a graceful no-op (NoHandlerContinuation acks it), not an
error - so ShelterAdoption's queue quietly absorbing every other module's
irrelevant events is the same behavior every other module's queue
already relies on.

CancelApplicationsForRemovedListingHandler and
NotifyApplicantsOfListingChangeHandler (both ShelterAdoption automations)
react to those same-module events, cascading ApplicationCancelledV1/
ApplicationListingChangedV1 per affected applicant to two new
Notifications automations. Application gains CancelDogNoLongerAvailable()
- the open-application counterpart to the existing Draft-only
CloseDraftDogNoLongerAvailable(), reusing the same status.

EditDogListingRequest gains a SignificantChange field (the yaml's own
prop name) - caller-supplied, not auto-detected from a field diff.



* feat: ShelterConfiguresNotificationTemplates

Notification template CRUD-with-locking for Shelter Staff: View/Edit/Save,
with "Template Edit Blocked" when someone else already has it locked. The
last emlang chapter that fit inside an existing module without standing
up a new one.

The yaml is sparse here - no actual content field on the template (added
Subject/Body, since a template with no content wouldn't do anything) and
no create/seed step at all, so ViewNotificationTemplatesHandler just
returns whatever's actually been created (possibly nothing - there's no
create endpoint, deliberately not invented). Edit submits new content and
acquires the lock in one step (the yaml shows no separate "start
editing" action); Save releases it. appliesToAlreadyQueuedNotifications
is accepted and echoed back but has no functional effect - nothing in
this codebase queues notifications for later delivery yet.

Deliberately does NOT wire these templates into the actual send path -
NotifyOnMatchHandler and the other Notify* automations still use their
own inline subject/body. That would be a separate, larger change across
every existing automation, not specified by this chapter.

Fixed the same test-isolation pattern as BootstrapAdminIntegrationTests:
ViewNotificationTemplatesHandler's query is unscoped (every template, no
per-test filter), so its "empty list" test can't safely share a
container with a test that seeds templates - given its own per-test
Postgres container instead of the usual shared collection fixture.



* feat: stand up the Chat module

First increment: the docs' own MVP slice table (event-sourced, matching
Discovery's pattern) - CreateConversationOnMatch (consumes MatchCreatedV1,
already live), SendMessage, GetConversationHistory, MarkAsRead - plus
GetMyConversations, a necessary addition beyond the 4 named slices since
nothing else lets a caller discover a conversationId after the
match-triggered automation creates one asynchronously.

Deliberately scoped narrow per this session's discussion: no SignalR
ChatHub yet (REST-only for now - persist + respond, no live push;
real-time is a genuinely separate follow-up), no manual message-request/
accept-gated conversations or group chat (the emlang yaml's fuller
MessagingDirectGroup chapter), and no Report Message -> Content Flagged
(needs a Moderation module that doesn't exist at all yet - zero code
anywhere).

Conversation streams are keyed directly by Discovery's MatchId rather
than a second pair-derived hash, since MatchCreatedV1 already provides a
stable unique key for the pair - avoids redundant derivation Discovery's
own MatchStream.IdFor needed for a different reason (no other shared key
existed there before a match forms).

Surfaced two real test-tooling issues while building this:
- FluentAssertions can't evaluate an `is` pattern inside a predicate it
  treats as an expression tree - fixed by filtering with OfType<T>() first.
- Marten's JasperFx.Events.SourceGenerator.AggregateEvolverGenerator needs
  AggregateStreamAsync<T> state types to be at least internally visible,
  not private nested classes.
Also hit the same cross-test-class isolation problem as
BootstrapAdminIntegrationTests/ViewNotificationTemplatesIntegrationTests -
sharing one container across Chat's test classes broke unrelated
LoadAsync calls after another class used AggregateStreamAsync with its
own state type. Root cause not fully isolated; fixed with the same
proven per-test-class dedicated container pattern.



* feat: build the AccountProfileSettings chapter

View/edit profile settings, request+confirm account deletion with a
recoverable 30-day grace period (ADR-026 scheduled-message pattern),
account recovery on next login, and feedback submission (Identity
module). Cross-module: ShelterAdoption reacts to the new
AccountDeletionRequestedV1 integration event to silently withdraw the
deleting owner's open applications, reusing Application's existing
Withdraw() method.

Deliberately deferred: the yaml's "Open Items Flagged" count display
and the shelter-side "Flag Active Listings" branch - both need either
a forbidden cross-module query or extra machinery nothing yet acts on.

New K9Crush.Modules.Identity.Tests project for Layer 1/2 coverage;
Layer 3 coverage for the cross-module withdrawal automation added to
K9Crush.IntegrationTests.

* feat: stand up the Admin module

Covers the emlang yaml's HandlingGeneralFeedbackSupport chapter -
Feedback Inbox/Detail views, Respond To Feedback, Resolve Feedback
(guarded to only be valid once responded). Fed by Identity's new
FeedbackSubmittedV1 cross-module event (SubmitFeedbackHandler now
cascades it) rather than a direct query, keeping module isolation
intact - Admin builds its own FeedbackInboxItem read model instead of
reading Identity's Feedback document.

Deliberately deferred: ModeratingFlaggedContentUserReports (no
flagged-content producers exist in any module yet) and
AdminDashboardLandingView (its "Platform Health Snapshot" link-out has
no infra-monitoring model in this codebase).

New K9Crush.Modules.Admin.Tests project (Layers 1-2) plus Layer 3
coverage in K9Crush.IntegrationTests/Admin; architecture-fitness test
assembly lists updated for the new module.

* feat: stand up the Media module

Covers the emlang yaml's UploadShareRemovePhotosAndVideos chapter -
Upload/Share/Remove Media plus Report Media -> Content Flagged. This
is the first real entity backing what was previously just an opaque
MediaAssetId Guid that Profiles.Api's AddDogProfilePhotoHandler already
accepted (built before this module existed): a caller now calls
UploadMedia first and passes the resulting id into AddDogProfilePhoto.

"Invalid file" rejection is enforced via IValidatableObject (extension
vs declared MediaType) rather than a separate command, consistent with
this codebase's validation convention. Report Media cascades a new
MediaContentFlaggedV1 integration event - nothing consumes it yet since
no Moderation module exists, same "publish now, consumer later"
pattern as Identity's FeedbackSubmittedV1 before Admin existed.

New K9Crush.Modules.Media.Tests project (Layers 1-2 only - no handler
here uses session.Query<T>(), so no Layer 3 integration tests are
needed this time); architecture-fitness test assembly lists updated.

* ci: add build/test/coverage workflow, CodeQL scanning, and Dependabot

- .github/workflows/ci.yml: restores, builds, and runs the full test
  suite (dotnet test collects coverage via coverlet.collector), then
  generates an HTML + GitHub-flavored markdown coverage report via
  ReportGenerator and publishes it to the workflow's job summary plus
  a downloadable artifact. Triggers on PRs and pushes to dev/main.
- .github/workflows/codeql.yml: CodeQL SAST scanning for C#, same
  triggers plus a weekly schedule.
- .github/dependabot.yml: weekly NuGet + GitHub Actions dependency
  update PRs.
- tests/Directory.Build.props: adds coverlet.collector to every test
  project in one place rather than nine separate csproj edits -
  explicitly re-imports the root Directory.Build.props since the SDK's
  auto-discovery only picks up the closest one, not every level.

Verified locally end-to-end (build, full test run with coverage
collection, report generation) before pushing - all 241 tests pass.

* ci: bump codeql-action to v4 ahead of v3's Dec 2026 deprecation

* build(deps): bump actions/upload-artifact from 4 to 7 (#1)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...





* build(deps): bump actions/checkout from 4 to 7 (#2)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...





* build(deps): bump actions/setup-dotnet from 4 to 6 (#3)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...





* Bump Marten and Marten.AspNetCore (#7)

Bumps Marten from 9.15.0 to 9.17.1
Bumps Marten.AspNetCore from 9.15.0 to 9.17.1

---
updated-dependencies:
- dependency-name: Marten
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: Marten.AspNetCore
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...





* test: fill remaining coverage gaps in Identity/Profiles/Discovery/ShelterAdoption

These four modules predate the 4-layer testing strategy, so their
earliest slices never got automated coverage. Adds tests for 23
previously-untested handlers:

- Identity (4): the Supabase webhook automations (Provision/Verify),
  PromoteOwnerToShelterOnAccountCreated, and OwnerAccountView. First
  tests in the codebase to mock HttpRequest/IConfiguration (NSubstitute
  proxies HttpRequest fine despite it being an abstract class, not an
  interface).
- Profiles (1): GetDogProfileHandler.
- Discovery (1): SwipeOnDogHandler - unlike UndoLastSwipeHandler, its
  success path never touches AggregateStreamAsync, so it's fully
  Layer-2-testable by mocking session.Events (IEventStoreOperations)
  directly rather than needing Testcontainers.
- ShelterAdoption (17): the full shelter onboarding chain (Request/
  Verify/Create/FlagVerificationIssues/Resubmit/Reject/Approve), the
  remaining application actions (AddDogListing/Withdraw/Review/
  SubmitAdditionalDetails), and 6 read-models - split across Layer 2
  (GetApplicationStatus/GetDogListingDetails, LoadAsync-only) and
  Layer 3 (GetShelterDogListings/GetPendingApplicationsQueue/
  GetDraftApplications/GetAdoptionListings, all using Query<T>()).
  GetAdoptionListingsHandler queries with no filter at all (the public
  marketplace browse), so its test gets its own dedicated container -
  same fix as the recurring shared-container isolation gotcha.

All 340 tests pass (up from 264), verified with two clean full runs of
K9Crush.IntegrationTests to rule out cross-test pollution in the newly
shared-container ShelterAdoption tests.

* feat: stand up the Moderation module

Covers the emlang yaml's ModeratingFlaggedContentUserReports chapter -
Moderation Queue/Flagged Content Detail views, Dismiss Flag, Remove
Content, and the Warn/Suspend/Ban User escalation ladder (each gated on
the yaml's own GWT preconditions: suspend requires a prior warning, ban
requires both a prior warning and suspension).

Fed by Media's MediaContentFlaggedV1, which now also carries
ContentOwnerId (the uploader, who Warn/Suspend/Ban target) alongside
the original ReporterOwnerId - the original shape only supported
building a queue, not actually acting against anyone. "Remove Content"
cascades a new ContentRemovalRequestedV1 event back to Media (Moderation
doesn't own the underlying content, so it can't delete it directly) -
Media's new RemoveMediaOnContentRemovalRequestedHandler reacts to it.

Deliberately does not enforce Suspend/Ban anywhere - UserModerationRecord
records the decision only. Actually blocking a suspended/banned owner
would mean every module's authorization checks consulting this record,
a cross-cutting change far bigger than this one chapter - a real,
separately-scoped follow-up.

New K9Crush.Modules.Moderation.Tests project (Layers 1-2) plus Layer 3
coverage for GetModerationQueueHandler (its "every open flag" query has
no per-test scoping, so it gets its own dedicated container, same as
GetFeedbackInboxIntegrationTests/GetAdoptionListingsIntegrationTests);
architecture-fitness test assembly lists updated for the new module.
Verified with two clean runs of K9Crush.IntegrationTests.

* feat: stand up the Places module

Covers the emlang yaml's LeaveAReviewRestaurantOrDogPark chapter -
Write/Publish/Edit/Remove Review, Respond To Review (gated by Place
ownership), and Report Review -> Content Flagged (Places' second real
Moderation producer, after Media).

Includes a disclosed gap-fill: CreatePlaceListing. The yaml's own
ClaimABusinessListing chapter is entirely about claiming an "existing
listing" - it never shows how one first comes into existence (implying
listings are meant to be seeded/imported externally). Without a create
command there'd be nothing for reviews to attach to, so this adds one
directly, deliberately NOT wired through ClaimABusinessListing's
request/verify/admin-override/ownership-transfer workflow - that's a
separate, larger, not-yet-built feature. Same for "Respond to Review"'s
ownership gate: checks Place.OwnerId directly rather than a verified
business-owner role, since that verification workflow doesn't exist.

Moderation's ContentType enum gains a Review member (appended, not
inserted - see FlaggedContent's own doc comment on why) plus a new
ReviewContentFlaggedProjectorHandler, confirming the "one moderation
queue, many distinctly-named per-module producers" design actually
generalizes to a second producer.

Deliberately deferred: ManagingSavedDogsSpots (entangled with
dog-to-dog matching/video-chat concepts that don't exist anywhere in
this codebase).

New K9Crush.Modules.Places.Tests project (Layers 1-2 only - no handler
uses session.Query<T>(), same as Media); architecture-fitness test
assembly lists updated for the new module. All 486 tests pass.

* docs: add v2 emlang spec reverse-engineered from the real implementation

Spec/K9CRUSH.emlang.v2.yaml captures the actual event model of the 10
built modules (Identity, Profiles, Discovery, ShelterAdoption,
Notifications, Chat, Admin, Media, Moderation, Places) as they exist in
code today, for re-import onto an eventmodelers board:

- 15 chapters rewritten to match real command/event names, props, and
  GWT tests, with inline DEVIATION comments explaining every place the
  build diverged from the original board (consolidated read-models,
  corrected actor swimlanes, deferred sub-flows).
- 3 new chapters with no v1 equivalent, covering real slices the
  original 35-chapter board never modeled: Discovery's actual
  swipe/match mechanic, Chat's real match-triggered messaging design,
  and Identity's Supabase-webhook + admin-bootstrap plumbing.
- 20 not-yet-built chapters carried over byte-identical to v1 (verified
  programmatically), so the remaining plan isn't lost.

v1 (K9CRUSH.emlang.yaml) is untouched and remains the record of what
was originally envisioned.

* Bump Microsoft.NET.Test.Sdk from 17.11.1 to 18.8.1 (#12)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.8.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump Microsoft.AspNetCore.Authentication.JwtBearer from 10.0.9 to 10.0.10 (#9)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Authentication.JwtBearer
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...




* Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0 (#16)

---
updated-dependencies:
- dependency-name: Testcontainers.PostgreSql
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump NSubstitute from 5.3.0 to 6.0.0 (#13)

---
updated-dependencies:
- dependency-name: NSubstitute
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump Serilog.AspNetCore from 8.0.3 to 10.0.0 (#14)

---
updated-dependencies:
- dependency-name: Serilog.AspNetCore
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...




* Bump Microsoft.Extensions.Caching.StackExchangeRedis from 10.0.9 to 10.0.10 (#11)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Caching.StackExchangeRedis
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...




* Bump Swashbuckle.AspNetCore from 6.9.0 to 10.2.3 (#15)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-version: 10.2.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...




---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant