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
23 changes: 21 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ implementation whose only job is to prove the conventions compile, run, and fail
| `src/tests/SharedTestingInfra` | `TestBuilder`; referenced automatically by every test project |
| `skills/` | Authored agent skills, installable with `npx skills add purview-dev/engineering-playbook` |
| `templates/` | Ready-to-copy starter files for a repository adopting the playbook |
| `samples/Acme.Persistence` | Standalone, runnable EF Core sample for the identifier conventions; run with `just samples` |
| `src/Directory.Build.props` | `NamespacePrefix`, the SDK import, and `NamespaceRemoveSuffix` |
| `.config/purview-build.json` | Pipeline configuration: solution, test discovery, filter |
| `.config/architecture-policy.json` | The dependency and naming policy the architecture tests read |
Expand Down Expand Up @@ -82,11 +83,11 @@ build-time `.agents/` mirror outright. The skills CLI clones the git repository,

| Skill | Answers |
| -- | -- |
| `skills/purview-engineering-principles/` | the ten repository conventions, what each prevents, and whether a build or a reviewer enforces it |
| `skills/purview-engineering-principles/` | the ten universal conventions, the conditional persistence rules, and whether a build or a reviewer enforces each |
| `skills/purview-architecture/` | how many projects, which layers, where validation lives |
| `skills/purview-architecture-testing/` | turning a boundary into a failing build |
| `skills/purview-testing-conventions/` | TUnit, AAA, `TestBuilder`, substitutes, tiers |
| `skills/purview-persistence/` | storage approach, EF Core abstraction, GUID key strategy |
| `skills/purview-persistence/` | storage approach, EF Core integration, well-known identifiers, GUID key strategy |
| `skills/purview-telemetry/` | logging, tracing, metrics, and when a project is "too small" for them |
| `skills/purview-org-harvest/` | which repository owns which convention, and how it is read at `main` before anything is adopted |

Expand Down Expand Up @@ -123,6 +124,23 @@ folder is installed with it. Name the other skill in prose instead.
no family, like `Bogus`, keeps its literal version inline.
- **A clean build has no warnings.**

## Persistence conventions

These apply only when the repository persists data; a stateless CLI or a pure library takes none of them.

- **A value known before storage is derived, not generated.** Use UUIDv5 over an installation-scoped
namespace and key it `ValueGeneratedNever()`, so a sequential convention never overwrites it. See
[Well-Known Identifiers](docs/wiki/Well-Known-Identifiers.md).
- **Every other GUID key uses one strategy per store.** SQL Server and PostgreSQL order GUIDs
differently, so the strategy is carried as context-options data and applied by a model-finalising
convention. See [GUID Key Strategy](docs/wiki/Guid-Key-Strategy.md).
- **EF Core is the boundary.** Use `DbContext` directly rather than a repository added for testability;
map value objects once; keep design-time and runtime options in step; apply migrations deliberately.
See [EF Core Integration](docs/wiki/EF-Core-Integration.md).
- **The runnable evidence is the sample.** `samples/Acme.Persistence` is outside `src/` and outside the
canonical solution; `just samples` builds, runs and self-checks it. Run it when you change the sample
or an identifier convention.

## Local commands

```text
Expand All @@ -132,6 +150,7 @@ just lint-check # CSharpier check
just lint-fix # CSharpier format
just org-inventory # drift between the organisation and .config/org-inventory.json
just org-inventory-write # regenerate the snapshot; commit the diff with the harvest
just samples # build and run the standalone persistence sample
just pipeline-pr # restore, build, lint, test through the shared Purview.Build tool
just scrub # reset bin/obj, forced restore, build-server shutdown
```
Expand Down
12 changes: 12 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ build *args:
echo "Building {{ BLUE }}{{ solution_file }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..."
dotnet build "{{ solution_file }}" --configuration "{{ build_configuration }}" {{ args }}

# Builds the standalone persistence sample
[group('Samples')]
samples-build *args:
echo "Building {{ BLUE }}samples/Acme.Persistence{{ NORMAL }}..."
dotnet build samples/Acme.Persistence/Acme.Persistence.csproj {{ args }}

# Runs the standalone persistence sample (builds it first)
[group('Samples')]
samples *args:
echo "Running {{ BLUE }}samples/Acme.Persistence{{ NORMAL }}..."
dotnet run --project samples/Acme.Persistence/Acme.Persistence.csproj -- {{ args }}

# Restore NuGet packages for the solution
[group('Build and Test')]
restore *args:
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ coding agents are enabled to work in a codebase without hand-holding.
| [Architecture as tests](docs/wiki/Architecture-As-Tests.md) | How does the build refuse a dependency that points the wrong way? |
| [Verification model](docs/wiki/Verification-Model.md) | Which checks run, in which tier, for which change? |
| [Contracts and data exchange](docs/wiki/Contracts-And-Data-Exchange.md) | How do value objects, schemas and results cross a boundary? |
| [Well-known identifiers](docs/wiki/Well-Known-Identifiers.md) | How is an identity derived so every process and restart agrees? |
| [GUID key strategy](docs/wiki/Guid-Key-Strategy.md) | How is a sequential GUID key chosen and applied per store? |
| [EF Core integration](docs/wiki/EF-Core-Integration.md) | How do the context, providers, value objects, models and migrations fit together? |
| [Testing and test data](docs/wiki/Testing-And-Test-Data.md) | How are tests written, and where does their data come from? |
| [Agent enablement](docs/wiki/Agent-Enablement.md) | How is a repository made legible to a coding agent? |
| [Agent skills distribution](docs/wiki/Agent-Skills-Distribution.md) | How is a skill published once and reach an agent? |
Expand All @@ -45,6 +48,7 @@ coding agents are enabled to work in a codebase without hand-holding.
| `docs/wiki` | The playbook. Aggregated by the purview-dev website. |
| `templates/` | Ready-to-copy starter files for a repository adopting the playbook. |
| `src/` | The reference implementation: a layered repository whose architecture rules are tests. |
| `samples/` | Runnable samples for conventions that `src/` cannot host; run with `just samples`. |
| `mkdocs.yml` | Wiki site configuration (`docs_dir: docs/wiki`). |
| `.config/purview-build.json` | Pipeline configuration for the shared Purview build workflow. |
| `Justfile` | Supported local workflow commands. |
Expand All @@ -56,6 +60,7 @@ coding agents are enabled to work in a codebase without hand-holding.
just build # dotnet build (Debug)
just test # run the unit tests
just lint-check # CSharpier check
just samples # build and run the standalone persistence sample
just pipeline-pr # restore, build, lint, test through the shared Purview.Build tool
just scrub # reset bin/obj, forced restore, build-server shutdown
```
Expand Down
15 changes: 15 additions & 0 deletions docs/wiki/Adopting-This-At-Your-Scale.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ adopt, and that decision is a review obligation.
| **Repository** | `src/src/<Project>`, one test project | `src/src/*` + `src/tests/*` + a shared testing project | As medium, plus `Sdk/` assets per package and `samples/` |
| **SDK** | Yes | Yes | Yes |
| **Test data builder** | Yes, from the first test | Yes | Yes |
| **Persistence identifiers** | `Guid.NewGuid()` or a store default; UUIDv5 only if a value must be known before storage | One provider's strategy, plus derived ids where seeding needs them | Two-provider strategy, derived catalogue, design-time parity |
| **Architecture tests** | No | Yes | Yes, and promoted into the build |
| **CI tiers** | One | Two (`Unit`, plus one slow tier) | Several, with a release gate |
| **Pack validation** | n/a | Only if it packs | Yes, exhaustively |
Expand Down Expand Up @@ -73,6 +74,9 @@ Everything from Small, plus:
Keep the fast tier always on; make the slow tier explicit.
- **A wiki page per public concern**, plus a getting-started page that is the shortest path to a working
example.
- **The EF Core integration rules as soon as persistence exists.** A `Persistence` project, value objects
mapped once, design-time and runtime options in step, and a real-store integration suite. See
[EF Core Integration](EF-Core-Integration.md).

This is the profile the reference implementation in this repository is built to. It is the smallest shape
where every mechanism in the playbook is visible.
Expand All @@ -88,6 +92,9 @@ Everything from Medium, plus:
catches the drift between a package's contract page and the package.
- **A release gate.** The slow tier runs before a release, not only on a pull request.
- **Release channels** rather than a second repository for prereleases.
- **The derived-identifier catalogue and the two-provider GUID strategy**, once a system tenant or a
second store appears. See [Well-Known Identifiers](Well-Known-Identifiers.md) and [GUID Key
Strategy](Guid-Key-Strategy.md).
- **Your own agent skills**, packaged so the guidance travels with the code.
- **Architecture rules promoted into the build.** At this size a rule that can be skipped is a rule that
will be, so move it from a test into an analyzer or an MSBuild check that runs before compile.
Expand All @@ -100,6 +107,11 @@ Four things that are more expensive than they look when taken out of order.
the web layer" is meaningless in a repository with one project, and it teaches the team that architecture
tests are ceremony.

**A GUID strategy before the second provider.** With one store and moderate write volume, a store default
or `Guid.NewGuid()` is enough. The strategy witness, options extension and model-finalising convention
earn their place when a second provider exists or the identifier must be known before `SaveChanges` —
not in anticipation. See [GUID Key Strategy](Guid-Key-Strategy.md).

**A test data builder after the tests.** Retrofitting `TestBuilder` means editing every test file at once,
which means a large diff nobody reviews carefully. Adopt it with the first test, when the diff is one file.

Expand Down Expand Up @@ -136,5 +148,8 @@ where it belongs. A migration that leaves the conventions undocumented is a migr
- [Getting Started](Getting-Started.md) — the same steps, in order, with the files inline
- [Templates](https://github.com/purview-dev/engineering-playbook/tree/main/templates) — the copy-paste versions
- [Engineering Principles](Engineering-Principles.md)
- [EF Core Integration](EF-Core-Integration.md)
- [Well-Known Identifiers](Well-Known-Identifiers.md)
- [GUID Key Strategy](Guid-Key-Strategy.md)
- [Worked Examples](Worked-Examples.md)
- [Contributing](Contributing.md)
8 changes: 6 additions & 2 deletions docs/wiki/Agent-Enablement.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ whose `description` is the trigger — it is what tells an agent that this skill
---
name: purview-persistence
description: >-
Use when a .NET project actually persists data and you are choosing a storage approach, deciding
whether to abstract EF Core, or selecting a GUID primary-key strategy.
Use when a .NET project actually persists data and you are choosing a storage approach, integrating
or abstracting EF Core, deriving a well-known identifier, or selecting a GUID primary-key strategy.
---
```

The persistence skill owns the whole persistence decision, not only the key: EF Core integration, the
derived-identifier gate and the provider-specific GUID strategy are one activation, because an agent that
is already answering "how do we store this" is the agent that must not answer only half of it.

Writing a good trigger is most of writing a good skill. "Use when X, and you need Y" is far more
useful than a topic label, because the agent is matching a situation, not searching an index.

Expand Down
Loading
Loading