Skip to content

Slice 0: the repository skeleton (D1.25) #167

Open

Description

@Hafeok

Step 0 of the order of work, D1.25,`` ratified in decision-driven-design at 799cbe84f2a010fb5b65a687eb253666948c73c8. It carries out ADR-0002, ADR-0003 and ADR-0004:

  • the eight layer projects, each with its public API baseline and a test that the layering analyzer refuses an upward reference;
  • the Native AOT publish of the host on every supported runtime in CI, with a smoke test of the published binary;
  • the package register: Varve, the analyzers, and the test-only packages.

It needs no sdlc: edition and adds no vocabulary, no node kind and no product behaviour.

The plan follows as a comment. No file is written until it is approved.

Activity

  1. Hafeok commented on Nov 3, 2026

    @Hafeok
    CollaboratorAuthor

    Plan: slice 0, the repository skeleton

    Everything below was read or resolved on 2026-11-03, 12:14 UTC. No file has been written.

    Preconditions: all hold

    Check Result
    #166 (D1.24) merged merged; main = 53bd54758ba71f7a810f91ddf3a6c83d5e653145, exactly. Tree: AGENTS.md, CLAUDE.md, README.md, LICENSE, NOTICE, docs/, releases/, .github/workflows/gates.yml, eng/gates/, nothing else
    docs/decisions/ ADR-0001 to ADR-0012, README.md and the collected file
    D1.25 in the definition decision-driven-design main = 799cbe84f2a010fb5b65a687eb253666948c73c8 ("D1.24 met, D1.25 adds step 0 …"), order of work item 0. This PR cites that commit.
    DD0001 applies to another family yes. At tag v0.1.0-preview.10 (a041cb2), DD0001 checks, for a project with ArchFamily=F and ArchLayer=n, every referenced assembly named F.*. The referenced layer is read from [assembly: ArchLayer] in its metadata. Nothing names Varve. F = ProductCli matches ProductCli.Shapes and not ProductCliX.
    DD0001 is AOT-neutral yes, as a package. The 0.1.0-preview.10 nupkg holds only analyzers/dotnet/cs/*.dll (netstandard2.0) and build/*.props/.targets, with developmentDependency=true and no lib/. One caveat, which ADR-0013 states (question 3): its generator emits internal attribute types (DecisionDriven.ArchLayerAttribute and the citation attributes) into every compilation, plus one [assembly: ArchLayer(n)]. Those types are compiled into our assemblies. They are plain attributes, with no reflection and no AOT effect.
    analyzer test harness none for consumers. The package's own tests use Microsoft.CodeAnalysis.Testing; nothing ships for us. See question 1 for how the probes compile.

    Varve, for the record: Varve.Rdf 0.1.0-preview.15 is tag 6cdb402 (2026-10-26). That is 120 commits before c0a776b, the commit the earlier canonicaliser checks ran against. No commit in that range touches src/Varve.Rdf/RdfCanonicaliser.cs or src/Varve.Turtle/. Deliverable 6 is the first check of the bytes that actually ship.

    Packages, resolved from nuget.org on 2026-11-03

    Package Version Adr= Scope= Referenced by Kind
    Varve.Rdf 0.1.0-preview.15 (latest) 0004 — ProductCli.Canonical runtime, MPL-2.0
    Varve.Iri 0.1.0-preview.15 0004 — transitive of Varve.Rdf, pinned so the register shows it runtime, MPL-2.0, no dependencies
    Varve.Turtle 0.1.0-preview.15 0004 tests/ ProductCli.Canonical.Tests only test-only
    xunit.v3 4.0.2 0004 tests/ every test project test-only, Apache-2.0; brings xunit.v3.mtp-v2 4.0.2 (Microsoft.Testing.Platform v2)
    CsCheck 5.0.1 0004 tests/ ProductCli.Host.Tests test-only, Apache-2.0, no dependencies
    DecisionDriven.Analyzers 0.1.0-preview.10 (latest) 0013 — every project, from Directory.Build.props, PrivateAssets="all" IncludeAssets="analyzers;build" build-time, MPL-2.0
    Microsoft.CodeAnalysis.PublicApiAnalyzers 5.6.0 (latest stable; 5.11.0-1.x is prerelease) 0013 — every library, PrivateAssets="all" build-time, MIT
    Microsoft.CodeAnalysis.BannedApiAnalyzers 5.6.0 (latest stable) 0013 — every project in src/, PrivateAssets="all" build-time, MIT

    Why Adr=0004 for the test stack and 0013 for the analyzers. ADR-0004 D2 admits Varve, and "the test framework and the property-testing library … once named in slice 0's plan". ADR-0013 names those two and records the reason. The analyzers are not in ADR-0004's register, so ADR-0013 admits them.

    Property testing: CsCheck, not FsCheck. CsCheck 5.0.1 has no dependencies and targets net10.0. FsCheck 3.6.0 brings FSharp.Core, a second language's runtime library in every test closure, for no capability we need. Varve chose the same, for the same reason, under its own ADR. I take the latest stable, 5.0.1, and not Varve's 4.9.1.

    Nothing else enters, and no TRX or hang-dump extension. nuget.config names one source, nuget.org, with source mapping.

    Deliverable 1: ADR-0013, build-time analyzers and the test stack

    docs/decisions/ADR-0013-build-time-analyzers-and-test-stack.md, in the adr-shape form:

    • Status: Proposed. See question 5 on who writes "Accepted".
    • D1, the test stack:
      • xunit v3 (xunit.v3) on Microsoft.Testing.Platform, selected in global.json;
      • CsCheck. Alternatives: FsCheck (rejected for FSharp.Core), and no property library (rejected: ADR-0004 D2 asks for one).
      • Both are test-only. Scope="tests/" is enforced, and no test project is published with AOT.
    • D2, the analyzers ADR-0002 D3 requires, with a reason for each:
      • DecisionDriven.Analyzers for layering (DD0001);
      • PublicApiAnalyzers for the baselines;
      • BannedApiAnalyzers, because the plan adopts one banned-symbol list (below).
    • D3, nothing ships. No package assembly reaches the binary: every analyzer reference is PrivateAssets="all", and the packages carry no lib/. The only bytes the analyzers add to our assemblies are the DD generator's internal attribute types and [assembly: ArchLayer(n)], stated as that.
    • D4, the register entry is the PackageVersion (question 4).
    • Consequences: every DD rule of the package is on as an error, not only DD0001 (question 2).

    Deliverable 2: the build policy (root files)

    File Content
    global.json sdk.version 10.0.401, rollForward latestFeature, allowPrerelease false; test.runner Microsoft.Testing.Platform
    nuget.config nuget.org only, with packageSourceMapping
    Directory.Build.props net10.0, LangVersion latest, Nullable enable, ImplicitUsings disable, TreatWarningsAsErrors, AnalysisLevel latest-recommended, EnforceCodeStyleInBuild, GenerateDocumentationFile, Deterministic, ContinuousIntegrationBuild under CI, IsPackable false. ArchFamily=ProductCli. The supported RIDs, as ProductCliRuntimeIdentifiers = linux-x64;linux-arm64;osx-arm64;win-x64 (ADR-0003 D5). Version 0.8.0-preview.2. The DD package reference.
    Directory.Build.targets Every src/ library: IsAotCompatible, IsTrimmable, the AOT, trim and single-file analyzers, PublicApi and BannedApi references, and the baseline files as AdditionalFiles. The checks below.
    Directory.Packages.props ManagePackageVersionsCentrally, CentralPackageTransitivePinningEnabled, the table above, one comment per entry giving its reason
    .editorconfig the IL2xxx and IL3xxx trim, AOT and single-file diagnostics at error, with nothing lowered; code style
    .globalconfig is_global = true, dd_require_layer = true: a ProductCli.* project with no layer is itself a DD0001 error
    .gitignore bin/, obj/, artifacts/
    ProductCli.slnx the 8 + 8 projects. The probe fixtures are not in it.
    eng/BannedSymbols.txt written here, citing ADR-0003 D4: System.Reflection.Emit; Type.GetType(string…); Activator.CreateInstance(Type…) and (string,string); Assembly.Load, LoadFrom and LoadFile. Nothing is copied from Varve's list or names its ADRs.

    The build's own checks, as MSBuild <Error>s in Directory.Build.targets. They run in every project's build, before compile:

    • PCLI0001: a PackageVersion has no Adr=.
    • PCLI0002: Adr="NNNN" names no docs/decisions/ADR-NNNN-*.md.
    • PCLI0003: a project references a package whose Scope= does not cover the project's path. xunit.v3 from src/ fails.
    • PCLI0004: a src/ library lacks PublicAPI.Shipped.txt or PublicAPI.Unshipped.txt. PublicApiAnalyzers is silent without the files, so the check is needed.
    • PCLI0005: ProductCli.Host is built with an empty SourceRevisionId. It never prints a version without a commit.

    No NoWarn, #pragma or SuppressMessage anywhere. Varve's .editorconfig sets CA1716 and CA1720 to none. I do not copy that. If a rule fires on this skeleton, the code changes.

    Deliverable 3: the eight projects

    Each project references only the one directly below it, so the chain is ADR-0002 D1's diagram; plus Varve and the BCL.

    Project ArchLayer References Content
    src/ProductCli.Vocabulary 0 — .csproj, PublicAPI.Shipped.txt, PublicAPI.Unshipped.txt
    src/ProductCli.Canonical 1 Vocabulary, Varve.Rdf same
    src/ProductCli.Shapes 2 Canonical same
    src/ProductCli.Draft 3 Shapes same
    src/ProductCli.Simulation 4 Draft same
    src/ProductCli.Runner 5 Simulation same
    src/ProductCli.Session 6 Runner same
    src/ProductCli.Host 7, ArchCompositionRoot=true Session .csproj (OutputType Exe, AssemblyName product, PublishAot, InvariantGlobalization), Program.cs, Verbs.cs
    • Each baseline holds #nullable enable only. No library has a public member in this slice, and deliverable 5 needs none: --version lives in the host.
    • The host is an executable, not a library, so it has no baseline.
    • Not created: ProductCli.Vocabulary.Generator, ProductCli.Workbench, ProductCli.Workbench.Contracts.

    Test projects: tests/ProductCli.<Layer>.Tests/, one per project. Each references its own layer and xunit.v3, and is unlayered: DD0001 skips *.Tests.

    Deliverable 4: the layering tests

    Probe: tests/fixtures/layering/ProductCli.Probe.csproj and Probe.cs, an empty internal class. It is not in the solution. It inherits Directory.Build.props and .targets, so it compiles under the real wiring, analyzer included. ArchLayer and the referenced project are passed as properties.

    Harness: tests/ProbeBuild.cs, linked into each test project. It runs dotnet build on the probe with:

    • --no-dependencies -p:RestoreRecursive=false, so it uses the solution's existing outputs and writes nothing under src/. The eight projects can then run probes in parallel.
    • its own obj and bin in a temp directory.

    It returns the compiler's output bytes and exit code, and the assertions read those.

    Test Lives in Probe Observes
    upward reference refused, ×7 ProductCli.<L>.Tests, L = 0 to 6 ArchLayer=L referencing the layer L+1 project exit ≠ 0, and the output holds error DD0001: 'ProductCli.Probe' is at layer L and references 'ProductCli.<L+1>', which is at layer L+1
    layer 7, refused ProductCli.Host.Tests ArchLayer=7 referencing ProductCli.Host. Nothing sits above 7, so its refusal is the same-layer case exit ≠ 0, error DD0001 … which is at layer 7
    downward reference compiles ProductCli.Canonical.Tests ArchLayer=1 referencing ProductCli.Vocabulary exit 0, and no DD0001 in the output
    unplaced project refused ProductCli.Host.Tests no ArchLayer (dd_require_layer) exit ≠ 0, error DD0001 naming the probe
    layers are the table ProductCli.Host.Tests none reads [assembly: ArchLayer(n)] from each built ProductCli.*.dll with System.Reflection.Metadata, and compares it with ADR-0002's table

    Deliverable 5: the host, and the AOT gate

    product --version. It writes the line below to stdout, as one Write with \n so the bytes match on Windows too, and exits 0:

    product 0.8.0-preview.2 (commit <40-hex SourceRevisionId>)\n
    

    Both values are read from AssemblyInformationalVersionAttribute and an AssemblyMetadata("SourceCommit", …) stamped at build. That read uses GetCustomAttribute, which raises no trim or AOT diagnostic.

    Every other argument, and no argument, exits 2. It writes one line to stderr and nothing to stdout:

    • a verb of chapter 3: product: '<verb>' arrives in step <n> of the order of work (D1.25);
    • anything else: product: '<arg>' is not a verb.

    The verb table is my reading of the order of work. Please confirm it (question 6):

    Verb Step
    init, product, domain 4
    proposal, view, mcp 5
    decider, projector, primitive 6
    scenario, conformance 7
    question 8
    diff 4 (draft positions arrive with nodes)

    domain export arrives with domain, at step 4. Its exporters are step 2, but step 2 adds no verb.

    Runtime identifiers: all four can be built. GitHub hosts each OS and this repository is public:

    RID Runner
    linux-x64 ubuntu-24.04
    linux-arm64 ubuntu-24.04-arm, as Varve uses
    osx-arm64 macos-15
    win-x64 windows-2025

    Native AOT does not cross OSes, so each publishes natively.

    CI. New jobs in gates.yml. Actions are pinned by SHA: actions/setup-dotnet v6.1.0 = 0f4a354dd5547a28c70b7853dbd95d5b67886e73, with global-json-file: global.json. Checkout uses the PR head SHA, so the stamped commit is the PR head and not GitHub's merge commit.

    Job Runner Does
    build-test ubuntu-24.04 dotnet build ProductCli.slnx -c Release (warnings are errors), then dotnet test -c Release --no-build
    aot (linux-x64) ubuntu-24.04 dotnet publish src/ProductCli.Host -c Release -r linux-x64; eng/gates/smoke.sh; prints the binary's size
    aot (linux-arm64) ubuntu-24.04-arm same, -r linux-arm64
    aot (osx-arm64) macos-15 same, -r osx-arm64
    aot (win-x64) windows-2025 same, -r win-x64, under shell: bash

    eng/gates/smoke.sh <binary> <version> <commit>, a new gate script:

    • it runs the binary with --version, captures stdout to a file, and cmps it byte for byte with the expected line;
    • it requires exit 0 and empty stderr;
    • it then runs <binary> domain and requires exit 2 and empty stdout.

    selftest gains plants for it, each a stub binary: a wrong version, a wrong commit, a CRLF line end, exit 1, and output on stderr.

    The new jobs must be added to main's required checks. That is yours to do.

    Deliverable 6: the Varve admission test

    • tests/fixtures/ddd/acme-shop.ttl and tests/fixtures/ddd/vectors.json, from decision-driven-design 799cbe84f2a010fb5b65a687eb253666948c73c8. The paths there are applications/sdlc/what/examples/acme-shop.ttl and applications/sdlc/what/vectors.json. Both files were last changed in 0c94c32.
      • sha256 4018cb9ad45c13fceafd7e585649777e7c8b9e7231fab46a42eb3286df91b777 (acme-shop.ttl)
      • sha256 953666f42051e50632290f73200298c9b5d518d2a153c20a31f9589f94374a1d (vectors.json)
    • tests/fixtures/ddd/SOURCE.md states the commit, both paths and both hashes.
    • tests/ProductCli.Canonical.Tests/VarveAdmissionTests.cs:
      • it checks both files' sha256 against SOURCE.md;
      • it parses the Turtle with Varve.Turtle and canonicalises it with RdfCanonicaliser.Canonicalise;
      • it asserts that the sha256 of the canonical N-Quads bytes is vectors.json's graphHash, read from the file. The expected value is sha256:10cd90893f7fadb1bdf80064a43795be95e2175684c6d3a93a7736abc7c2156b.
    • If it fails, I stop and file a Varve issue first (ADR-0002 D5).

    Host tests (ProductCli.Host.Tests)

    Each runs the built product executable as a process and reads its stdout and stderr bytes (ADR-0006):

    • --version: stdout equals product <v> (commit <c>)\n byte for byte. <v> is the Version the test project is stamped with, from the same props. <c> is git rev-parse HEAD, run by the test.
    • Verbs: each chapter-3 verb gives exit 2, empty stdout, and exactly its line on stderr.
    • Property, CsCheck, 100 cases: for any argument vector other than exactly ["--version"] (the empty vector included), the result is exit 2, empty stdout, and exactly one line on stderr.

    The no-toolchain rule

    Strictly, today's check would pass this PR. It lists only Rust, Node and Python files, and checks only the root. I still change it, because after this PR the rule should be "exactly one toolchain, .NET, pinned".

    The exact rule that replaces it (eng/gates/no-toolchain.sh):

    The only toolchain is .NET, pinned at the root. The check fails when:

    1. a Rust, Node or Python toolchain file from today's list, or a requirements*.txt, is anywhere in the tree outside .git/, bin/ and obj/, not only at the root;
    2. the root has no global.json, or its sdk.version is not 10.0.401, its sdk.rollForward is not latestFeature, or its test.runner is not Microsoft.Testing.Platform;
    3. a global.json exists below the root.
    • The job keeps its name, no-toolchain, so any required-check setting still matches. Its step title changes to "The only toolchain is .NET, pinned in global.json".
    • selftest gains plants: package.json under src/, no global.json, a wrong SDK version, a wrong rollForward, and a second global.json in tests/. Its clean case gets a global.json.

    Planted failures

    Each is a commit on the PR branch, pushed alone. Its red run is linked in the PR, then the next commit removes it:

    1. a PackageVersion with no Adr= → PCLI0001;
    2. a PackageVersion citing Adr="0099" → PCLI0002;
    3. xunit.v3 referenced from src/ProductCli.Draft → PCLI0003;
    4. JsonSerializer.Serialize(object) (reflection-based) in ProductCli.Shapes → IL2026 and IL3050 as errors in build-test.

    The layering plants are permanent: they are the tests in deliverable 4. See question 7 on making 1 to 4 permanent too.

    Deliverable 7: updates

    • AGENTS.md:
      • build, test and publish commands;
      • the layer table;
      • the new jobs in the gates table;
      • the rule that a new package needs an ADR and a PackageVersion with Adr= (and Scope= if test-only);
      • "There is no source code yet" goes.
    • README.md: build and test instructions.
    • releases/v0.8.0-preview.2.yaml:
      • basis: issue 167, decision-driven-design 799cbe8, Varve.Rdf 0.1.0-preview.15, ADR-0002, 0003, 0004 and 0013;
      • the AOT sizes per RID;
      • accepted-by left for you.
    • docs/traceability/2026-11-03-issue-167-slice-0.md, in the shape of D1.24: remove the Rust tree from main #165's record.

    Commits (one logical change each, signed, Refs #167, DCO)

    1. docs(decisions): ADR-0013, build-time analyzers and the test stack
    2. build: the build policy and the package register
    3. ci(gates): no-toolchain allows exactly one toolchain, .NET, with its selftest plants
    4. feat: the seven layer libraries, with their API baselines
    5. feat(host): product --version, and exit 2 for every verb not yet built
    6. test: the layering probes, one per layer
    7. test(canonical): the Varve admission test, with the vendored example
    8. test(host): the version bytes, the verb messages, the argument property
    9. ci(gates): build-test and the AOT publish and smoke matrix
    10. to 17. the four plants and their removals
    11. docs: AGENTS.md and README for the build
    12. releases: v0.8.0-preview.2, the repository skeleton
    13. docs(traceability): the session record for #167

    Questions before I start

    1. The probes compile under dotnet build, not in-process Roslyn. Without a harness, the prompt says "compile the probes with Roslyn in the test". dotnet build is Roslyn, but through MSBuild. I recommend it:

      • it observes the real wiring (Directory.Build.*, .globalconfig, the package's build/ props);
      • in-process compilation would need Microsoft.CodeAnalysis.CSharp as one more test package, and hand-built analyzer config to imitate MSBuild.

      The cost is about 2 to 4 s per probe. Agree?

    2. Adopting DecisionDriven.Analyzers turns on all its rules, not only DD0001. That includes DD0002 to DD0023: [Contract] on public interfaces (DD0009), no naked primitives on contracts (DD0013), no mutable statics (DD0004), and more. They can't be lowered without a suppression, which DD0008 and ADR-0007 D6 forbid. None fires on this skeleton. The first public interface will need a decision set in the package's front-matter format, beside the ADRs. That is a later slice's question, but ADR-0013 states it as a consequence. Accept?

    3. "None ships in the binary". ADR-0013 says that no analyzer assembly ships. It also says that the DD generator adds internal attribute types and one assembly attribute to each of our assemblies. Is that wording acceptable?

    4. The register entry is the PackageVersion. ADR-0004 D1 says "every PackageReference carries Adr=", but under central versions the register is Directory.Packages.props, Varve's form, which D1 adopts. I recommend enforcing Adr= on PackageVersion only, with ADR-0013 D4 saying so, and AGENTS.md's wording following. The alternative is to require it on both. Which?

    5. ADR-0013's status. A session never writes an acceptance. I commit it as Proposed. Do you change it to Accepted (date) in your own commit, or should I write it, quoting your approval comment?

    6. The verb-to-step table above. Is it right?

    7. Plants 1 to 4 as permanent tests. They are shown red and then removed, as the brief says. I'd also keep them as permanent probe fixtures, built by a BuildPolicyTests in ProductCli.Host.Tests, so every run re-proves them, as selftest does for the gates. It costs four more probe builds. Yes or no?

    8. The version. 0.8.0-preview.2, after the descriptor v0.8.0-preview.1. Agree?


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Effort

    None yet

    Progress

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions