Skip to content

MP Core 1.0.0

Choose a tag to compare

@panahister panahister released this 14 Oct 12:36
· 6 commits to main since this release

MP Core 1.0.0

The first version that makes a promise. From here a patch fixes, a minor version adds, and a break waits for the next
major version (README, Versioning; ADR-020). The public
API was decided once, type by type (the review): 35 types and 2 members became internal, and 226 types stay.

A product that moves from 0.9.3 reads this page. It follows 0.10.1, which followed
0.10.0.

Every behaviour change since 0.9.3

One row for each change of behaviour, API, tooling or generated output since 0.9.3, with what to do and where it is
explained. "Nothing" means a product has nothing to change.

Version Change What to do Details
0.10.0 CurrentActor.AuthenticatedAt is when the person authenticated, and null when the token does not say. Keep reading AuthenticatedAt, or call IsAuthenticationFresh(maximumAge, now); read the new IssuedAt for the issue time. 0.10.0, Moving from 0.9.3
0.10.0 A second call of AddMPCoreBearerAuthentication fails. A host with two issuers replaces both calls with one AddMPCoreBearerIssuers. 0.10.0, Changed
0.10.0 MP Core's own messages are English only. A product that served the Persian texts of 0.9.x copies that resource file into its own repository and registers it. 0.10.0, Moving from 0.9.3
0.10.0 MPCore.Localization carries English only and no satellite assembly. Nothing, beyond the entry above. 0.10.0, Changed
0.10.0 Several token issuers: AddMPCoreBearerIssuers registers one bearer scheme per issuer. Nothing: an addition. A host with two issuers uses it in place of two calls of AddMPCoreBearerAuthentication. 0.10.0, New
0.10.0 A second token as evidence: IMPCoreBearerTokenValidator validates a token of any configured issuer with that issuer's rules. Nothing: an addition. 0.10.0, New
0.10.0 A person's token as evidence beside the service's identity: AddMPCoreSubjectEvidence() on the calling side, AddMPCoreSubjectEvidenceValidation(...) and UseMPCoreSubjectEvidence() on the called side. Nothing: an addition. A command that needs the person declares RequireSubjectEvidence(...). 0.10.0, New
0.10.0 Mutual TLS between services: AddMPCoreMutualTls(...) and RequireWorkloadCertificate() on the called side, AddMPCoreClientCertificate(...) on a client. Nothing: an addition, off until a host calls it. 0.10.0, New
0.10.0 The generated host carries mutual TLS: Security:MutualTls in appsettings.json, off by default; the host calls AddMPCoreMutualTls when it is on, and its pipeline has UseMPCoreCertificateForwarding() and UseMPCoreMutualTls(), which do nothing while it is off. Nothing while it is off. A repository generated before keeps its host; to adopt it, copy the setting and the three calls from a new one. 0.10.0, New
0.10.0 A value that never prints: SensitiveValue, and AddMPCoreSensitiveMessageTypes(...) for types whose objects are masked whole. Nothing: an addition. The registry it fills, SensitiveMessageTypes, became internal in 1.0.0; register types with AddMPCoreSensitiveMessageTypes. 0.10.0, New
0.10.0 The name processors mask more: dotted names with a sensitive segment, sensitive names inside a nested collection, a SensitiveValue, and an object of a registered message type. Nothing, unless a test or a dashboard matched on a value that is now ***. 0.10.0, New
0.10.0 MPCore.Observability references MPCore.Application. Nothing: restore pulls it. 0.10.0, New
0.10.0 A console sink after the redaction: MPCoreObservabilityOptions.EnableConsoleLogExporter, off by default. Nothing: an addition. 0.10.0, New
0.10.0 The generated host prints through the pipeline only: ClearProviders() and Observability:EnableConsoleLogExporter. A repository generated before keeps its host; to adopt it, copy the line and the setting from a new one. 0.10.0, New
0.10.0 Token introspection (RFC 7662): AddMPCoreTokenIntrospection(...) and ITokenIntrospector. Nothing: an addition. 0.10.0, New
0.10.0 Resource keys: AddMPCoreResourceKeys(...), RequireResourceKey("<key>"), [ResourceKey("<key>")] and ExemptFromResourceKey(); the product implements IResourceAuthorizer. Nothing until a host turns them on; then every endpoint declares a key or an exemption, or the host fails to start. 0.10.0, New
0.10.0 Module rules as tests: a generated modular monolith carries ModuleRulesTests, which fail on code that breaks a module rule. A repository generated before copies the tests, runs them and fixes what they name. 0.10.0, New
0.10.0 A product's own culture in English's place, proved over REST and gRPC; no API changes. Nothing: a product that serves its own culture makes it the only supported culture and the default of both transports, as the language guide says. 0.10.0, New
0.10.0 The identity provider administered by a governed adapter (ADR-019): the template's skills allow it in the one backend the owner names, under the six rules of mpcore-apply-security; the generated README.md, Program.cs and docs/architecture.md say the same. Nothing: no package and no API change. A repository generated before keeps its skills and texts; a backend that administers the identity provider follows ADR-019 and the skill of a new repository. 0.10.0, New
0.10.0 What a module owns, as tests: ModuleRulesTests also checks one schema for each module, no foreign key across schemas, the reason of a Contracts interface that writes (CrossModuleWriteAttribute), and that a handler takes only its own module's repositories. A repository generated before copies the tests, runs them and fixes what they name. 0.10.0, New
0.10.0 The cohort version Nothing: the packages, the tool and the template move together. 0.10.0, Changed
0.10.0 The release gate Nothing: it concerns the maintainers. 0.10.0, Changed
0.10.0 The template Nothing: its comments and documents say that MP Core's messages are English and that the repository adds its languages. 0.10.0, Changed
0.10.0 What MP Core does not do Nothing: the capability catalogue names four kinds of platform work as not available, and their owner. 0.10.0, Changed
0.10.1 The list of known keys: IMessageCatalog.ListKnownKeys(). Nothing: an addition. An IMessageCatalog the product implements itself throws NotSupportedException from it until it implements the member. 0.10.1, New
0.10.1 Typed message arguments: MessageArgument and FailureMessageDescriptor(key, arguments, typedArguments). Nothing: an addition; a message with string arguments only renders as before. 0.10.1, New
0.10.1 A product's formatter for typed arguments: catalog.UseArgumentFormatter(IMessageArgumentFormatter). Nothing: without one, a typed argument renders in its invariant form in every culture. 0.10.1, New
0.10.1 The API baseline Nothing: it concerns the maintainers. 0.10.1, Changed
0.10.1 The template A repository generated by the template alone has a solution file at its root, <Product>.slnx; one generated by mpcore new backend also holds a second, see the fix below. 0.10.1, Changed
0.10.1 The template's security skill Nothing: it now says what ADR-019 section 6 says. 0.10.1, Changed
0.10.1 The release workflow Nothing: it concerns the maintainers. 0.10.1, Changed
1.0.0 35 public types became internal. Read "Moving from 0.10.x": each has what to use instead. None was used by a guide, the template, a skill or a reference sample. 1.0.0, Moving from 0.10.x
1.0.0 2 public members became internal. Nothing: HandlerActorScope.ActorName and HandlerTenantScope.TenantOf were helpers of the scopes' own code. 1.0.0, Moving from 0.10.x
1.0.0 A direct Render refuses a typed argument name that is not an argument name. Name a typed argument with a lower-case letter followed by lower-case letters, digits or underscores. 1.0.0, Moving from 0.10.x
1.0.0 Building MP Core itself needs a checked baseline. Maintainers run ./eng/restore-api-baseline.sh once per checkout; a product does nothing. 1.0.0, Moving from 0.10.x
1.0.0 API validation Nothing: it concerns the maintainers. 1.0.0, Changed
1.0.0 The API baseline of this cohort Nothing: it concerns the maintainers. 1.0.0, Changed
1.0.0 CI and the release Nothing: they restore and check the baseline before they build. 1.0.0, Changed
1.0.0 NuGet.config Nothing: it concerns the maintainers. 1.0.0, Changed
1.0.0 The contract of the tool and the template Nothing: it records what the tool and the template already do. 1.0.0, Changed
1.0.0 The guides of a generated backend Add a project you create later to the solution with dotnet sln add. 1.0.0, Changed
1.0.0 The compatibility promise Read README, Versioning, and ADR-020. 1.0.0, Changed
1.0.0 A repository generated by mpcore new backend and 0.10.1 holds two solution files. Delete <Organization>.<Component>.Backend.sln in it. 1.0.0, Moving from 0.10.x
1.0.0 mpcore new backend left two solution files Install the 1.0.0 tool; a new repository has one. 1.0.0, Fixed
1.0.0 mpcore configure --cache, --business-audit and --timeseries stopped on an untouched repository Install the 1.0.0 tool; nothing in the repository changes. 1.0.0, Fixed

Moving from 0.9.3

Three entries change behaviour for a product that did not read the notes of 0.10.0; each says what changed, why and
what to do. The notes of 0.10.0 give the long form, with the steps to keep the Persian
texts.

CurrentActor.AuthenticatedAt is when the person authenticated, and null when the token does not say. It is
read from auth_time only. Before, a token without auth_time gave its issue time, so a token refreshed long after
the person signed in looked freshly authenticated. Code that judged how recently a person signed in keeps reading
AuthenticatedAt, or calls IsAuthenticationFresh(maximumAge, now), which is never true without an authentication
time; code that wanted the issue time reads IssuedAt.

A second call of AddMPCoreBearerAuthentication fails. It never worked: the second scheme kept no authority and no
issuer. A host with two issuers replaces both calls with one call of AddMPCoreBearerIssuers.

MP Core's own messages are English only. English is the only built-in language and the default; a product decides
its own languages in its own repository (ADR-012, amendment of 2026-10-08). MPCore.Localization no longer carries the
Persian translation of MP Core's keys that 0.9.x shipped, so a product that relied on it receives English for those
keys. To keep the Persian texts the product copies MPCoreMessages.fa.resx from the 0.9.3 source into its own
repository and registers it with AddMPCoreMessageCatalog(catalog => catalog.AddResources<...>()).

The masking of more names, the generated host's console, the dependency of MPCore.Observability on MPCore.Application
and the module rules (rows of the table above) need no step of their own: they widen what is masked or add to what is
generated, and a repository generated before keeps what it has.

Moving from 0.10.x

35 public types became internal. The public API of 0.10.1 had 261 types, and a product that builds on MP Core for
years needs to know which of them it may depend on. The review applied one rule: a type stays public only if the guides
document it, the template, a skill or a reference sample uses it, it appears in the signature of a kept member, or it is a
documented extension point a product implements. Everything else is internal, shared between the packages of the
cohort. The review found none of the 35 documented, used by the template, a skill or a reference sample, or named by a
kept signature. If you used one, the table says what to use instead. A type made public again later is an addition,
which a minor version may make.

Type made internal Use instead
IIdempotencyKeySource, NoIdempotencyKeySource, GrpcIdempotencyKeySource, HttpIdempotencyKeySource, IdempotencyKeyReading Nothing to implement: the transport reads the key. RequireIdempotencyKey() on an endpoint, or [RequireIdempotencyKey] on a gRPC method, makes it required
IIdempotencyScopeProvider, CurrentActorIdempotencyScope Nothing: a key is scoped to the current actor
IIdempotencyStore, EntityFrameworkIdempotencyStore<TContext>, IdempotencyPurger<TContext>, IdempotencySaveChangesInterceptor, IdempotencyKeyConflictException AddMPCoreIdempotency<TContext>() registers them, ApplyMPCoreIdempotency() maps the tables, UseMPCoreIdempotency(provider) adds the interceptor
IdempotencyContext, IdempotencyEntry, IdempotencyRequest, IdempotentExecutor, IdempotencyServiceCollectionExtensions IIdempotentExecutor.ExecuteAsync, registered by AddMPCoreIdempotency<TContext>()
SensitiveMessageTypes, GrpcSensitiveMessageOptions AddMPCoreSensitiveMessageTypes(...) for a type, AddMPCoreSensitiveMessages("package.Service") for a gRPC service
AuditMasking, AuditSaveChangesInterceptor MaskStyle in an AuditPolicy; AddMPCoreAudit<TContext>() and UseMPCoreAudit(provider)
MessageTranslationRefresher<TContext> AddMPCoreMessageTranslations<TContext>() registers it
AggregateEventBridgeExtensions AddMPCoreWolverineDbContext<TContext>() already applies it
ObservabilityPlan, ResolvedDestination MPCoreObservabilityOptions, read by AddMPCoreObservability or AddMPCoreFoundation
SensitiveActivityProcessor, SensitiveLogRecordProcessor, SensitiveDataPolicy AddMPCoreObservability registers the processors; RedactionOptions (Enabled, AdditionalSensitiveFields) configures them
IServiceTokenSource AddMPCoreServiceIdentity on a client's builder
MPCoreRoleRequirement, ScopeRequirement, ResourceKeyRequirement, SubjectEvidenceRequirement, WorkloadCertificateRequirement RequireMPCoreRole(...), RequireScope(...), RequireResourceKey("<key>"), RequireSubjectEvidence(...), RequireWorkloadCertificate()
SubjectEvidenceHeader The header is x-subject-token; AddMPCoreSubjectEvidence() carries it

2 public members became internal. HandlerActorScope.ActorName(envelope) and HandlerTenantScope.TenantOf(envelope) were
static helpers that the scopes' own For method called and nothing else did: no guide, template, skill or reference sample
uses them, and the middleware that Wolverine's generated code calls does not need them public. The Wolverine middleware
classes themselves stay public.

A direct Render refuses a typed argument name that is not an argument name. Render(key, arguments, typedArguments, culture) now does what FailureMessageDescriptor does: it requires a lower-case letter followed by lower-case letters, digits
or underscores. Before, only a
missing value and a repeated name were refused, and the name reached the warning log and the metric tag argument when a
formatter failed. The rule ends the name with \z, so "x\n", which the descriptor accepted, is refused as well. What to do:
name typed arguments as their placeholders are named.

A repository generated by mpcore new backend and 0.10.1 holds two solution files. Delete <Organization>.<Component>.Backend.sln at its
root. That tool created a solution file of its own beside the template's <Organization>.<Component>.slnx, and with two,
dotnet build at the root fails with MSB1011. The .slnx lists exactly the projects that were generated.

Building MP Core itself needs a checked baseline. Run ./eng/restore-api-baseline.sh once per checkout and again when
MPCoreBaselineVersion moves: a build of a runtime package fails without its checked baseline. For work offline,
-p:MPCoreOfflineWithoutApiBaseline=true builds without it, with a warning, and fails in CI.

Changed

What Now
API validation is never skipped in silence, and never in CI or the release. A build of a runtime project fails unless its package of the baseline version is in artifacts/packages, was checked, and is still the package that was checked; the one switch for work offline, -p:MPCoreOfflineWithoutApiBaseline=true, warns for every package and fails in CI. The script downloads each package from nuget.org only, and accepts it only with dotnet nuget verify --all, a repository signature whose certificate nuget.org lists, and the commit of the tag of that version. Before, a clean checkout validated nothing and printed that it had skipped. ADR-010, addendum of 2026-10-12
The API baseline of this cohort MPCoreBaselineVersion is 0.10.1, the last published version. The 35 types made internal are recorded as breaks against it, each with its reason, in src/<package>/CompatibilitySuppressions.xml; the files go with the release of 1.0.0, which becomes the baseline
CI and the release restore and check the baseline before they build, with the tags, after the script's self-test. They run the per-package check alone; require mode for the whole restore is not switched on: it fails on macOS for four third-party packages with expired signatures, and has not been run on GitHub's runner
NuGet.config lists nuget.org only. The baseline folder was a package source for MPCore.*
The contract of the tool and the template docs/contracts/cli.json and template.json record every command, option, default and probed exit code of the tool and the manifest it writes, and every parameter of the template and the files it generates for each shape and transport. Tests generate them by running the built tool and the template, fail when they differ, and fail when a fact of the file at the baseline tag is gone unless the version may break it. ADR-004, addendum of 2026-10-12
The guides of a generated backend say that a project you add later, a module's projects or a test project, is added to the solution with dotnet sln add, and why: dotnet test at the root runs only the tests the solution lists
The compatibility promise README, Versioning, states what 1.0.0 promises and how a version may change it; ADR-020 gives the reasons. The public API is recorded in PublicAPI.Shipped.txt, and the build fails on an undeclared change

Fixed

Where What was wrong Since
mpcore new backend left two solution files after generating, it created a solution file of its own beside the template's <Product>.slnx, so the repository held two and dotnet build at its root failed with MSB1011. It now uses the template's, and refuses a template that did not generate exactly one (exit code 4) 0.10.1
mpcore configure --cache, --business-audit and --timeseries stopped on an untouched repository they stopped with exit code 7 ("these files differ from what the generator produced") because every generation writes a new random UserSecretsId into the host project and the regenerated reference differed in that line. Had it applied, the project would have got another id and the secrets stored under the old one would have been orphaned. The reference now takes the repository's id 0.10.0

Known limits

Limit What to do
eng/restore-api-baseline.sh is a bash script and was run on macOS and Linux; Windows was not run on Windows, run it in Git Bash or WSL
Require mode for the whole restore is off the baseline, which is what a release is compared with, is checked package by package on every operating system; third-party packages keep NuGet's default verification
The contract files do not record the words of messages, or exit codes of situations the tests cannot set up see docs/contracts/README.md

Checking a download

The release run freezes the SHA-256 of every package it built in SHA256SUMS.txt, uploaded with the packages
as the run's artifact. The sums are of the packages as the release run built them. nuget.org adds its
repository signature (.signature.p7s) to every package after upload, so a downloaded file has another
SHA-256. To check a download, verify its signatures with dotnet nuget verify --all <file> and compare its
entries other than .signature.p7s with the built package.