Bump Serilog.AspNetCore from 8.0.3 to 10.0.0#14
Merged
Powerworks merged 1 commit intoJul 21, 2026
Merged
Conversation
--- 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>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
dependabot
Bot
deleted the
dependabot/nuget/code/K9Crush-scaffold/K9Crush/Serilog.AspNetCore-10.0.0
branch
July 21, 2026 21:42
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 Serilog.AspNetCore from 8.0.3 to 10.0.0.
Release notes
Sourced from Serilog.AspNetCore's releases.
10.0.0
What's Changed
New Contributors
Full Changelog: serilog/serilog-aspnetcore@v9.0.0...v10.0.0
9.0.0
What's Changed
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)