Adds an abstraction over the CreateChallengeRequest.complexity field
(already present in the proto since the original altcha module landed),
letting applications scale PoW difficulty per request based on actor
signals — repeat-offender counters, threat-intel headers, reputation
scores — without leaking those concerns into the gRPC provider.
- new IAltchaDifficultyAdvisor in Svrnty.CQRS.Altcha.Abstractions:
Task<uint?> GetComplexityAsync(...). null means "use the upstream
service's configured default."
- NullAltchaDifficultyAdvisor in Svrnty.CQRS.Altcha is the no-op
fallback registered by AddSvrntyAltcha() via TryAddSingleton, so
applications can replace it without ordering constraints.
- AltchaGrpcChallengeProvider now resolves the advisor and sets
CreateChallengeRequest.Complexity when the advisor returns a value.
The Altcha server clamps to its configured min/max, so callers
don't need to enforce bounds here.
No breaking changes to existing consumers — the no-op default keeps
behaviour identical when no advisor is registered.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a [Altcha]-decorated ProtectedActionCommand with IHasAltchaSolution
and a StubAltchaVerifier that treats the literal "valid-solution" as
passing PoW. Exercises both the HTTP MinimalApi and gRPC pipelines
without requiring an external altcha service.
Validated 4 scenarios on each transport (8 total, all pass):
HTTP /api/command/protectedAction POST 6001 gRPC :6000
-------------------------------------------------------------------
no AltchaSolution 401 Unauthenticated
AltchaSolution = "wrong" 401 Unauthenticated
AltchaSolution = "valid-solution" 200 result OK + result
addUser (no [Altcha]) 200 result OK + result
The last row confirms backward compatibility: a request type that
isn't decorated with [Altcha] bypasses the check entirely — the
AltchaAuthorizationCheck self-applies and no-ops, and any consumer
that doesn't call AddSvrntyAltcha() sees zero behavior change.
Generated CommandServiceImpl.g.cs verified to include the
ICommandAuthorizationCheck loop after validation, before handler
invocation, with the materialized command instance in ctx.Command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single helper extension: MapSvrntyAltchaChallenge() exposes
GET /api/altcha/challenge (configurable prefix) that fetches a fresh
challenge from IAltchaChallengeProvider and projects it onto the
JSON shape the altcha widget v3 expects from its challengeurl —
{ algorithm, challenge, salt, signature, maxnumber } in lowercase.
AllowAnonymous on purpose: the whole point is gating mutations from
unauthenticated callers, so the challenge endpoint must be reachable
without credentials.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default transport for IAltchaVerifier / IAltchaChallengeProvider —
calls a self-hosted altcha service over gRPC.
Wire contract
- Protos/altcha.proto defines svrnty.cqrs.altcha.v1.AltchaService with
CreateChallenge + VerifyChallenge RPCs. Shipped in this package as
source-of-truth; Go (and other) implementations vendor a copy.
- Challenge.challenge_hash is named (not "challenge") to avoid a C#
property/class name collision; the MinimalApi widget JSON remaps.
Runtime
- AltchaGrpcVerifier maps RpcException → AltchaVerifyResult.Fail with
a diagnostic reason ("verify-timeout", "service-unavailable", etc.)
so the auth check surfaces a clean Unauthorized without leaking
transport detail.
- AltchaGrpcChallengeProvider lets create-challenge failures bubble
(challenge endpoint should 5xx if altcha is down — clients retry).
- AltchaGrpcOptions.TokenProvider hook for consumer-supplied HMAC
service-token minting (plan-b will plug in ServiceTokenIssuer).
- AddGrpcClient<AltchaServiceClient> registered with HttpClientFactory.
AddSvrntyAltchaGrpcVerifier(Action<...>) and overload binding from
IConfiguration cover both wiring styles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Altcha authorization check, plugged into the
ICommandAuthorizationCheck / IQueryAuthorizationCheck seam.
Behavior
- Self-applies: returns Allowed for any request whose type isn't
decorated with [Altcha]. No-op for the 99% of endpoints that don't
need PoW.
- Reads ctx.Items["mobile_attested"] for Phase 3 bypass when the
attribute's AllowMobileAttestationBypass is true.
- Pulls the solution off the request via IHasAltchaSolution and
delegates verification to IAltchaVerifier (resolved per-call from
the request scope, so any verifier lifetime works).
- Stashes a diagnostic reason in ctx.Items["altcha_reason"]
(missing / misconfigured / invalid / replayed / expired / etc.)
for downstream middleware to surface in error responses.
- Singleton itself — stateless; one instance shared via factory
registrations under both check interfaces.
AddSvrntyAltcha() registers the check. The verifier is provided by
a transport-specific module (e.g. Svrnty.CQRS.Altcha.Grpc, next).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Abstractions for the Altcha-based proof-of-work module:
- AltchaAttribute (AllowMobileAttestationBypass param)
- IHasAltchaSolution — marker interface for request POCOs carrying
the widget's solution payload over HTTP/gRPC transports
- IAltchaVerifier / IAltchaChallengeProvider — transport-agnostic
interfaces; default gRPC implementations ship in Svrnty.CQRS.Altcha.Grpc
- IMobileAttestationProvider — Phase 3 placeholder; concrete impls
stamp ctx.Items["mobile_attested"] for the Altcha check to read as
a bypass when AllowMobileAttestationBypass is true
- AltchaChallenge / AltchaVerifyResult DTOs
Lean dependencies — only references Svrnty.CQRS.Abstractions for the
auth-check pipeline types.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a non-breaking, multi-instance authorization-check pipeline
that runs alongside the existing single-instance auth services.
Motivation
- Cross-cutting checks (proof-of-work, mobile attestation, rate-limit
gates, IP allow-lists) don't belong in consumer auth services — they
ship from framework modules and self-apply via attributes.
- The existing ICommandAuthorizationService takes only a Type; checks
need the request *instance* to read payload fields (e.g. an Altcha
solution carried on the command).
Shape
- New abstractions: ICommandAuthorizationCheck, IQueryAuthorizationCheck,
CommandAuthorizationCheckContext, QueryAuthorizationCheckContext.
- Context carries (Type, Instance, IServiceProvider, Items dict). The
Items dict lets sibling checks signal one another — e.g. a future
mobile-attestation check stamps "mobile_attested" for the Altcha
check to read as a bypass.
- AND semantics: framework resolves IEnumerable<…Check>, runs each in
registration order, first non-Allowed short-circuits.
- Wired into MinimalApi (commands + queries, POST + GET) and the
Svrnty.CQRS.Grpc.Generators source generator (commands, queries,
dynamic queries). In all paths the checks run AFTER the instance
is materialized and validated, BEFORE handler invocation.
Backward compatibility
- No registered checks = today's behavior exactly.
- ICommandAuthorizationService / IQueryAuthorizationService signatures
unchanged; consumers' existing auth services keep working untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generated CommandServiceImpl.g.cs had warnings like:
Slug = request.Slug?.ToList(), // CS8601 if Slug is non-nullable List<T>
The ?. was over-defensive: proto3 repeated fields are emitted as
RepeatedField<T> in C# and are NEVER null. The conditional access
made the result List<T>? which then triggered CS8601 when assigned
to a non-nullable target on the command POCO.
Dropped ?. in 4 emission sites in GrpcGenerator.cs covering:
- Top-level primitive list mapping (line 872)
- Top-level Guid list mapping (line 861)
- Nested primitive list mapping in NestedPropertyAssignment (line 1083)
- Complex list .Select chain in GenerateComplexListMapping (line 974,
conditional: kept ?. for value-type collections where source.Items is
read off a possibly-null wrapper message)
Real fix in the generator instead of CS8601 NoWarn suppression in
consumer csprojs. Consumers can drop the suppression after bumping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generated code was using locale-dependent parsing for decimal values.
On systems with comma decimal separator (e.g., French locale), parsing
"0.95" would throw FormatException because the system expected "0,95".
Switched all 4 decimal.Parse() call sites in the generated proto→domain
mappers to pass System.Globalization.CultureInfo.InvariantCulture for
consistent behavior across locales.
Inspired by JP's commit 599204d on feat/grpc-generator-improvements
(applied manually since cherry-pick had heavy context conflicts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The in-repo .claude/ harness (rules, skills, settings) is superseded by
the standalone claude-cqrs-plugin which provides the same guidance as a
reusable plugin across all Svrnty.CQRS projects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gRPC-only projects couldn't call app.UseSvrntyCqrs() without adding the
MinimalApi package. The method only calls ExecuteMappingCallbacks() which
is already in core — it had no MinimalApi dependency. Adds ASP.NET Core
FrameworkReference to the core package.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scaffolds a complete Svrnty.CQRS project from a natural language
description — creates solution, web project, DAL with PostgreSQL,
entities, Program.cs, first feature, proto file, and .editorconfig.
Defaults to gRPC-only; MinimalApi added only on request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automated formatting: BOM removal, using sort order, final newlines,
whitespace normalization across all projects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add path-specific rules for commands/queries, dynamic queries, validation, and gRPC
- Add /add-command, /add-query, /add-dynamic-query scaffolding skills
- Add project settings with post-edit formatting, proto validation, and build-gate hooks
- Add .editorconfig codifying existing code style conventions
- Trim CLAUDE.md from 414 to 130 lines (domain details moved to rules)
- Add .harness-version tracking for the shared claude-harness repo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generic types like Translation<T> now produce qualified message names
(e.g. TranslationOfFaqTranslationQueryItem) to avoid duplicate message
definitions in generated .proto files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert string filter values (e.g. from gRPC transport) to their actual
property types (DateTime, DateTimeOffset) so PoweredSoft.DynamicLinq can
build valid LINQ expressions. Also removes filters with null values and
recurses into composite filters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ProtoFileSourceGenerator and WriteProtoFileTask with a new
GenerateProtoFileTask that creates its own Roslyn compilation. This
solves the timing issue where source generators run too late for
Grpc.Tools to process the generated proto files.
The new task runs after ResolveAssemblyReferences but before
_gRPC_GetProtoc and CoreCompile, ensuring the proto file exists
when Grpc.Tools needs it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
If a proto file already exists (committed to repo), don't overwrite it
with a placeholder. This allows first-time builds to work correctly
when the proto file is already in the repository.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Grpc.Tools needs service definitions to generate the base classes
(CommandService+CommandServiceBase, etc.) that GrpcGenerator looks for.
Without these, the service bases wouldn't exist on first build.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The WriteProtoFileTask now receives RootNamespace and AssemblyName from
MSBuild and uses them for the placeholder proto's csharp_namespace instead
of hardcoded "Generated.Grpc". This ensures GrpcGenerator can find the
service base types on first build, enabling gRPC service registration
to work without requiring a second build.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The generated proto file holder class was internal, preventing
reflection-based service registration from discovering it across
assembly boundaries.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When AddGrpcFromConfiguration method is not found via reflection,
logs detailed diagnostics to help identify the root cause:
- Entry assembly name and total type count
- All Grpc-related types with their IsClass/IsSealed/IsPublic flags
- Whether the target method exists on each type
- ReflectionTypeLoadException details if type loading fails
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ICommandAuthorizationService check to CommandServiceImpl
- Add IQueryAuthorizationService check to QueryServiceImpl
- Add IQueryAuthorizationService check to DynamicQueryServiceImpl
- Return Unauthenticated/PermissionDenied gRPC status codes
- Use global:: prefix for Grpc.Core namespace to avoid conflicts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove the WithAllowAnonymousIfAttributePresent helper method.
Authorization should be handled by IQueryAuthorizationService and
ICommandAuthorizationService implementations, not by ASP.NET Core
middleware.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Endpoints with [AllowAnonymous] attribute on query/command class
now bypass ASP.NET Core authorization middleware.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Resolve ICommandAuthorizationService and IQueryAuthorizationService from request-scoped serviceProvider
- Allows Scoped authorization services that depend on DbContext
- Updated both MinimalApi and DynamicQuery.MinimalApi
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add DateTime/Timestamp conversion in nested property mapping
- Add IsReadOnly property detection to skip computed properties
- Extract ElementNestedProperties for complex list element types
- Skip read-only properties in GenerateComplexObjectMapping and GenerateComplexListMapping
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add proper complex type mapping for command results (same as queries already had)
- Handle nullable primitives (long?, int?, etc.) with default value fallback
- Fixes CS0029 and CS0266 compilation errors in generated gRPC service implementations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add IsCollectionTypeByInterface() to detect types implementing IList<T>, ICollection<T>, IEnumerable<T>
- Add GetCollectionElementTypeByInterface() to extract element type from collection interfaces
- Add IsCollectionInternalProperty() to filter out Count, Capacity, IsReadOnly, etc.
- Update GenerateComplexTypeMessage to generate `repeated T items` for collection types
- Filter out indexers (!p.IsIndexer) and collection-internal properties from all property extraction
This fixes the invalid proto syntax where C# indexers (this[]) were being generated
as proto fields, causing proto compilation errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The MapToProtoModel function was silently failing when mapping Guid
properties to proto string fields, causing IDs to be empty in gRPC
responses. Added explicit Guid → string conversion handling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>