[pull] main from fullstackhero:main#2
Open
pull[bot] wants to merge 868 commits into
Open
Conversation
Full route-mocked, JWT-seeded Playwright coverage of every page in both React apps — no live backend required. - Dashboard (121): login/demo picker, overview/activity/invoices, catalog, identity, tickets, chat, files, settings, system. - Admin (93): login/dashboard, tenants, users/roles, billing, audits/impersonation/webhooks/notifications/health, settings. - Shared harness in tests/helpers/shell-mocks.ts mocks all global AppShell calls (notifications/chat/profile/permissions; aborts SSE/realtime). All green, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… helpers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+ coverage-gate job Fixes the stale test matrix (Chat.Tests/Files.Tests were never run in CI) and adds a Coverage Gate job that runs the whole solution with Coverlet and fails below an 80%-ish line floor (ratchet — bump MIN_LINE as coverage rises).
…te limiting + security headers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…te limiting + security headers
…ption handler + rate limiting + security headers) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
….Middleware.Tests)
… audit Findings and fixes from a race-condition / code-smell audit (Roslyn navigator detect_antipatterns, find_dead_code, detect_circular_dependencies + targeted concurrency sweeps). Concurrency hygiene was already strong — no async void, no sync-over-async, no fire-and-forget, thread-safe singletons, zero circular deps. The genuine items: - Audit (static): swap the enricher list for an atomically-replaced immutable array read once per WriteAsync. Kills the latent "collection modified" race if Configure() ever runs concurrently with enrichment, and removes a process-global mutable-List test-isolation hazard. - QuotaEnforcementMiddleware: inject TimeProvider instead of DateTimeOffset.UtcNow for Retry-After, matching the rest of the Quota subsystem (deterministic in tests). - PresenceTracker.Connect: also report the offline->online transition when the AddOrUpdate update factory resurrects a count==0 key (Disconnect set 0 but hadn't removed it yet) — previously a reconnect in that window missed the presence broadcast. - ChannelMember: remove dead SetMuted (zero callers); IsMuted is now get-only (still persisted + exposed via DTO, populated by EF via backing field). Investigated but intentionally NOT changed (false positives confirmed): - catch(Exception) in hosted services already filter OperationCanceledException. - Flagged EF "missing AsNoTracking" queries all read-then-mutate-then-save (tracking required); the read-only paths already use AsNoTracking. - find_dead_code hits for ChannelAuthorization / EntityEntryExtensions are used extension methods; EF model snapshots are tooling artifacts. All unit (779) + integration (665) + middleware (5) tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (accurate to current codebase) Establish AGENTS.md as the canonical, tool-neutral AI guide (CLAUDE.md/GEMINI.md are thin @import bridges) and build out .agents/ guidance verified against the current code via Roslyn navigation + real-file extraction. Rules (.agents/rules/) — lean, on-demand, indexed from AGENTS.md: - cross-cutting: architecture, api-conventions, database, eventing, caching, jobs, resilience, storage, security, realtime, logging, testing, integration-testing - per-module: identity, multitenancy, chat, files, webhooks, auditing, billing, catalog, tickets, notifications - frontend: shared + admin + dashboard (the two React apps' real divergences) - replaces the old flat modules.md/persistence.md/testing-rules.md Skills (.agents/skills/) — audited the 6 existing against the live codebase and rewrote them (they were stale: IRepository<T>, PagedList<T>, Moq/FluentAssertions, Guid.NewGuid, class-level [FshModule(Order=n)], DbContext vs BaseDbContext, only 2 of the 4 module-registration sites). Added 5: add-react-page, add-full-slice, create-migration, add-integration-event, add-permission. Skills hold the recipe. Workflows (.agents/workflows/) — reconciled so they orchestrate and delegate to skills (no duplicated/contradictory templates); fixed stale facts (migrations run via DbMigrator, not on startup; TenantDbContext not "MultitenancyDbContext"). Also corrects the [FshModule] attribute (assembly-level positional, not class-level) in architecture.md and the AGENTS.md pointer. Docs only; no source/build impact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop nswag.consolecore from dotnet-tools and delete scripts/openapi/* (NSwag-based C# client generation is no longer used). - Remove scripts/test-cli.ps1 and the requirements/frontend-and-platform.md planning doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…page redesign, v10 - AI-Driven Development docs section (overview, AGENTS.md & .agents/, skills & workflows, developing with Claude Code) + section registration. - Sponsorship: sticky sponsor card atop the docs ToC, and a "Back the build" home section (Open Collective), placed right under the architecture section. - Homepage: new "Stack at a glance" bento opener; the architecture section rebuilt as a modular-monolith + vertical-slice diagram (two balanced boards); FSH brand mark in the hero; "Who it's for" redesigned as icon-led cards; module-card code panels removed; competitor product references removed. - Version: product version aligned to v10 (.NET 10) across the homepage and docs copy. API version paths (/api/v1, Features/v1, Contracts.v1) untouched. - Fix: type the header nav `match` field — astro check now 0 errors / 0 warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lish - Add "Tested to production standard" landing section (Testing.astro): a four-pillar scoreboard (unit / integration / E2E / architecture) over a green test-run terminal, leading with real-PostgreSQL-via-Testcontainers. Wire in as section 05; renumber WhoItsFor/FAQ/FinalCta to 06/07/08. Sync stale test counts in Hero (900+ -> 1,400+) and the FAQ answer. - Theme toggle: replace the muddy CSS token cross-fade with a View Transitions API snapshot cross-fade (GPU-composited, text stays crisp, no frame drops); clean instant fallback for reduced-motion / unsupported browsers. - Mobile responsiveness pass on the homepage: drop heading bases (sections 30px, hero 34px) so they stop dominating phones; hero stats, TechStack, and the testing scoreboard go single-column on mobile; hero terminal scrolls instead of clipping; FAQ answers reclaim full width on mobile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t, React SPA hosting - Pin Terraform >= 1.15.4 and AWS provider ~> 6.46 in the roots; permissive floors (>= 1.15 / >= 6.0) in child modules. Commit a multi-platform .terraform.lock.hcl locked to aws 6.46.0. - Collapse the shared/ -> app_stack/ wrapper into a single root (provider + backend now live in app_stack/), removing ~500 lines of duplicated vars and outputs plus a passthrough bug that silently dropped ~30 tunables. Dedupe tags via provider default_tags. - Add reusable modules/static_site (private S3 + OAC CloudFront, SPA 403/404->index.html fallback, default-on managed security headers, Terraform-owned config.json) and host the two React SPAs (admin, dashboard) that replaced the removed server-rendered Blazor service. SPA CloudFront origins are auto-added to the API CORS allow-list. No Route53/domain resources (custom aliases optional). - Drop the unnecessary api_task_secrets IAM policy (least privilege). - Add one-command deploy.sh / deploy.ps1: terraform apply -> optional API image build/push -> build + s3 sync + CloudFront invalidate for both SPAs. - Refresh README; terraform fmt + validate clean against aws 6.46.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub advisory GHSA-hv8m-jj95-wg3x (high severity, LZ4 decompression AccessViolationException) was published against MessagePack < 2.5.301, which Microsoft.AspNetCore.SignalR.StackExchangeRedis pulls in at 2.5.187. NuGet audit runs with warnings-as-errors, so every restore in CI now fails with NU1903 — breaking all PR checks regardless of their content. CentralPackageTransitivePinningEnabled is on, so a single PackageVersion entry bumps the transitive across the solution. Remove the pin once the SignalR backplane package references a patched MessagePack itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(deps): pin transitive MessagePack to 2.5.301 (NU1903 breaks all CI)
…urfaces fix(clients): permission-gate every nav surface to mirror server authorization
…lesAsync lookup GetUserRolesAsync issued one membership query per role in the tenant (N+1). One GetRolesAsync call now feeds a case-insensitive set lookup, matching ASP.NET Identity's normalized-name comparison semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lates PII minimization: user registration, token generation, and welcome-email failure logs now identify users by the pseudonymous UserId only. The email previously rode along in three message templates and flowed into every exported log sink. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct, and schema The property was get-only and never set anywhere, so it always serialized as false: dead weight in the domain, ChannelMemberDto, the dashboard client type, and a chat.ChannelMembers column. Removed end-to-end with migration DropChannelMemberIsMuted. Deliberate contract change: ChannelMemberDto loses a field no consumer ever read (verified across both React apps and all tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r chat error toasts - refreshAccessToken now aborts after the standard 30s timeout; a stalled refresh previously hung the shared refreshPromise and every queued 401-retry behind it (admin already had this guard). - Ticket comment composer passes the body through mutate(arg) instead of closed-over state (golden rule #9) and gains an aria-label. - Chat DM/delete/pin failure toasts now include the ProblemDetails description instead of a bare generic message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No codegen step exists - API types are hand-written per the frontend conventions. The dependency only created false expectations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hardening audit: identity N+1, PII log scrubbing, dead IsMuted column, dashboard refresh timeout
Build the link with QueryHelpers: trim the trailing slash from the configured origin, URL-encode each value, and include the tenant the reset page requires. A host-only OriginUrl produced "//reset-password" (404 in the SPA router), the link lacked the tenant query param (treated as malformed), and the email was not encoded. Adds UserPasswordServiceTests.
Validated the full publish pipeline end-to-end (pack -> install -> scaffold full/no-aspire/no-frontend -> build backend+frontends -> run Aspire stack -> auth + endpoints). Three fixes surfaced: - pack: stop shipping a local AI-tool settings file. src/.claude/settings.local.json was tracked and leaked into both the template package and scaffolded output (root .gitignore only anchored /.claude/). Untrack it and ignore **/.claude/ settings.local.json at any depth. - template pkg: render a README on the nuget.org gallery page. The template pack lives outside src/ so it never inherited src/Directory.Build.props's PackageReadmeFile; add PackageReadmeFile + the root README at package root (same README the CLI ships). Clears the "missing a readme" pack warning. - aspire: pin the dashboard to the documented port 15888. launchSettings used an auto-generated 17273 while the README, README-template, fsh CLI "Next Steps" (FshConstants.AspireDashboardPort) and `fsh doctor` all advertise 15888. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(identity): correct password-reset email link (trailing slash, tenant, URL-encoding)
…ness chore(nuget): publish-readiness fixes for CLI + template packages
The gallery icon was 1692x1692 (150 KB) — above NuGet's ~1024px practical max. Downscale the same FSH mark to 512x512 (53 KB), preserving transparency. Embedded at the root of both the CLI and template packages via PackageIcon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(nuget): optimize package icon to 512x512
…laims UserPermissionService computed the effective permission set from a user's DIRECT roles only, while IdentityService.AddRoleClaimsAsync already unions direct + group-derived roles when minting the JWT. Result: a user whose only role comes via a UserGroup saw the role in their token but failed every .RequirePermission() gate (and GET /identity/permissions under-reported). Union direct roles with roles reachable via UserGroups -> GroupRoles before resolving permissions (group mutations already invalidate this cache entry). Query-only — no schema change. Tests: - GroupRolePermissionTests (integration): a group-only user's own-permissions include the group role's grants and pass the gated endpoint. - AuthorizationMetadataTests (architecture): RequiredPermissionAttribute exists exactly once implementing IRequiredPermissionMetadata, so gates can't silently fail open via a duplicate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he JWT The dashboard JWT carries only role names; auth-context.tsx fetches the effective permission list from GET /api/v1/identity/permissions, caches it, and exposes permissionsHydrated. Update the agent rules/skills to match the shipped behavior, and align the Playwright auth-seed helper: permissions in the fake JWT are inert (gated specs mock GET /identity/permissions instead). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop `dotnet workload install aspire` (devcontainer postCreate + template README prerequisite): .NET 10 ships Aspire as NuGet packages, no workload. - DbMigrator is a console Generic Host, so its env gate reads DOTNET_ENVIRONMENT, not ASPNETCORE_ENVIRONMENT — fix the seed-demo refusal message + docs that told users to set the wrong variable. - fsh CLI: the `new` example used the non-existent `--no-git`; use `--no-frontend`. - CONTRIBUTING: Node.js 20+ to match both READMEs (was 22+). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore: pre-publish cleanup — group-permission fix, fetched-permissions docs, .NET 10 nits
Ran npm audit fix in clients/admin and clients/dashboard (all fixes within existing semver ranges) plus an esbuild override: - react-router 7.14.1 -> 7.18.0 (turbo-stream RCE, __manifest DoS, CSRF) - vite 7.3.2 -> 7.3.5 (server.fs.deny bypass, launch-editor NTLMv2) - ws 7.5.10 -> 7.5.11 (memory-exhaustion DoS) - js-yaml 4.1.1 -> 4.2.0 (quadratic-complexity DoS) - @babel/* -> 7.29.7 (sourceMappingURL arbitrary file read) - esbuild -> 0.28.1 via overrides (dev-server file read); vite 7 pins ^0.27.0 and 0.28.1 is the only patched release, so an override avoids the breaking vite 8 / plugin-react 6 major bump Both apps: npm audit reports 0 vulnerabilities and the production build (tsc -b + vite build) passes clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration failures (duplicate email/username, weak password) were thrown as CustomException defaulting to HTTP 500, and the actual Identity reasons — already carried in ErrorMessages → ProblemDetails `errors` — were dropped by the clients. Users saw a misleading "500 error while registering a new user" with no indication of what to fix. Backend: throw these (and password-mismatch) as 400 BadRequest, since they are client-input errors, not server faults. Frontend: surface the ProblemDetails `errors` array in both apps. dashboard's describe() and a new shared admin describeError() now show the real reason (e.g. "Email 'x' is already taken.") instead of the bare status + generic detail. Both shapes handled (Identity flat string[]; FluentValidation map). Tests: tighten the duplicate-email and weak-password registration integration tests to assert 400 instead of just non-success. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arity fix(identity): return 400 with specific reasons on registration failure
The admin app posted to /api/v1/identity/users/register and /api/v1/identity/users/change-password, but the API serves these as bare /api/v1/identity/register and /api/v1/identity/change-password (the /users prefix is only for user-collection sub-resources). The mismatch returned 405/404, so "Add new user" and "Change password" failed in the admin UI. The dashboard app already calls the correct bare routes, and the identity integration tests exercise them — only admin's users.ts was wrong, and it was internally inconsistent (it correctly used the IDENTITY base for forgot-password/reset-password/profile but the /users BASE for these two). Point both calls at the IDENTITY base and update the route-mocked Playwright specs to match. Fixes #1309 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er-route fix(admin): correct identity register & change-password routes (405)
…1312) WebhookSubscription.SecretHash stores the per-subscription signing secret encrypted at rest via ASP.NET Data Protection (IWebhookSecretProtector.Protect), then recovered with Unprotect to sign outbound HMAC payloads. The name "SecretHash" wrongly implies a one-way, irreversible hash, misrepresenting the value's security properties and inviting mishandling. - Rename the persisted property/column to ProtectedSecret (matches the existing protectedSecret local in CreateWebhookSubscriptionCommandHandler) with a RenameColumn migration (reversible, no data loss). - Rename IWebhookDeliveryService.DeliverAsync secretHash parameter to signingSecret: there the value is already unprotected plaintext used for signing, matching the signingSecret local in WebhookDispatchJob. No public contract change (the secret is write-only via command).
The tenant billing grace span is named "grace window" in code (GraceWindowDays option in both TenantBillingOptions and Identity's TenantGraceOptions, the Billing:GraceWindowDays config key, and the _graceWindowDays field) while every user-facing and conceptual reference calls it the grace period. "Grace period" is the standard SaaS billing term; standardize the vocabulary on it. - Rename the GraceWindowDays property in both options classes, the bound config key (Billing:GraceWindowDays -> Billing:GracePeriodDays), all references, and the _graceWindowDays field to GracePeriodDays. - Align the surrounding comments/XML docs that still said "grace window". BREAKING (config): deployments setting Billing:GraceWindowDays must rename the key to Billing:GracePeriodDays. The default (7 days) is unchanged. No public contract change: the GraceEndsUtc DTO/event fields are untouched.
…1315) * docs(plan): WhatsApp wallet + topup Phase 1 implementation plan * feat(billing): add Topup invoice purpose + wallet/topup enums * feat(billing): add Wallet aggregate + WalletTransaction ledger Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(billing): add TopupRequest aggregate with status transitions * feat(billing): persist wallet/ledger/topup tables + filter invoice unique index Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(billing): wallet/topup DTOs + string-enum mappings * feat(billing): wallet top-up invoice generation and credit-on-paid Adds GetOrCreateWalletAsync, CreateTopupInvoiceAsync to IBillingService/BillingService. MarkInvoicePaidAsync now credits the tenant wallet and completes the TopupRequest in the same unit of work when Purpose == Topup. Invoice.CreateTopupDraft convenience factory added. Invoice number scheme: TOP-{yyyyMM}-{tenantToken}-{requestSuffix} ensures collision-safety for multiple top-ups in the same month. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(billing): guard topup-invoice to Pending requests + eager-load wallet ledger Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(billing): tenant wallet + topup-request endpoints Adds the three dashboard-facing wallet endpoints under api/v1/billing: GET /wallet/me, POST /wallet/topup-requests, GET /wallet/topup-requests/me. Handlers root-gate to caller tenant via IMultiTenantContextAccessor; BillingDbContext is not EF-filtered so every handler explicitly filters by callerTenantId. Validators enforce Amount bounds and pagination invariants. Integration tests confirm happy-path and cross-tenant isolation (Tenant B cannot see Tenant A's requests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(billing): operator list/approve/reject topup-request endpoints Adds three admin-facing endpoints under api/v1/billing/wallet/topup-requests: - GET (root cross-tenant list, non-root scoped to own tenant) - POST /{id}/approve (creates+issues Topup invoice via IBillingService) - POST /{id}/reject (transitions request to Rejected) Root-gating mirrors GetInvoicesQueryHandler. Approve/reject require BillingPermissions.Manage and use .WithIdempotency(). All handlers are public sealed with ValueTask<T> and ConfigureAwait(false). Three validators added (Architecture.Tests requires validator per command/paginated-query). Architecture.Tests extended with Approve/Reject as valid endpoint verb prefixes. 4 integration tests (approve happy-path, reject happy-path, cross-tenant isolation, root tenantId filter) + 12 unit validator tests — all green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(billing): reject of non-pending topup returns 4xx, not 500 Add an explicit Pending-state guard in RejectTopupRequestCommandHandler that throws CustomException(HttpStatusCode.Conflict) before delegating to the domain Reject() method. Without the guard the domain's InvalidOperationException fell through to the global 500 branch. Add integration test Reject_of_already_rejected_request_returns_409_Conflict to TopupApprovalTests that asserts a second reject on the same request returns 409 Conflict. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(dashboard): WhatsApp wallet page + request top-up Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(admin): top-up requests review/approve/reject page * test(e2e): wallet topup request + admin approval flows Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(billing): enforce exactly-once topup credit via unique partial index Top-up wallet credit on invoice-paid was exactly-once only via an in-process status check; two concurrent MarkInvoicePaid calls on the same top-up invoice could both credit and commit. Add a unique partial index on WalletTransactions(ReferenceId) filtered to Kind=Topup so a second ledger row for the same top-up request cannot be inserted -- the concurrent second transaction violates the constraint and rolls back, leaving exactly one credit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1314) Resolves CodeQL cs/exposure-of-sensitive-information (alert #27). The delivery-failure warning logged the raw recipient address, writing PII to an external sink (the log). Drop {Email} from the template; {Context} already identifies the operation for debugging. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )