@cratis/pi 7.103.5 → 7.103.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. package/package/corpus/harnesses/copilot/instructions/local-work-artifacts.instructions.md +7 -7
  2. package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +7 -5
  3. package/package/corpus/hooks/scripts/cratis-cleanup-worktrees.sh +13 -5
  4. package/package/corpus/rules/local-work-artifacts.md +7 -7
  5. package/package/corpus/rules/specs.csharp.md +7 -5
  6. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/SKILL.md +7 -3
  7. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dependency-map.md +31 -14
  8. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dotnet-nuget.md +3 -0
  9. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/npm-yarn.md +6 -4
  10. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/train-mode.md +43 -0
  11. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/worked-examples.md +5 -1
  12. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/cratis-local-packages.sh +1065 -60
  13. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/dependency-map.tsv +1 -1
  14. package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +20 -13
  15. package/package/corpus/skills/cratis-specifications-csharp/references/running-specs.md +90 -0
  16. package/package/corpus/skills/cratis-specifications-csharp/references/shared-lifecycle.md +55 -23
  17. package/package/corpus/skills/cratis-specifications-csharp/verification.json +5 -3
  18. package/package/corpus/skills/filter-syntax/SKILL.md +3 -1
  19. package/package/corpus/skills/run-tests/SKILL.md +1 -1
  20. package/package.json +1 -1
@@ -17,8 +17,9 @@ dumps, and similar coordination files. These are **work records, not documentati
17
17
  If you find one tracked in git, move it into `.ai-work/` and remove it from
18
18
  tracking in a dedicated commit.
19
19
  - A genuine follow-up that must survive the session is **not** a work record — suggest
20
- opening a GitHub issue for it (or open one when asked) so future work is tracked
21
- where everyone can see it, instead of leaving a planning file behind.
20
+ opening a GitHub issue for it so future work is tracked
21
+ where everyone can see it, instead of leaving a planning file behind. A follow-up
22
+ issue includes an `## Acceptance criteria` section of concrete, testable bullets.
22
23
  - Knowledge that must outlive the session (real documentation, ADRs, operator
23
24
  guides) is written deliberately into the repository's documentation structure
24
25
  through normal review — not left behind as a work record.
@@ -37,16 +38,15 @@ dumps, and similar coordination files. These are **work records, not documentati
37
38
  replace the prompt.
38
39
  - **A worktree is a work record too.** Create it outside the repository tree, for
39
40
  example in a sibling `../.worktrees/` directory. A nested worktree inherits the
40
- parent's `.globalconfig` alongside its own, causing .NET's `MultipleGlobalAnalyzerKeys`
41
+ parent's `.globalconfig`, causing .NET's `MultipleGlobalAnalyzerKeys`
41
42
  errors or silently changing analyzer severities. **Delivered work is cleaned up by
42
43
  `.cratis/ai/hooks/scripts/cratis-cleanup-worktrees.sh`, not by memory:** run it from the
43
44
  main checkout when a pull request merges or you finish a task (hooks also run it after
44
45
  `gh pr merge` and at session start where the harness supports them, and **ship-changes**
45
46
  ends with it). It removes a worktree and its branch only on evidence the work reached the
46
- default branch — pushed is not merged — and never forces. Worktrees with non-empty `.ai-work/`
47
- directories are kept by default as "has untracked work records (.ai-work/)". Manual
48
- `--include-work-records` permits deleting those records after you preserve what you need;
49
- automatic hooks never opt in. Pin a long-lived worktree with `git worktree lock <path>`;
47
+ default branch — pushed is not merged — and never forces. It keeps worktrees with non-empty
48
+ `.ai-work/` directories; manual `--include-work-records` deletes those records after you
49
+ preserve what you need, and automatic hooks never opt in. Pin a long-lived worktree with `git worktree lock <path>`;
50
50
  locked worktrees are always kept, even with opt-in. Other ignored files such as `.env` files
51
51
  and local databases can still be deleted with a removed worktree, so move anything you
52
52
  still need out first. Do not hand-remove worktrees or `git branch -D` a delivered branch
@@ -18,9 +18,9 @@ Extends the base [specs.md](./specs.instructions.md) (folder structure, naming,
18
18
 
19
19
  ## Frameworks
20
20
 
21
- - [xUnit](https://xunit.net/) **2.x** for execution (not v3).
22
- - Reference `Cratis.Specifications.XUnit` **4.1.1** for the `Specification` base, convention lifecycle and `ShouldXxx` assertions; the namespace remains `Cratis.Specifications`. Its `Cratis.Specifications` dependency supplies `Catch` and `SpecificationMethods`, not the base or assertions.
23
- - 4.x targets net8.0/net9.0/net10.0; net48 requires 3.x. `Cratis.Specifications.NUnit` is an alternative with `[Test]` and public test methods; xUnit remains the house default.
21
+ - [xUnit v3](https://xunit.net/docs/getting-started/v3/migration) is the house default for new and migrated spec projects: reference `Cratis.Specifications.XUnit.v3` **5.0** with `xunit.v3.mtp-off`, `Microsoft.NET.Test.Sdk` and `xunit.runner.visualstudio`, and set `<OutputType>Exe</OutputType>` in the project. The package supplies the `Specification` base, convention lifecycle and `ShouldXxx` assertions; the namespace remains `Cratis.Specifications`. Its `Cratis.Specifications` dependency supplies `Catch` and `SpecificationMethods`.
22
+ - 5.0 targets `net10.0` only. A spec project that must stay on net8.0/net9.0 stays on 4.x, and net48 on 3.x. A project that stays on xUnit v2 uses `Cratis.Specifications.XUnit`, with the same base and conventions.
23
+ - `.NUnit`/`.NUnit5`, `.TUnit` and the experimental `.Runner` are alternatives for repositories already built on them.
24
24
  - [NSubstitute](https://nsubstitute.github.io/) for mocking collaborators.
25
25
  - Spec projects are named `<Source>.Specs`.
26
26
 
@@ -48,9 +48,11 @@ public class when_combining_parts : Specification
48
48
  | `[Fact] void should_*()` | one assertion per fact | use the `ShouldXxx()` extension methods |
49
49
  | `void Destroy()` | teardown after each fact by default | once per eligible class with shared lifecycle opt-in |
50
50
 
51
- Shared lifecycle is optional (4.1.0+): add `[assembly: Xunit.TestFramework("Cratis.Specifications.SpecificationTestFramework", "Cratis.Specifications.XUnit")]`. Eligible classes derive from `Specification`, have one public parameterless constructor, do not implement `IDisposable`, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. Initialization failure fails every fact. Other classes keep per-fact execution. Keep the stock lifecycle by default; details are in the **cratis-specifications-csharp** skill's shared-lifecycle reference.
51
+ The shared lifecycle is the default on xUnit v2 and v3 since 5.0; the package registers the framework, so delete any hand-written `[assembly: Xunit.TestFramework(...)]` (it fails with `CS0579`). Eligible classes derive from `Specification`, have one public parameterless constructor, do not implement `IDisposable`, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. Initialization failure fails every fact. Theories and `ITestOutputHelper` constructors keep per-fact execution; `[PerFactSpecificationLifecycle]` opts a class out. `CRSPEC0008` (a fact modifies the shared context) is an error by default: move the change into `Establish` or `Because`. See the **cratis-specifications-csharp** skill's shared-lifecycle reference.
52
52
 
53
- CRSPEC0001–CRSPEC0007 are warnings bundled in `.Specifications`, `.XUnit`, and `.NUnit` 4.0.0+. With warnings as errors, a non-`should_` fact, multiple specs per file (given contexts exempt), a fact on a given context, a `should_` without a test attribute, explicit base lifecycle calls, non-public specs with facts, or `Because` on a given context fail the build. Follow **cratis-specification-by-example**; do not suppress these diagnostics.
53
+ Run specifications as the built executable, not `dotnet test`: restore and build once, then run `bin/<Config>/net10.0/<Project>.Specs`. In a solution that mixes Microsoft.Testing.Platform and VSTest projects, run each project on its own; `dotnet test` on the whole solution does not work there. Filters and the 4.x → 5.0 checklist are in the **cratis-specifications-csharp** skill's [running-specs reference](../../../skills/cratis-specifications-csharp/references/running-specs.md).
54
+
55
+ CRSPEC0001–CRSPEC0007 are warnings bundled in `.Specifications`, `.XUnit`, `.XUnit.v3`, and `.NUnit`. With warnings as errors, a non-`should_` fact, multiple specs per file (given contexts exempt), a fact on a given context, a `should_` without a test attribute, explicit base lifecycle calls, non-public specs with facts, or `Because` on a given context fail the build. Follow **cratis-specification-by-example**; do not suppress these diagnostics.
54
56
 
55
57
  ## Reusable layered contexts
56
58
 
@@ -158,12 +158,19 @@ cached_pulls() {
158
158
  }
159
159
  take_lock() {
160
160
  mkdir "$state/lock" 2>/dev/null && return 0
161
- local modified retired acquired
162
- read -r _ modified < <(lock_stat "$state/lock")
161
+ local inode modified retired acquired current
162
+ read -r inode modified < <(lock_stat "$state/lock")
163
163
  [[ "$modified" =~ ^[0-9]+$ ]] && [ "$now" -ge "$((modified + 900))" ] || return 1
164
164
  # Rename atomically: a killed reclaimer can leave only a retired directory, never a blocking marker.
165
165
  retired="$state/lock.stale.$$.$RANDOM"
166
166
  mv "$state/lock" "$retired" 2>/dev/null || return 1
167
+ read -r current _ < <(lock_stat "$retired")
168
+ if [ "$current" != "$inode" ]; then
169
+ # Another reclaimer replaced the stale directory. Restore its lock if possible;
170
+ # never acquire or delete a directory that was not the stale one we inspected.
171
+ [ -e "$state/lock" ] || mv "$retired" "$state/lock" 2>/dev/null
172
+ return 1
173
+ fi
167
174
  acquired=1
168
175
  mkdir "$state/lock" 2>/dev/null && acquired=0
169
176
  rm -rf "$retired"
@@ -282,9 +289,10 @@ in_use=$(git worktree list --porcelain | sed -n 's#^branch refs/heads/##p')
282
289
  planned_branches=$(printf '%s\n' "${PLAN_BRANCH[@]+"${PLAN_BRANCH[@]}"}")
283
290
  while IFS='|' read -r b bsha; do
284
291
  if [ -z "$b" ] || [ "$b" = "$default_local" ]; then continue; fi
285
- if printf '%s\n' "$in_use" | grep -qx "$b"; then
292
+ # Drain producers under pipefail: grep -q can turn a match into a SIGPIPE failure.
293
+ if printf '%s\n' "$in_use" | grep -x "$b" >/dev/null; then
286
294
  [ "$DRY" = 0 ] || continue
287
- printf '%s\n' "$planned_branches" | grep -qx "$b" || continue
295
+ printf '%s\n' "$planned_branches" | grep -x "$b" >/dev/null || continue
288
296
  fi
289
297
  plan_item branch "$b" "$bsha" "$b"
290
298
  done < <(git for-each-ref --format='%(refname:short)|%(objectname)' refs/heads)
@@ -307,7 +315,7 @@ git worktree prune >/dev/null 2>&1
307
315
  for ((i=0; i<${#PLAN_KIND[@]}; i++)); do
308
316
  kind=${PLAN_KIND[i]}; path=${PLAN_PATH[i]}; sha=${PLAN_SHA[i]}; br=${PLAN_BRANCH[i]}; ev=${PLAN_EVIDENCE[i]}
309
317
  if [ "$kind" = branch ]; then
310
- git worktree list --porcelain | sed -n 's#^branch refs/heads/##p' | grep -qx "$br" && continue
318
+ git worktree list --porcelain | sed -n 's#^branch refs/heads/##p' | grep -x "$br" >/dev/null && continue
311
319
  fi
312
320
  if [ -z "$ev" ]; then
313
321
  if [ "$kind" = worktree ]; then note_kept "worktree $path (${br:-detached}): work not delivered to $default"
@@ -16,8 +16,9 @@ dumps, and similar coordination files. These are **work records, not documentati
16
16
  If you find one tracked in git, move it into `.ai-work/` and remove it from
17
17
  tracking in a dedicated commit.
18
18
  - A genuine follow-up that must survive the session is **not** a work record — suggest
19
- opening a GitHub issue for it (or open one when asked) so future work is tracked
20
- where everyone can see it, instead of leaving a planning file behind.
19
+ opening a GitHub issue for it so future work is tracked
20
+ where everyone can see it, instead of leaving a planning file behind. A follow-up
21
+ issue includes an `## Acceptance criteria` section of concrete, testable bullets.
21
22
  - Knowledge that must outlive the session (real documentation, ADRs, operator
22
23
  guides) is written deliberately into the repository's documentation structure
23
24
  through normal review — not left behind as a work record.
@@ -36,16 +37,15 @@ dumps, and similar coordination files. These are **work records, not documentati
36
37
  replace the prompt.
37
38
  - **A worktree is a work record too.** Create it outside the repository tree, for
38
39
  example in a sibling `../.worktrees/` directory. A nested worktree inherits the
39
- parent's `.globalconfig` alongside its own, causing .NET's `MultipleGlobalAnalyzerKeys`
40
+ parent's `.globalconfig`, causing .NET's `MultipleGlobalAnalyzerKeys`
40
41
  errors or silently changing analyzer severities. **Delivered work is cleaned up by
41
42
  `.cratis/ai/hooks/scripts/cratis-cleanup-worktrees.sh`, not by memory:** run it from the
42
43
  main checkout when a pull request merges or you finish a task (hooks also run it after
43
44
  `gh pr merge` and at session start where the harness supports them, and **ship-changes**
44
45
  ends with it). It removes a worktree and its branch only on evidence the work reached the
45
- default branch — pushed is not merged — and never forces. Worktrees with non-empty `.ai-work/`
46
- directories are kept by default as "has untracked work records (.ai-work/)". Manual
47
- `--include-work-records` permits deleting those records after you preserve what you need;
48
- automatic hooks never opt in. Pin a long-lived worktree with `git worktree lock <path>`;
46
+ default branch — pushed is not merged — and never forces. It keeps worktrees with non-empty
47
+ `.ai-work/` directories; manual `--include-work-records` deletes those records after you
48
+ preserve what you need, and automatic hooks never opt in. Pin a long-lived worktree with `git worktree lock <path>`;
49
49
  locked worktrees are always kept, even with opt-in. Other ignored files such as `.env` files
50
50
  and local databases can still be deleted with a removed worktree, so move anything you
51
51
  still need out first. Do not hand-remove worktrees or `git branch -D` a delivered branch
@@ -17,9 +17,9 @@ Extends the base [specs.md](./specs.md) (folder structure, naming, philosophy) w
17
17
 
18
18
  ## Frameworks
19
19
 
20
- - [xUnit](https://xunit.net/) **2.x** for execution (not v3).
21
- - Reference `Cratis.Specifications.XUnit` **4.1.1** for the `Specification` base, convention lifecycle and `ShouldXxx` assertions; the namespace remains `Cratis.Specifications`. Its `Cratis.Specifications` dependency supplies `Catch` and `SpecificationMethods`, not the base or assertions.
22
- - 4.x targets net8.0/net9.0/net10.0; net48 requires 3.x. `Cratis.Specifications.NUnit` is an alternative with `[Test]` and public test methods; xUnit remains the house default.
20
+ - [xUnit v3](https://xunit.net/docs/getting-started/v3/migration) is the house default for new and migrated spec projects: reference `Cratis.Specifications.XUnit.v3` **5.0** with `xunit.v3.mtp-off`, `Microsoft.NET.Test.Sdk` and `xunit.runner.visualstudio`, and set `<OutputType>Exe</OutputType>` in the project. The package supplies the `Specification` base, convention lifecycle and `ShouldXxx` assertions; the namespace remains `Cratis.Specifications`. Its `Cratis.Specifications` dependency supplies `Catch` and `SpecificationMethods`.
21
+ - 5.0 targets `net10.0` only. A spec project that must stay on net8.0/net9.0 stays on 4.x, and net48 on 3.x. A project that stays on xUnit v2 uses `Cratis.Specifications.XUnit`, with the same base and conventions.
22
+ - `.NUnit`/`.NUnit5`, `.TUnit` and the experimental `.Runner` are alternatives for repositories already built on them.
23
23
  - [NSubstitute](https://nsubstitute.github.io/) for mocking collaborators.
24
24
  - Spec projects are named `<Source>.Specs`.
25
25
 
@@ -47,9 +47,11 @@ public class when_combining_parts : Specification
47
47
  | `[Fact] void should_*()` | one assertion per fact | use the `ShouldXxx()` extension methods |
48
48
  | `void Destroy()` | teardown after each fact by default | once per eligible class with shared lifecycle opt-in |
49
49
 
50
- Shared lifecycle is optional (4.1.0+): add `[assembly: Xunit.TestFramework("Cratis.Specifications.SpecificationTestFramework", "Cratis.Specifications.XUnit")]`. Eligible classes derive from `Specification`, have one public parameterless constructor, do not implement `IDisposable`, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. Initialization failure fails every fact. Other classes keep per-fact execution. Keep the stock lifecycle by default; details are in the **cratis-specifications-csharp** skill's shared-lifecycle reference.
50
+ The shared lifecycle is the default on xUnit v2 and v3 since 5.0; the package registers the framework, so delete any hand-written `[assembly: Xunit.TestFramework(...)]` (it fails with `CS0579`). Eligible classes derive from `Specification`, have one public parameterless constructor, do not implement `IDisposable`, and contain only plain facts. Construction, Establish/Because and Destroy run once; all facts share the instance and must not mutate it. Initialization failure fails every fact. Theories and `ITestOutputHelper` constructors keep per-fact execution; `[PerFactSpecificationLifecycle]` opts a class out. `CRSPEC0008` (a fact modifies the shared context) is an error by default: move the change into `Establish` or `Because`. See the **cratis-specifications-csharp** skill's shared-lifecycle reference.
51
51
 
52
- CRSPEC0001–CRSPEC0007 are warnings bundled in `.Specifications`, `.XUnit`, and `.NUnit` 4.0.0+. With warnings as errors, a non-`should_` fact, multiple specs per file (given contexts exempt), a fact on a given context, a `should_` without a test attribute, explicit base lifecycle calls, non-public specs with facts, or `Because` on a given context fail the build. Follow **cratis-specification-by-example**; do not suppress these diagnostics.
52
+ Run specifications as the built executable, not `dotnet test`: restore and build once, then run `bin/<Config>/net10.0/<Project>.Specs`. In a solution that mixes Microsoft.Testing.Platform and VSTest projects, run each project on its own; `dotnet test` on the whole solution does not work there. Filters and the 4.x → 5.0 checklist are in the **cratis-specifications-csharp** skill's [running-specs reference](../skills/cratis-specifications-csharp/references/running-specs.md).
53
+
54
+ CRSPEC0001–CRSPEC0007 are warnings bundled in `.Specifications`, `.XUnit`, `.XUnit.v3`, and `.NUnit`. With warnings as errors, a non-`should_` fact, multiple specs per file (given contexts exempt), a fact on a given context, a `should_` without a test attribute, explicit base lifecycle calls, non-public specs with facts, or `Because` on a given context fail the build. Follow **cratis-specification-by-example**; do not suppress these diagnostics.
53
55
 
54
56
  ## Reusable layered contexts
55
57
 
@@ -57,6 +57,7 @@ commands before choosing the build, regression spec and full gate commands.
57
57
  - [ ] Reproduce the consumer bug and select its regression spec/app action.
58
58
  - [ ] Read [references/dependency-map.md](references/dependency-map.md) for the chain between the named repositories, generate one stamp, then pack each producer in upstream order.
59
59
  - [ ] If needed, publish the kernel and assets, then build a matching local image.
60
+ - [ ] Run `closure` for the consumer and producers, follow the printed commands, and run its one `verify-pack` after the last pack.
60
61
  - [ ] Activate the consumer overlay and select the local image.
61
62
  - [ ] Build the consumer, run its regression specs/app action and required gates.
62
63
  - [ ] On failure, diagnose, fix the producer, create a distinct stamp and repeat.
@@ -94,9 +95,11 @@ exit `0`. Inspect package dependencies: every locally changed dependency ID must
94
95
  resolve to the stamp, not a published fallback. Chronicle's client solution filter
95
96
  can omit `Cratis.Chronicle.CodeAnalysis`; pack its code-analysis package project
96
97
  separately. The package IDs a producer should ship are listed in
97
- [dependency-map.tsv](scripts/dependency-map.tsv) (a reference, see
98
- [references/dependency-map.md](references/dependency-map.md)); compare them with the feed by hand.
99
- A successful pack alone does not prove dependency closure.
98
+ [dependency-map.tsv](scripts/dependency-map.tsv); `closure CONSUMER=PATH PRODUCER=PATH...` prints
99
+ the stamp, every overlay, one `pack-*` command per map row (with its `--property` arguments) and
100
+ ONE `verify-pack` for the whole plan (see [references/dependency-map.md](references/dependency-map.md)).
101
+ Run the printed commands in order. A successful pack alone does not prove dependency closure;
102
+ `verify-pack` does.
100
103
 
101
104
  ### 3. Build an image when needed
102
105
 
@@ -242,6 +245,7 @@ minutes, instead of after a release and a pin bump.
242
245
  - Read [references/docker-images.md](references/docker-images.md) when publishing a kernel or selecting images in a consumer.
243
246
  - Read [references/go-jvm-python-elixir.md](references/go-jvm-python-elixir.md) when the boundary uses another language toolchain.
244
247
  - Read [references/worked-examples.md](references/worked-examples.md) when following Chronicle → Studio, Fundamentals → Arc, Components → Studio, Chronicle → Direct, Screenplay → Stage and Studio, Arc → Chronicle, or Fundamentals → Arc → Chronicle.
248
+ - Read [references/train-mode.md](references/train-mode.md) when verifying a downstream repository against a local release train (`stamp --train`).
245
249
  - Read [references/other-environments.md](references/other-environments.md) when running on another developer machine or an agent host.
246
250
 
247
251
  Read `scripts/npm-restore-check.cjs` when inspecting the read-only npm restoration
@@ -5,9 +5,10 @@
5
5
 
6
6
  `scripts/dependency-map.tsv` is a reference table of the producer-to-consumer edges between
7
7
  Cratis repositories, the pack targets of each producer and the package IDs each ships.
8
- `cratis-local-packages.sh` does not read it: use it to decide which repositories to pack,
9
- in which order, and which IDs to expect in the feed. It holds no versions; read pins live
10
- from the consumer manifests (`Directory.Packages.props`, `package.json`, `Dockerfile`).
8
+ `cratis-local-packages.sh` reads it (or the file in `CRATIS_LOCAL_MAP`, or `--map FILE`) to derive
9
+ one plan: which repositories to pack, in which order, and which IDs the feed must hold. It holds
10
+ no versions; read pins live from the consumer manifests (`Directory.Packages.props`,
11
+ `package.json`, `Dockerfile`).
11
12
 
12
13
  ## Rows
13
14
 
@@ -15,7 +16,7 @@ Tab-separated, `#` starts a comment.
15
16
 
16
17
  | Row | Fields | Meaning |
17
18
  | --- | --- | --- |
18
- | `pack` | repo, eco, target, `PROP=VAL,...` | A pack target; the optional MSBuild properties are the `-p:` arguments the producer's own publish workflow passes |
19
+ | `pack` | repo, eco, target, `PROP=VAL,...` | A pack target; the optional MSBuild properties are the `-p:` arguments the producer's own publish workflow passes. `closure` prints each as one `--property NAME=VALUE` argument of that row's `pack-nuget` command |
19
20
  | `ids` | repo, eco, comma-separated IDs | The packages the producer ships in that ecosystem |
20
21
  | `uses` | consumer, producer, eco, manifest | An edge; the manifest is the pin file, or the Yarn project directory for npm |
21
22
  | `image` | repo, dockerfile, tag suffix | A producer image a consumer builds on |
@@ -29,15 +30,30 @@ Tab-separated, `#` starts a comment.
29
30
  (Fundamentals reaches Chronicle through Arc as well as directly).
30
31
  - Pack upstream first. Overlay the earlier producer into the next repository with
31
32
  `use-nuget`, pack that, then `restore` it. Use one stamp for the whole chain: `stamp`
32
- takes one producer, so generate it once and reuse the string.
33
- - Pack properties from a `pack` row are not accepted by `pack-nuget`. When a row has them,
34
- run `dotnet pack TARGET -c Release -p:PackageVersion=$stamp -p:Version=$stamp -p:PROP=VAL
35
- -o "$CRATIS_LOCAL_FEED/nuget"` yourself, with the properties as `-p:` arguments and never
36
- as exported environment variables (an exported `IsPackaging=true` reaches Chronicle's
37
- nested `dotnet run` code generator and fails with a multi-target error).
38
- - After packing, list the feed for the stamp and compare it with the `ids` row; a missing ID
39
- is a silent partial overlay. Open the nuspec of each packed ID and check that every locally
40
- changed dependency is at the stamp.
33
+ takes several producers, so generate it once and reuse the string.
34
+ - Read the closure: `closure [--train] CONSUMER=PATH PRODUCER=PATH...` (producers in dependency
35
+ order) prints the commands to run, in order, from the plan; `plan CONSUMER PRODUCER...` prints
36
+ the plan rows alone. Both exit 2 and print nothing when the plan is invalid.
37
+ - Pack properties are `pack-nuget --property NAME=VALUE` arguments, one per `-p:` of that command
38
+ only; never export them (an exported `IsPackaging=true` reaches Chronicle's nested `dotnet run`
39
+ code generator and fails with a multi-target error).
40
+ - `--train` prints the train stamp (see [train-mode.md](train-mode.md)) instead of a local stamp.
41
+
42
+ ## Pack plan and verification scope
43
+
44
+ The plan is a set of `(producer, ecosystem)` pack rows, overlay rows (a producer and the upstream
45
+ producers it consumes) and consume rows (the consumer's own edges), derived from the `uses` rows
46
+ for the names you list. It is rejected (exit 2) for an unknown or repeated name, no edge, a wrong
47
+ order, or a planned pack without `ids` or `pack` rows.
48
+
49
+ - Run `verify-pack STAMP --consumer C --producer P...` once, after the last pack. It checks the
50
+ planned IDs exist at exactly STAMP and each planned NuGet dependency is exactly STAMP, [STAMP] or [STAMP, ) (an unreadable dependency element fails).
51
+ - After `use-*` and restore, `verify-assets CONSUMER-PATH STAMP --consumer C --producer P...`
52
+ checks the same plan inside the consumer: no planned ID resolved at another version, and no
53
+ producer the consumer uses left unconsumed. `--json` lists the resolved IDs only.
54
+ - Each command derives and checks the plan itself and keeps no state between runs.
55
+ - `verify-pack STAMP NAME...` and `verify-assets CONSUMER STAMP` without a plan are manual and
56
+ weaker: they check the named repositories' IDs, or every stamped package the consumer resolves.
41
57
 
42
58
  ## Facts about the chains
43
59
 
@@ -50,7 +66,8 @@ Tab-separated, `#` starts a comment.
50
66
  - A repository you did not name keeps its released version: Stage reaches Arc, Chronicle
51
67
  reaches Orleans. A binary break through that hop is a finding about the consumer's pins.
52
68
  - The current Screenplay rows list only npm IDs although Stage and Studio consume its NuGet
53
- packages; read the IDs from the producer's publish workflow.
69
+ packages; `closure` exits 2 for them until the map has the Screenplay NuGet `ids`. Read the
70
+ IDs from the producer's publish workflow and add them first.
54
71
 
55
72
  ## Extend it
56
73
 
@@ -28,6 +28,9 @@ dotnet restore path/to/consumer.slnx
28
28
  dotnet build path/to/consumer.slnx -c Release --no-restore -warnaserror
29
29
  ```
30
30
 
31
+ Transitive pinning stays as the consumer sets it; `--pin-transitive` forces it on so stamped IDs the
32
+ consumer does not pin still take effect (it can raise NU1109 floors).
33
+
31
34
  The printed properties are `DirectoryPackagesPropsPath` and
32
35
  `RestoreAdditionalProjectSources`. For an IDE, launch it from that shell, or pass
33
36
  the printed `-p:` properties explicitly to its build. Verify restore uses the
@@ -5,8 +5,9 @@
5
5
 
6
6
  ## Pack the package, not a link
7
7
 
8
- Requires node, tar and Yarn 4. Run the producer's normal build first. The helper does
9
- not run a separate build; Yarn's normal pack lifecycle hooks still run.
8
+ Requires node, npm and Yarn 4. Run the producer's normal build first. The helper does
9
+ not build or run lifecycle scripts: it packs with `npm pack --ignore-scripts`, the publish
10
+ workflows' tool, so the tarball matches the published one.
10
11
 
11
12
  ```bash
12
13
  stamp=$(bash "$local" stamp "$producer")
@@ -15,8 +16,9 @@ bash "$local" pack-npm "$producer" "$stamp"
15
16
 
16
17
  It requires clean tracked workspace manifests, temporarily changes all non-private
17
18
  workspace versions and packs actual tarballs. It restores those manifests on
18
- success and failure; inspect diffs for other lifecycle-generated output. Yarn's
19
- `%s-%v.tgz` output slugifies a scoped name (`@cratis/foo` becomes `@cratis-foo`).
19
+ success and failure. Tarballs are named `<scope>-<name>-<version>.tgz` (`@cratis/foo` becomes
20
+ `cratis-foo-<version>.tgz`). `verify-assets --npm [--dir REL]` reads installed copies under
21
+ `node_modules`, or under `REL` when the consumer's Yarn project is a subfolder.
20
22
  Inspect dependency ranges in packed manifests: the helper changes versions, not
21
23
  arbitrary inter-package dependency constraints. Include every required workspace
22
24
  and verify the consumer resolves the intended tarballs.
@@ -0,0 +1,43 @@
1
+ <!-- Copyright (c) Cratis. All rights reserved. -->
2
+ <!-- Licensed under the MIT license. See LICENSE file in the project root for full license information. -->
3
+
4
+ # Train mode
5
+
6
+ A local release train combines several branches into one pull request, one CI run and one
7
+ release. Verify a downstream repository against the upstream train's packages, not against
8
+ per-branch packages.
9
+
10
+ ## Stamp
11
+
12
+ `stamp --train PRODUCER...` prints `X.Y.Z-train.g<sha7>`. X.Y.Z is the highest next patch over the
13
+ producers' latest reachable tags, so no stamped package sorts below a released pin. sha7 is the first
14
+ seven hex characters of the SHA-1 of the sorted `<repository> <full HEAD sha>` lines of every
15
+ producer (the repository is its origin's last path part, else its folder name). There is no minute: one
16
+ combined code state yields one stamp, a change in any producer changes it, and the order of the
17
+ producers does not matter. The grammar is fixed so tools can rely on it. Every command that takes a
18
+ stamp accepts it: `pack-*`, `use-*`, `verify-*`, `check` and `clean --stamp`. A train stamp
19
+ sorts above the released pin like a local stamp does.
20
+
21
+ ## Rules
22
+
23
+ - One stamp per train state. The train tool records it in the train manifest; read it from there
24
+ instead of recomputing it for a different HEAD.
25
+ - A fold into the upstream train changes the upstream HEAD and so the stamp. Downstream
26
+ verification against the old stamp is stale: pack again, overlay the new stamp, rebuild and
27
+ re-verify. Do not carry a pass over a fold.
28
+ - Train versions are as local as local stamps. `check` reports `-train.g<sha7>` in changed
29
+ tracked, staged and untracked files, so a train version never reaches a commit or pull request.
30
+ - Upstream first. The downstream ships only after the upstream has released and the downstream pin
31
+ is bumped to the released version; run `check` (0 leaks) before the downstream push.
32
+
33
+ ## Recording consumed versions
34
+
35
+ `verify-pack STAMP --consumer C --producer P... --json` and
36
+ `verify-assets CONSUMER STAMP [--npm] [--consumer C --producer P...] --json` print one line:
37
+ `{"stamp":"...","packages":[{"id":"...","version":"..."}]}`. Findings go to stderr and the exit status
38
+ is unchanged, so a train tool can record the consumed versions without scraping text.
39
+
40
+ ## Clean
41
+
42
+ `clean --stamp STAMP` lists and removes the feed files, cache folders and Yarn cache entries of
43
+ that one train stamp; a local stamp is not matched. Review the dry run before `--apply`.
@@ -162,6 +162,10 @@ bash "$local" use-nuget "$stage" "$stamp"; bash "$local" use-nuget "$studio" "$s
162
162
  bash "$local" use-npm "$studio" "$stamp" # at the workspace root, not the nested app
163
163
  ```
164
164
 
165
+ `closure` exits 2 for this chain until `dependency-map.tsv` has the Screenplay NuGet `ids` (it lists
166
+ only npm IDs); add them from the publish workflow first, then use the printed commands and its one
167
+ `verify-pack`.
168
+
165
169
  Gates: Stage's frontend `npm ci`, test and build, then `dotnet build` in Debug and the repository's
166
170
  own spec-shard helper; skip probes that need a live kernel or database and say so. Studio:
167
171
  `dotnet build` of its solution in Debug with warnings as errors, then the frontend `yarn ci` and
@@ -171,7 +175,7 @@ own spec-shard helper; skip probes that need a live kernel or database and say s
171
175
  - Pitfall: a repository without a committed lockfile fails `yarn install --immutable`; use plain
172
176
  `yarn install` there.
173
177
  - Pitfall: yarn hoists to the root `node_modules`; check and install at the root.
174
- - Pitfall: the packed language tarball must contain its built bundles. `pack-npm` uses `yarn pack`;
178
+ - Pitfall: the packed language tarball must contain its built bundles. `pack-npm` uses `npm pack`;
175
179
  a package whose `files` lists a gitignored folder can ship only part of it. Compare the
176
180
  tarball file list with the package's `dist` folder.
177
181
  - Pitfall: multi-hop. Studio keeps its released Stage; a binary break is a finding.