Bump Testcontainers.PostgreSql from 3.10.0 to 4.13.0#16
Merged
Powerworks merged 1 commit intoJul 21, 2026
Conversation
--- 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
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>
Merged
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
📦 Dependency Updates
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.🚀 Features
🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
📦 Dependency Updates
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
latesttag 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 thelatesttag, we decided to replace it withvnext-preview. You find more information about the image here: https://github.com/Azure/azure-cosmos-db-emulator-docker.🚀 Features
🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
... (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, ideally1.52, which corresponds to v29, if you're already running it.🚀 Features
🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
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 to1.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 👍.
🚀 Features
🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
4.8.1
What's Changed
🐛 Bug Fixes
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 with4.8.0.Wait strategies now default to the
Runningmode. This mode expects the container to remain running throughout startup. If the container exits unexpectedly, Testcontainers will throw aContainerNotRunningExceptionthat 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 toIContainerBuilder.IContainerBuilder.WithResourceMappingandIContainer.CopyAsyncnow include two new optional arguments:uidandgid. If you do not need to specify those, use named arguments for the existing parameters:fileModeorct.🚀 Features
WithTarget(string)to image builder (#1534) @HofmeisterAn🐛 Bug Fixes
db.runCommand({hello:1})do detect readiness (#1548) @HofmeisterAn📖 Documentation
IContainerBuilder.WithAutoRemovecode comment (#1546) @hojmark🧹 Housekeeping
... (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 👏.
🚀 Features
.slnx(solution) file in common directory paths (#1492) @alexander-jesner-AP🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
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
Connectionheader break Podman and possibly other environments. Shipped a quick fix. Thanks again to @ahaeber and @victor-lambret for the help.🐛 Bug Fixes
🧹 Housekeeping
4.5.0
What's Changed
Big thanks to everyone who contributed to this release 🤜🤛.
🚀 Features
Task<ExecResult>extension methodThrowOnFailure(#1448) @HofmeisterAnDockerUnavailableExceptionwhen Docker is not available (#1308) @0xced🐛 Bug Fixes
📖 Documentation
🧹 Housekeeping
3.128.1(#1443) @HofmeisterAn4.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
🐛 Bug Fixes
WithName(IImage)(#1413) @HofmeisterAnCreatedTimeforUntilMessageIsLoggedwait strategy (#1384) @0xced📖 Documentation
testcontainers/helloworldimage in docs to version1.2.0(#1419) @VishnuJin🧹 Housekeeping
4.3.0
What's Changed
Thank you to all the contributors 🙌.
🚀 Features
🐛 Bug Fixes
🧹 Housekeeping
SharedContainerInstanceand useContainerFixtureinstead (#1378) @0xced4.2.0
What's Changed
Thanks for all the contributions. Great first release of the year ❤️.
🚀 Features
🐛 Bug Fixes
🧹 Housekeeping
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
IImageinterface. As part of this update, we removed obsolete properties and overloaded constructors. If you previously used an overload other thanDockerImage(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
net6.0and addnet9.0TFM (#1311) @HofmeisterAn🐛 Bug Fixes
DbContainerFixtureto match the behavior ofContainerFixture(#1310) @0xced📖 Documentation
🧹 Housekeeping
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
IImageinterface 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 theRepositoryproperty 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.and Papercutmodule (#1265) @HofmeisterAnIImageproperties with Docker DSL (#1256) @HofmeisterAn🚀 Features
execto replace current shell with the Kafka process (as ENTRYPOINT) (#1282) @jasoncoutureWithEnterpriseEdition(bool)) (#1269) @SossenbinderIImage,DockerImageand in theWithImage(string)implementation (#1249) @KielekIImageproperties with Docker DSL (#1256) @HofmeisterAnAccountNameandAccountKeyto public, move toAzuriteBuilder(#1258) @mikecole🐛 Bug Fixes
🧹 Housekeeping
Commits viewable in compare view.
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 rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill 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 versionwill 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 dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)