Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/data/search-index.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs-src/onboarding/00-primer.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ the full set, for orientation:
| 032 | ~~Password hashing: PBKDF2-HMAC-SHA512 (600k iters) with by-salt-length migration of legacy records~~ **superseded by [ADR-102](https://ivanball.github.io/docs/adr/102-pbkdf2-only-password-hashing.html)** (PBKDF2-only, the legacy branch deleted) | [g08](group-08-auth.md) |
| 033 | Resource-ownership authorization: `OwnerOrAdminFilter`/`OwnershipHelper` row-scope a single resource beside RBAC | [g08](group-08-auth.md) |
| 034 | Generic entity controllers + dynamic query contract (`EntityControllerBase`; `fields`/filter/sort/paging); the write side (generic create/update/delete, `CrudEntityControllerBase`) is completed by [ADR-099](https://ivanball.github.io/docs/adr/099-generic-write-side-entity-commands.html) | [g12](group-12-api-hosting-mapping.md)/[g03](group-03-querying-specifications.md) |
| 035 | Optimistic concurrency: a `RowVersion` token round-trips through `IConcurrencyAware` DTOs; a stale write maps to HTTP 412 Precondition Failed | [g07](group-07-persistence-ef-core.md)/[g12](group-12-api-hosting-mapping.md) |
| 035 | Optimistic concurrency over HTTP preconditions: an `IConcurrencyAware` read DTO carries the `RowVersion` and the GET emits it as a weak `ETag`; a `[SupportsIfMatch]` write must echo it in `If-Match` (no precondition is 428, a stale token is 412 Precondition Failed) | [g07](group-07-persistence-ef-core.md)/[g12](group-12-api-hosting-mapping.md) |
| 036 | External OAuth login (Google/GitHub/Apple): `OAuthControllerBase` swaps a single-use 2-minute code for the local JWT pair (tokens never ride the redirect URL) | [g08](group-08-auth.md)/[g12](group-12-api-hosting-mapping.md) |
| 037 | Field-level encryption at rest: `EncryptedStringConverter` (AES-256-GCM), shipped + tested but **unadopted** (no entity config wires it yet) | [g07](group-07-persistence-ef-core.md) |
| 038 | Supply-chain provenance: SBOM release gate + committed lock files + transitive vuln audit + `packageSourceMapping` | [devops-cicd](devops-cicd.md) |
Expand Down
7 changes: 4 additions & 3 deletions docs-src/onboarding/CONCEPT-MAPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,10 @@ flowchart TD

## 8. Persistence, database-per-service + polyglot engines ([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html) / 018 / 030)

One concrete `SQLServerDbContext` over the abstract `ApplicationDbContext`, **one instance per
database**. Each entity is engine-agnostic; a single `[UseDataSource(engine)]` attribute on its
config class picks SQL Server, Cosmos, or SQLite. Cross-source relationships auto-degrade; the outbox
One sealed context class per engine (`SQLServerDbContext`, `PostgreSQLDbContext`, `SqliteDbContext`,
`CosmosDbContext`) over the abstract `ApplicationDbContext`, **one instance per database**. Each
entity is engine-agnostic; a single `[UseDataSource(engine)]` attribute on its config class picks
SQL Server, PostgreSQL ([ADR-113](https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html)), Cosmos, or SQLite. Cross-source relationships auto-degrade; the outbox
is the cross-source consistency mechanism. Each service self-applies its EF migrations at boot
([ADR-030](https://ivanball.github.io/docs/adr/030-startup-sole-migrator.html)).

Expand Down
2 changes: 1 addition & 1 deletion docs-src/onboarding/devops-cicd.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ static client secret is ever stored in GitHub. `packages: read` is needed for `G
NuGet restore of the MMCA.Common packages.

`actions: read` is the least obvious of the four, and the comment above it says why (`deploy.yml:32-34`):
the three freshness gates read run history through the Actions API, **and** `e2e-gate` needs it here
the four freshness gates read run history through the Actions API, **and** `e2e-gate` needs it here
because a reusable workflow can never request more than its caller holds, so `e2e.yml`'s own
skip-if-unchanged guard would die on "Resource not accessible by integration" if the caller did not grant
it. A `permissions:` block is a ceiling for every workflow it calls, not just for its own steps.
Expand Down
6 changes: 4 additions & 2 deletions docs-src/onboarding/devops-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,10 @@ demonstrates deliberate stratification rather than a single catch-all integratio

## 3. Shipped testing-infrastructure packages

MMCA.Common ships **five** of its nineteen packages as testing infrastructure that downstream apps
consume as NuGet references rather than writing their own harness (`MMCA.Common/FACTS.md:19,35-39`):
MMCA.Common ships **five** of its twenty-two packages as general testing infrastructure that downstream
apps consume as NuGet references rather than writing their own harness (`MMCA.Common/FACTS.md:19,38-42`;
a sixth, `MMCA.Common.AI.Testing`, is the language-model replay harness covered in
[group-27](group-27-common-ai-integration.md)):

- `MMCA.Common.Testing` (23 types), integration-test base, JWT generator, SQL fixture base, handler
scaffold, entity builders, and the eight runtime conformance bases (this section).
Expand Down
2 changes: 1 addition & 1 deletion docs-src/onboarding/group-07-persistence-ef-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -1815,7 +1815,7 @@ survives a module being pulled out into its own service.

`[Rubric §8, Data Architecture]` assesses whether persistence is deliberate: one save boundary, one concurrency story. This interface has no `Save` and no `SaveChangesAsync`. The doc comment states the division (`IRepository.cs:359-363`): the repository stages changes and never flushes them, because persisting is the unit of work's job, so every repository touched in a scope is written as one unit under one audit stamp. A handler that mutates through a repository and then forgets [`IUnitOfWork.SaveChangesAsync`](#iunitofwork) has written nothing.
- **Concept introduced, optimistic-concurrency wiring and change-tracking-bypass writes.** Five members carry the weight.
- `SetOriginalRowVersion(TEntity entity, byte[] rowVersion)` (`IRepository.cs:406`): plants the client's last-observed `RowVersion` as the tracked entity's *original* concurrency token, so the next save emits its `WHERE RowVersion = @original` and raises `DbUpdateConcurrencyException`, mapped to `409 Conflict`, when the row moved since the client read it (`IRepository.cs:399-403`). The implementation sets `OriginalValue` on the tracked entry's `RowVersion` property and rejects a null token outright (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-84`).
- `SetOriginalRowVersion(TEntity entity, byte[] rowVersion)` (`IRepository.cs:406`): plants the client's last-observed `RowVersion` as the tracked entity's *original* concurrency token, so the next save emits its `WHERE RowVersion = @original` and raises `DbUpdateConcurrencyException` when the row moved since the client read it (`IRepository.cs:399-403`). On an endpoint marked `[SupportsIfMatch]` that is answered as `412 Precondition Failed` (`MMCA.Common/Source/Presentation/MMCA.Common.API/Concurrency/SupportsIfMatchAttribute.cs:130-138`); on an endpoint without it the exception falls through to `DbUpdateExceptionHandler`'s plain `409 Conflict` ([ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). The implementation sets `OriginalValue` on the tracked entry's `RowVersion` property and rejects a null token outright (`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/Persistence/Repositories/EFRepository.cs:75-84`).
- `SetOriginalRowVersion(Domain.Interfaces.IRowVersioned childEntity, byte[] rowVersion)` (`IRepository.cs:417`): the same protection for a tracked **child** of the aggregate, for example a `ProductVariant` under a `Product`. The doc comment explains why a second overload exists at all (`IRepository.cs:408-414`): the repository's `TEntity` is the root, so the typed overload cannot reach children, and this one accepts any [`IRowVersioned`](group-02-domain-building-blocks.md#irowversioned) entity instead ([ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html)). It reaches the entry through an `(object)` cast (`EFRepository.cs:86-95`).
- `TouchConcurrencyToken(TEntity entity)` (`IRepository.cs:440`): forces the aggregate ROOT to take part in the next save when a conditional write left it untouched, so the precondition the caller sent is actually evaluated (SEC-Common-77). The remarks explain the gap it closes (`IRepository.cs:424-439`): `SetOriginalRowVersion` only stamps the tracked entry's ORIGINAL value, it does not make the entry dirty, so an applier that changes only child rows leaves the root `Unchanged`, EF emits no root `UPDATE`, no `WHERE RowVersion = @token` reaches the database, and the stale-token check silently does not fire, letting two administrators editing different children of the same aggregate from the same ETag both get `200` while the second silently discards the first's edit. Touching the root restores the `412` [ADR-035](https://ivanball.github.io/docs/adr/035-optimistic-concurrency.html) promises. It is declared with a default no-op body (`IRepository.cs:440-443`) so an existing implementer stays source- and binary-compatible; a repository that does not override it keeps the pre-hardening behaviour rather than failing to compile. `EFRepository<TEntity, TIdentifierType>` overrides it.
- `ExecuteDeleteAsync(Expression<Func<TEntity, bool>> where, CancellationToken)` (`IRepository.cs:453-455`): a set-based delete run directly in the database, one statement, no change tracker. The doc comment warns in capitals that it does **not** trigger domain events, audit stamps, or soft delete, and is for maintenance scenarios only (`IRepository.cs:445-452`). The implementation is a one-liner over the `DbSet` (`EFRepository.cs:118-124`).
Expand Down
2 changes: 1 addition & 1 deletion docs-src/onboarding/group-14-module-system-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ the database-per-service strategy
([ADR-006](https://ivanball.github.io/docs/adr/006-database-per-service.html)).
[`UseDataSourceAttribute`](#usedatasourceattribute)
(`MMCA.Common/Source/Core/MMCA.Common.Infrastructure/UseDataSourceAttribute.cs:12-17`) names the
**engine** ([`DataSource`](group-07-persistence-ef-core.md#datasource): SQL Server, Cosmos or SQLite)
**engine** ([`DataSource`](group-07-persistence-ef-core.md#datasource): SQL Server, PostgreSQL, Cosmos or SQLite)
and is carried by the provider-specific configuration base classes, so choosing a base class chooses
the engine with no change to the entity (see
[primer §2](00-primer.md#2-architectural-styles-this-codebase-commits-to)).
Expand Down
7 changes: 4 additions & 3 deletions docs-src/onboarding/group-28-testing-infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
**What this group covers.** Everything the codebase uses to *prove* itself: the five reusable
test-support packages that ship out of `MMCA.Common/Source/Hosting` (`MMCA.Common.Testing`,
`MMCA.Common.Testing.Architecture`, `MMCA.Common.Testing.Aspire`, `MMCA.Common.Testing.E2E`,
`MMCA.Common.Testing.UI`, five of the nineteen packages published from MMCA.Common and listed in
`MMCA.Common/FACTS.md:19-40`), the
`MMCA.Common.Testing.UI`, five of the twenty-two packages published from MMCA.Common and listed in
`MMCA.Common/FACTS.md:19-43`; the sixth test-support package, `MMCA.Common.AI.Testing`, belongs to
[group-27](group-27-common-ai-integration.md)), the
architecture-fitness rule library that gates the build, the runtime-conformance bases that gate a
booted host, the backend-less component Gallery harness, the BenchmarkDotNet performance suite, and
the many per-repo test projects that consume all of it. The distinction to hold onto while reading:
Expand Down Expand Up @@ -1054,7 +1055,7 @@ tracking. Its results are compared in CI by `build/perfgate` against the committ
(`MMCA.Common/.github/workflows/ci.yml:371`), so moving a number has to be a deliberate, reviewed
change, [Rubric §12, Performance & Scalability]. The same job family carries one more quiet gate
worth knowing: the unit run is invoked with `--minimum-expected-tests 2000`
(`MMCA.Common/.github/workflows/ci.yml:144`), so a discovery regression that silently drops thousands
(`MMCA.Common/.github/workflows/ci.yml:158`), so a discovery regression that silently drops thousands
of tests fails the build instead of reporting a green, empty run.

The takeaway for a new engineer: pick the tier that matches what you are proving (a fast unit test
Expand Down
2 changes: 1 addition & 1 deletion docs/onboarding/00-primer.html
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ <h3 id="the-decision-records-adrs-this-guide-tags">The decision records (ADRs) t
</tr>
<tr>
<td>035</td>
<td>Optimistic concurrency: a <code>RowVersion</code> token round-trips through <code>IConcurrencyAware</code> DTOs; a stale write maps to HTTP 412 Precondition Failed</td>
<td>Optimistic concurrency over HTTP preconditions: an <code>IConcurrencyAware</code> read DTO carries the <code>RowVersion</code> and the GET emits it as a weak <code>ETag</code>; a <code>[SupportsIfMatch]</code> write must echo it in <code>If-Match</code> (no precondition is 428, a stale token is 412 Precondition Failed)</td>
<td><a href="group-07-persistence-ef-core.html">g07</a>/<a href="group-12-api-hosting-mapping.html">g12</a></td>
</tr>
<tr>
Expand Down
7 changes: 4 additions & 3 deletions docs/onboarding/CONCEPT-MAPS.html
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,10 @@ <h2 id="7-modular-monolith--extractable-services-adr-006--007--008--012">7. Modu
class MONO,GW,SVC1,SVC2,SVC3,SVC4 dep</pre>
<hr>
<h2 id="8-persistence-database-per-service--polyglot-engines-adr-006--018--030">8. Persistence, database-per-service + polyglot engines (<a href="https://ivanball.github.io/docs/adr/006-database-per-service.html" target="_blank" rel="noopener">ADR-006</a> / 018 / 030)</h2>
<p>One concrete <code>SQLServerDbContext</code> over the abstract <code>ApplicationDbContext</code>, <strong>one instance per
database</strong>. Each entity is engine-agnostic; a single <code>[UseDataSource(engine)]</code> attribute on its
config class picks SQL Server, Cosmos, or SQLite. Cross-source relationships auto-degrade; the outbox
<p>One sealed context class per engine (<code>SQLServerDbContext</code>, <code>PostgreSQLDbContext</code>, <code>SqliteDbContext</code>,
<code>CosmosDbContext</code>) over the abstract <code>ApplicationDbContext</code>, <strong>one instance per database</strong>. Each
entity is engine-agnostic; a single <code>[UseDataSource(engine)]</code> attribute on its config class picks
SQL Server, PostgreSQL (<a href="https://ivanball.github.io/docs/adr/113-postgresql-as-a-first-class-engine.html" target="_blank" rel="noopener">ADR-113</a>), Cosmos, or SQLite. Cross-source relationships auto-degrade; the outbox
is the cross-source consistency mechanism. Each service self-applies its EF migrations at boot
(<a href="https://ivanball.github.io/docs/adr/030-startup-sole-migrator.html" target="_blank" rel="noopener">ADR-030</a>).</p>
<pre class="mermaid">flowchart TD
Expand Down
2 changes: 1 addition & 1 deletion docs/onboarding/devops-cicd.html
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ <h3 id="triggers-and-concurrency">Triggers and concurrency</h3>
static client secret is ever stored in GitHub. <code>packages: read</code> is needed for <code>GITHUB_TOKEN</code>-authenticated
NuGet restore of the MMCA.Common packages.</p>
<p><code>actions: read</code> is the least obvious of the four, and the comment above it says why<span class="cite"><span> (<code>deploy.yml:32-34</code>)</span></span>:
the three freshness gates read run history through the Actions API, <strong>and</strong> <code>e2e-gate</code> needs it here
the four freshness gates read run history through the Actions API, <strong>and</strong> <code>e2e-gate</code> needs it here
because a reusable workflow can never request more than its caller holds, so <code>e2e.yml</code>&#39;s own
skip-if-unchanged guard would die on &quot;Resource not accessible by integration&quot; if the caller did not grant
it. A <code>permissions:</code> block is a ceiling for every workflow it calls, not just for its own steps.</p>
Expand Down
6 changes: 4 additions & 2 deletions docs/onboarding/devops-testing.html
Original file line number Diff line number Diff line change
Expand Up @@ -805,8 +805,10 @@ <h3 id="test-type-totals">Test-type totals</h3>
demonstrates deliberate stratification rather than a single catch-all integration tier.</p>
<hr>
<h2 id="3-shipped-testing-infrastructure-packages">3. Shipped testing-infrastructure packages</h2>
<p>MMCA.Common ships <strong>five</strong> of its nineteen packages as testing infrastructure that downstream apps
consume as NuGet references rather than writing their own harness<span class="cite"><span> (<code>MMCA.Common/FACTS.md:19,35-39</code>)</span></span>:</p>
<p>MMCA.Common ships <strong>five</strong> of its twenty-two packages as general testing infrastructure that downstream
apps consume as NuGet references rather than writing their own harness (<span class="cite"><span><code>MMCA.Common/FACTS.md:19,38-42</code></span></span>;
a sixth, <code>MMCA.Common.AI.Testing</code>, is the language-model replay harness covered in
<a href="group-27-common-ai-integration.html">group-27</a>):</p>
<ul>
<li><code>MMCA.Common.Testing</code> (23 types), integration-test base, JWT generator, SQL fixture base, handler
scaffold, entity builders, and the eight runtime conformance bases (this section).</li>
Expand Down
Loading
Loading