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
49 changes: 49 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# The style and analyzer severities of the build (ADR-0002 D3, ADR-0003 D2).
# Nothing here lowers a rule's severity (ADR-0007 D6).
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

[*.{csproj,props,targets,slnx,config,json,yml,yaml}]
indent_size = 2

[*.md]
trim_trailing_whitespace = false

[*.cs]
# Usings are explicit (ImplicitUsings disable), and an unused one is an error.
dotnet_diagnostic.IDE0005.severity = warning
csharp_style_namespace_declarations = file_scoped:warning
dotnet_diagnostic.IDE0161.severity = warning
csharp_using_directive_placement = outside_namespace:warning
dotnet_sort_system_directives_first = true

# ADR-0003 D2: the trim, AOT and single-file analyzers' diagnostics are
# errors, whatever their default severity.
dotnet_diagnostic.IL2026.severity = error
dotnet_diagnostic.IL2046.severity = error
dotnet_diagnostic.IL2050.severity = error
dotnet_diagnostic.IL2055.severity = error
dotnet_diagnostic.IL2057.severity = error
dotnet_diagnostic.IL2058.severity = error
dotnet_diagnostic.IL2059.severity = error
dotnet_diagnostic.IL2060.severity = error
dotnet_diagnostic.IL2062.severity = error
dotnet_diagnostic.IL2067.severity = error
dotnet_diagnostic.IL2070.severity = error
dotnet_diagnostic.IL2072.severity = error
dotnet_diagnostic.IL2075.severity = error
dotnet_diagnostic.IL2087.severity = error
dotnet_diagnostic.IL2091.severity = error
dotnet_diagnostic.IL3000.severity = error
dotnet_diagnostic.IL3001.severity = error
dotnet_diagnostic.IL3002.severity = error
dotnet_diagnostic.IL3050.severity = error
dotnet_diagnostic.IL3051.severity = error
dotnet_diagnostic.IL3053.severity = error
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Line feeds in every checkout, Windows included: the gate scripts run under
# bash on every runner, and a build's bytes do not depend on the platform.
* text=auto eol=lf

# The vendored fixtures are compared by sha256, so git never touches their bytes.
tests/fixtures/ddd/** -text
80 changes: 79 additions & 1 deletion .github/workflows/gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ concurrency:
group: gates-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
DOTNET_NOLOGO: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"

jobs:
commits:
name: commits
Expand Down Expand Up @@ -63,7 +67,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: No Rust, Node or Python toolchain file at the root
- name: The only toolchain is .NET, pinned in global.json
run: eng/gates/no-toolchain.sh .

selftest:
Expand All @@ -73,3 +77,77 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Each check fails on its plant and passes its clean case
run: eng/gates/selftest.sh

# The .NET build (ADR-0002, ADR-0003, ADR-0013). Checked out at the pull
# request's head, not GitHub's merge commit, so the commit the host stamps
# is the head that is reviewed.
build-test:
name: build-test
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0
with:
global-json-file: global.json
- name: Build, warnings as errors
run: dotnet build ProductCli.slnx -c Release
- name: Test, the layering and build-policy probes included
run: dotnet test -c Release --no-build

# ADR-0003 D3, D5: the Native AOT publish of the host on every supported
# runtime identifier, each on a runner of its own operating system (Native
# AOT does not cross-compile between them), and the smoke test of the
# published binary.
aot:
name: aot (${{ matrix.rid }})
strategy:
fail-fast: false
matrix:
include:
- rid: linux-x64
os: ubuntu-24.04
- rid: linux-arm64
os: ubuntu-24.04-arm
- rid: osx-arm64
os: macos-15
- rid: win-x64
os: windows-2025
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0
with:
global-json-file: global.json
- name: The runtime identifier is one the build policy supports
env:
RID: ${{ matrix.rid }}
run: |
supported=$(dotnet msbuild src/ProductCli.Host/ProductCli.Host.csproj -getProperty:ProductCliRuntimeIdentifiers)
echo "supported: $supported"
case ";$supported;" in
*";$RID;"*) ;;
*) echo "aot: $RID is not in ProductCliRuntimeIdentifiers"; exit 1 ;;
esac
- name: Publish, Native AOT
env:
RID: ${{ matrix.rid }}
run: dotnet publish src/ProductCli.Host/ProductCli.Host.csproj -c Release -r "$RID" -o "artifacts/publish/$RID"
- name: Smoke test of the published binary
env:
RID: ${{ matrix.rid }}
run: |
ext=""
if [ "$RID" = win-x64 ]; then ext=.exe; fi
binary="artifacts/publish/$RID/product$ext"
version=$(dotnet msbuild src/ProductCli.Host/ProductCli.Host.csproj -getProperty:Version)
eng/gates/smoke.sh "$binary" "$version" "$(git rev-parse HEAD)"
size=$(wc -c < "$binary" | tr -d ' ')
echo "aot: $RID: $binary is $size bytes"
echo "| $RID | \`product$ext\` | $size bytes |" >> "$GITHUB_STEP_SUMMARY"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
artifacts/
*.user
.vs/
6 changes: 6 additions & 0 deletions .globalconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Analyzer options for the whole compilation (ADR-0002 D3, ADR-0013 D2).
is_global = true

# DD0001: a ProductCli.* project that declares no ArchLayer is itself an error,
# so no project can stay unplaced in the layer table.
dd_require_layer = true
94 changes: 86 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,85 @@ ADRs and its tests**, and its release descriptors (D1.24).
- `docs/traceability/` holds one record per agent session (ADR-0007 D2).
- `releases/` holds the release descriptors (ADR-0007 D3).
- `eng/gates/` holds the checks `.github/workflows/gates.yml` runs.
- `src/` holds the eight layer projects, `tests/` their tests and the probes
under `tests/fixtures/` (slice 0, D1.25, #167).

There is no source code yet. Slice 0 (ADR-0002, ADR-0003, ADR-0004) brings
the eight layer projects, the layering analyzer, the AOT publish gate and the
package register, in its own pull request.
## Build, test, publish

The SDK is pinned in `global.json` (`10.0.401`, `rollForward: latestFeature`),
with Microsoft.Testing.Platform as the test runner. Warnings are errors.

```bash
dotnet build ProductCli.slnx -c Release
```

```bash
dotnet test -c Release --no-build
```

```bash
dotnet publish src/ProductCli.Host/ProductCli.Host.csproj -c Release -r linux-x64 -o artifacts/publish/linux-x64
```

```bash
eng/gates/smoke.sh artifacts/publish/linux-x64/product "$(dotnet msbuild src/ProductCli.Host/ProductCli.Host.csproj -getProperty:Version)" "$(git rev-parse HEAD)"
```

- **Build the solution before testing.** The layering and build-policy probes
are built by the tests with `dotnet build`, against the solution's outputs.
- **Native AOT publishes on its own operating system only.** The supported
runtime identifiers are `ProductCliRuntimeIdentifiers` in
`Directory.Build.props`: `linux-x64`, `linux-arm64`, `osx-arm64` and
`win-x64` (ADR-0003 D5). CI publishes each one on its own runner.
- **`product --version`** is the host's one behaviour in slice 0. Every other
argument exits 2 with one line on stderr naming the step of the order of
work its verb arrives in. **That line is not a format**: it is for a person,
nothing may parse it, and it changes as verbs are built.

## The layers

ADR-0002 D1. Each project references only the one below it, Varve and the
BCL. `ArchLayer` is set in each project file, `ArchFamily` is `ProductCli`,
and DD0001 (`DecisionDriven.Analyzers`, ADR-0013) fails the build on a
reference that is not strictly downward, and on a `ProductCli.*` project with
no layer (`.globalconfig`).

| Layer | Project | Holds |
|---|---|---|
| 0 | `ProductCli.Vocabulary` | the generated node records, the `sdlc:` terms, the edition |
| 1 | `ProductCli.Canonical` | the RDFC graph, the exporters, the `dpp/1` record; references `Varve.Rdf` |
| 2 | `ProductCli.Shapes` | the shape-engine contract, over Varve |
| 3 | `ProductCli.Draft` | the draft-store contract, over Varve; DPP commits |
| 4 | `ProductCli.Simulation` | derivation, rule evaluation, findings |
| 5 | `ProductCli.Runner` | the wire protocol, conformance |
| 6 | `ProductCli.Session` | the verbs, the phase gate |
| 7 | `ProductCli.Host` | the composition root and the only executable, `product` (Native AOT) |

Every library is `IsAotCompatible` and `IsTrimmable`, with the AOT and trim
analyzers' diagnostics as errors (ADR-0003 D2), and has a public API baseline,
`PublicAPI.Shipped.txt` and `PublicAPI.Unshipped.txt`, beside its project
file (ADR-0002 D3). A new public member is a line in that diff.

`ProductCli.Vocabulary.Generator`, `ProductCli.Workbench` and
`ProductCli.Workbench.Contracts` do not exist yet: each arrives with the slice
that needs it.

## Packages

`Directory.Packages.props` is the register (ADR-0004, as amended by
ADR-0013 D4). Versions are central: no project file carries one.

- **A new package needs an ADR**, and a `PackageVersion` entry whose `Adr=`
names it. A test-only entry also carries `Scope="tests/"`.
- **The build fails** on an entry without `Adr=` (`PCLI0001`), on an `Adr=`
naming no file in `docs/decisions/` (`PCLI0002`), and on a reference from
outside an entry's `Scope=` (`PCLI0003`). `BuildPolicyTests` builds each
failing case on every run.
- **Versions are resolved from nuget.org**, never from memory, and the date is
recorded in the commit that adds or changes them.
- Every `DecisionDriven.Analyzers` rule is on, as an error. The first public
interface needs a decision set in that package's format, so that
`[Contract]` can cite it (ADR-0013, Consequences).

**The Rust workspace is frozen at the tag `rust-final`** (commit `70395df`).
Nothing at the tag changes. Anything the rewrite reads from the Rust tool — the
Expand Down Expand Up @@ -56,8 +131,8 @@ interim rule of Writ ADR 0008); `docs/decisions/README.md` states the rule.
were.
- ADR-0001 to ADR-0012 are held byte for byte against
`docs/decisions/collected-2026-11-03.md`; `adr-text` checks it.
- Every `PackageReference` carries `Adr=` naming the ADR that admits it
(ADR-0004), from slice 0.
- Every `PackageVersion` in `Directory.Packages.props` carries `Adr=` naming
the ADR that admits it (ADR-0004 D1, as amended by ADR-0013 D4).

## Gates

Expand All @@ -70,8 +145,10 @@ is a Bash script in `eng/gates/` and runs locally the same way:
| `signatures` | `eng/gates/signatures.sh <commits.json>` | GitHub does not report a commit of the pull request as verified |
| `adr-shape` | `eng/gates/adr-shape.sh [dir]` | an ADR lacks its number, status, alternatives or consequences, or an unexpected file is in `docs/decisions/` |
| `adr-text` | `eng/gates/adr-text.sh [dir]` | ADR-0001 to ADR-0012 differ by a byte from their sections of the collected file, with the Draft 4 notes |
| `no-toolchain` | `eng/gates/no-toolchain.sh [root]` | a Rust, Node or Python toolchain file is at the root |
| `selftest` | `eng/gates/selftest.sh` | any check above passes on the case planted for it |
| `no-toolchain` | `eng/gates/no-toolchain.sh [root]` | a Rust, Node or Python toolchain file is anywhere in the tree, or `global.json` is missing, pins another SDK, roll-forward or test runner, or has a second copy below the root |
| `selftest` | `eng/gates/selftest.sh` | any check above, or `smoke.sh`, passes on the case planted for it |
| `build-test` | `dotnet build` then `dotnet test`, both `-c Release` | the build has a warning or error, or a test fails |
| `aot (<rid>)` | `dotnet publish -r <rid>`, then `eng/gates/smoke.sh <binary> <version> <commit>` | the Native AOT publish fails on that runtime's own runner, or the binary's `--version` bytes, its exit code, or its refusal of a verb are not as required |

`selftest` plants a failing case for every check in a scratch repository on
the runner, so each run shows that each check still fails when it should.
Expand Down Expand Up @@ -145,7 +222,8 @@ verb's result is rejected in review.
- **A suppression of any rule** (ADR-0007 D6).
- **Copied Rust code.** The Rust tool is read at `rust-final` for its
behaviour, and its bytes are never carried.
- **A package without `Adr=`**, or a version taken from memory (ADR-0004).
- **A package without `Adr=`**, or a version taken from memory (ADR-0004, as
amended by ADR-0013).
- **A commit** unsigned, without `Refs #N` or a DCO sign-off, or naming a model.
- **Rewriting or deleting a tag or a branch**, force-pushing `main`, or
changing anything at `rust-final`.
Expand Down
91 changes: 91 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<Project>

<!--
The build policy (ADR-0002, ADR-0003, ADR-0004 as amended by ADR-0013).
Imported before every project body. Directory.Build.targets, imported
after it, applies what depends on the project's own settings, and holds
the checks that fail the build.
-->

<PropertyGroup>
<ProductCliRoot>$([MSBuild]::EnsureTrailingSlash('$(MSBuildThisFileDirectory)'))</ProductCliRoot>
</PropertyGroup>

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<!-- A file's dependencies are visible in the file. -->
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>

<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<!-- Two builds of one commit produce the same bytes. -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' != ''">true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

<!--
The analyzer configuration is the root's .editorconfig and .globalconfig,
named here for every project. The SDK's discovery finds them only above a
project's source files, at evaluation, and a library with no source yet
(every library of slice 0) has none, so it would build without them:
without dd_require_layer, and without the IL severities. Discovery is off,
so that a project with sources does not get the same file twice.
-->
<PropertyGroup>
<DiscoverEditorConfigFiles>false</DiscoverEditorConfigFiles>
<DiscoverGlobalAnalyzerConfigFiles>false</DiscoverGlobalAnalyzerConfigFiles>
</PropertyGroup>

<ItemGroup>
<EditorConfigFiles Include="$(ProductCliRoot).editorconfig" />
<GlobalAnalyzerConfigFiles Include="$(ProductCliRoot).globalconfig" />
<EditorConfigFiles Include="$(ProductCliRoot).globalconfig" />
</ItemGroup>

<PropertyGroup>
<!-- Nothing is a NuGet package. The product ships as one binary (ADR-0003 D1). -->
<IsPackable>false</IsPackable>
</PropertyGroup>

<PropertyGroup>
<!-- The product version, which the host prints for its version flag, and the version
of the release descriptor this build belongs to (releases/). -->
<Version>0.8.0-preview.2</Version>
<!-- The source commit is stamped as its own assembly metadata, so the
informational version stays the bare version. -->
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>

<PropertyGroup>
<!-- ADR-0003 D5: the supported runtime identifiers. CI publishes the host
natively for each one, on a runner of its operating system. -->
<ProductCliRuntimeIdentifiers>linux-x64;linux-arm64;osx-arm64;win-x64</ProductCliRuntimeIdentifiers>
</PropertyGroup>

<!--
ADR-0002 D3, ADR-0013 D2: the layering rule. ArchFamily is the family of
every ProductCli.* assembly; ArchLayer is set in each project file, from
ADR-0002 D1's table. .globalconfig sets dd_require_layer, so a ProductCli
project with no layer fails. Build-time only: PrivateAssets keeps the
package out of every output, and IncludeAssets takes its analyzers and its
build props and targets and nothing else (ADR-0013 D3).
-->
<PropertyGroup>
<ArchFamily>ProductCli</ArchFamily>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DecisionDriven.Analyzers" PrivateAssets="all" IncludeAssets="analyzers;build" />
</ItemGroup>

</Project>
Loading
Loading