@cratis/pi 0.0.1
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.
- package/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-fundamentals-concept
|
|
3
|
+
description: Create strongly typed Cratis domain values with ConceptAs<T> and Chronicle event-source identities with EventSourceId<T>. Use when a C# domain value has meaning beyond its primitive or when an identity is actually used as a Chronicle event-source/stream ID. Do not use for enums, DTO-only transport values, arbitrary non-stream entity IDs, or event schema migration.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cratis domain concepts and event-source identities
|
|
8
|
+
|
|
9
|
+
Replace a primitive only when the domain gives it distinct meaning. Keep value
|
|
10
|
+
concepts and Chronicle stream identities separate.
|
|
11
|
+
|
|
12
|
+
## Verified product sources
|
|
13
|
+
|
|
14
|
+
This skill is verified against these exact public releases:
|
|
15
|
+
|
|
16
|
+
| Package | Version | Purpose |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `Cratis.Fundamentals` | `7.18.1` | `Cratis.Concepts.ConceptAs<T>` |
|
|
19
|
+
| `Cratis.Chronicle` | `16.38.1` | `Cratis.Chronicle.Events.EventSourceId` and `EventSourceId<T>` |
|
|
20
|
+
|
|
21
|
+
Reverify product sources before claiming support for another version.
|
|
22
|
+
|
|
23
|
+
## Choose the type
|
|
24
|
+
|
|
25
|
+
- Derive a name, amount, code, number, or non-stream entity ID from
|
|
26
|
+
`ConceptAs<T>`.
|
|
27
|
+
- Derive an identity from `EventSourceId<T>` only when that value is actually
|
|
28
|
+
passed to Chronicle as the event-source/stream ID.
|
|
29
|
+
- Do not use `ConceptAs<Guid>` for a Chronicle stream identity.
|
|
30
|
+
- Do not use `EventSourceId<T>` merely because a value is called an ID.
|
|
31
|
+
- Do not wrap an enum. An enum already expresses a closed domain concept.
|
|
32
|
+
- Keep DTO-only transport values primitive unless the domain type belongs in the
|
|
33
|
+
public contract.
|
|
34
|
+
|
|
35
|
+
Both generic bases require an underlying type that implements `IComparable`.
|
|
36
|
+
|
|
37
|
+
## Create a value concept
|
|
38
|
+
|
|
39
|
+
A value concept contains exactly one wrapped value. Do not add extra properties;
|
|
40
|
+
Fundamentals converters assume the concept is a single-value type and additional
|
|
41
|
+
state can be lost during serialization.
|
|
42
|
+
|
|
43
|
+
```csharp
|
|
44
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
45
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
46
|
+
|
|
47
|
+
using Cratis.Concepts;
|
|
48
|
+
|
|
49
|
+
namespace <NamespaceRoot>.<Feature>;
|
|
50
|
+
|
|
51
|
+
/// <summary>
|
|
52
|
+
/// Represents the <description>.
|
|
53
|
+
/// </summary>
|
|
54
|
+
/// <param name="Value">The underlying value.</param>
|
|
55
|
+
public record <ConceptName>(<ComparableUnderlyingType> Value) :
|
|
56
|
+
ConceptAs<<ComparableUnderlyingType>>(Value);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`ConceptAs<T>` supplies implicit conversion from the concept to `T`. Add the
|
|
60
|
+
reverse conversion only when it improves the domain API:
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
public static implicit operator <ConceptName>(<ComparableUnderlyingType> value) =>
|
|
64
|
+
new(value);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Primitive-to-concept conversion is optional; it is not a Fundamentals
|
|
68
|
+
requirement.
|
|
69
|
+
|
|
70
|
+
### Absence and sentinels
|
|
71
|
+
|
|
72
|
+
`ConceptAs<T>` rejects a null wrapped value. Represent absence with a nullable
|
|
73
|
+
concept reference such as `<ConceptName>?` when absence is valid.
|
|
74
|
+
|
|
75
|
+
A `NotSet` or `Empty` value is optional domain policy. Add one only when the
|
|
76
|
+
chosen primitive value is impossible or explicitly reserved in that domain.
|
|
77
|
+
Do not assume `string.Empty`, `0`, or `Guid.Empty` is universally invalid.
|
|
78
|
+
|
|
79
|
+
## Create a Guid-backed Chronicle stream identity
|
|
80
|
+
|
|
81
|
+
Use this shape only for an identity actually supplied to Chronicle append/read
|
|
82
|
+
operations as the event-source ID.
|
|
83
|
+
|
|
84
|
+
```csharp
|
|
85
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
86
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
87
|
+
|
|
88
|
+
using Cratis.Chronicle.Events;
|
|
89
|
+
|
|
90
|
+
namespace <NamespaceRoot>.<Feature>;
|
|
91
|
+
|
|
92
|
+
/// <summary>
|
|
93
|
+
/// Represents the event-source identity of a <description>.
|
|
94
|
+
/// </summary>
|
|
95
|
+
/// <param name="Value">The underlying Guid value.</param>
|
|
96
|
+
public record <ConceptName>(Guid Value) : EventSourceId<Guid>(Value)
|
|
97
|
+
{
|
|
98
|
+
/// <summary>
|
|
99
|
+
/// Creates a new <ConceptName>.
|
|
100
|
+
/// </summary>
|
|
101
|
+
/// <returns>A new <ConceptName>.</returns>
|
|
102
|
+
public static <ConceptName> New() => new(Guid.NewGuid());
|
|
103
|
+
|
|
104
|
+
/// <summary>
|
|
105
|
+
/// Converts a Guid to a <ConceptName>.
|
|
106
|
+
/// </summary>
|
|
107
|
+
public static implicit operator <ConceptName>(Guid value) => new(value);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`New()` and the primitive-to-derived conversion are conveniences on this domain
|
|
112
|
+
type. `EventSourceId<T>` does not construct an arbitrary derived identity for
|
|
113
|
+
you.
|
|
114
|
+
|
|
115
|
+
## Create a non-Guid Chronicle stream identity
|
|
116
|
+
|
|
117
|
+
Use a factory only when the domain has an authoritative way to create the
|
|
118
|
+
underlying value.
|
|
119
|
+
|
|
120
|
+
```csharp
|
|
121
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
122
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
123
|
+
|
|
124
|
+
using Cratis.Chronicle.Events;
|
|
125
|
+
|
|
126
|
+
namespace <NamespaceRoot>.<Feature>;
|
|
127
|
+
|
|
128
|
+
/// <summary>
|
|
129
|
+
/// Represents the event-source identity of a <description>.
|
|
130
|
+
/// </summary>
|
|
131
|
+
/// <param name="Value">The underlying value.</param>
|
|
132
|
+
public record <ConceptName>(<ComparableUnderlyingType> Value) :
|
|
133
|
+
EventSourceId<<ComparableUnderlyingType>>(Value)
|
|
134
|
+
{
|
|
135
|
+
/// <summary>
|
|
136
|
+
/// Converts the underlying value to a <ConceptName>.
|
|
137
|
+
/// </summary>
|
|
138
|
+
public static implicit operator <ConceptName>(<ComparableUnderlyingType> value) =>
|
|
139
|
+
new(value);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The exact `EventSourceId<T>` base supports conversions among `T`, string,
|
|
144
|
+
untyped `EventSourceId`, and `EventSourceId<T>`. Those operators do not create
|
|
145
|
+
your derived `<ConceptName>` from `T`, string, or untyped `EventSourceId`.
|
|
146
|
+
Declare only the derived-type conversions your domain API needs.
|
|
147
|
+
|
|
148
|
+
String and Guid are the safest round-trip primitives. Chronicle also supports
|
|
149
|
+
constructible `ConceptAs<string>` and `ConceptAs<Guid>` values. Other comparable
|
|
150
|
+
values rely on `Convert.ChangeType`; verify round-trip behavior before using
|
|
151
|
+
them as stream IDs.
|
|
152
|
+
|
|
153
|
+
### Unspecified and sensitive identities
|
|
154
|
+
|
|
155
|
+
`EventSourceId.Unspecified` belongs to the untyped string-backed ID.
|
|
156
|
+
`Guid.Empty`, `0`, `0L`, and similar typed values become real, specified stream
|
|
157
|
+
IDs after conversion; they are not Chronicle's unspecified value. Treat any
|
|
158
|
+
sentinel on a typed identity as explicit domain policy, not framework behavior.
|
|
159
|
+
|
|
160
|
+
Never use a sensitive natural identifier directly as an event-source ID.
|
|
161
|
+
Chronicle cannot encrypt event-source IDs. Use a random surrogate stream ID and
|
|
162
|
+
store the sensitive value separately under the approved compliance model.
|
|
163
|
+
|
|
164
|
+
## Use the identity with Chronicle
|
|
165
|
+
|
|
166
|
+
Pass the typed identity as the append/read event-source ID. Merely declaring an
|
|
167
|
+
`EventSourceId<T>` property does not select the event stream.
|
|
168
|
+
|
|
169
|
+
Do not add `[Key]` or `[Subject]` to an `EventSourceId<T>`-derived member;
|
|
170
|
+
Chronicle analyzer `CHR0026` reports that misuse. Do not add `[PII]` to an
|
|
171
|
+
event-source ID; analyzer `CHR0034` rejects it.
|
|
172
|
+
|
|
173
|
+
## Placement is an application convention
|
|
174
|
+
|
|
175
|
+
In a Cratis application, place the concept with the feature that owns its
|
|
176
|
+
meaning rather than in a generic `Concepts/` folder. Put genuinely cross-feature
|
|
177
|
+
concepts in `Common/`. Do not introduce a top-level `Features/` wrapper.
|
|
178
|
+
|
|
179
|
+
This placement is a Cratis application convention, not a Fundamentals or
|
|
180
|
+
Chronicle API requirement. Framework and client repositories follow their own
|
|
181
|
+
repository structure.
|
|
182
|
+
|
|
183
|
+
## Verify
|
|
184
|
+
|
|
185
|
+
- `ConceptAs<T>` and `EventSourceId<T>` use an `IComparable` underlying type.
|
|
186
|
+
- A concept contains exactly one wrapped value and no extra properties.
|
|
187
|
+
- Enums remain enums.
|
|
188
|
+
- Null absence uses a nullable concept reference rather than a null wrapped
|
|
189
|
+
value.
|
|
190
|
+
- Primitive-to-derived conversions and sentinels exist only when justified by
|
|
191
|
+
the domain.
|
|
192
|
+
- An `EventSourceId<T>` type represents a real Chronicle stream identity.
|
|
193
|
+
- The typed identity is passed explicitly to Chronicle operations.
|
|
194
|
+
- No `[Key]`, `[Subject]`, or `[PII]` attribute is placed on the stream identity.
|
|
195
|
+
- Sensitive natural identifiers use a surrogate stream ID.
|
|
196
|
+
- The file carries the repository license header.
|
|
197
|
+
- The project builds and its relevant specifications pass against the verified
|
|
198
|
+
package versions.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-fundamentals-type-discovery
|
|
3
|
+
description: Wire a type that must enumerate every implementation of an abstraction by injecting IInstancesOf<T> from Cratis.Types and deleting the hand-maintained service registrations. Use when a handler set, filter chain, strategy set, or dispatcher fans out to all implementations. Do not use for ordinary single-service dependency injection.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cratis implementation discovery
|
|
8
|
+
|
|
9
|
+
`services.AddSingleton<IFoo, Foo>()` looks free at the registration line. The
|
|
10
|
+
cost arrives later: a new implementation in another folder does nothing until
|
|
11
|
+
someone remembers the line, a removed one leaves a registration that fails at
|
|
12
|
+
startup, and every specification setup has to mirror the composition root.
|
|
13
|
+
|
|
14
|
+
`IInstancesOf<T>` moves that knowledge into the implementation. Adding or
|
|
15
|
+
removing one becomes a single-file change.
|
|
16
|
+
|
|
17
|
+
## Verified product sources
|
|
18
|
+
|
|
19
|
+
| Package | Version | Purpose |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `Cratis.Fundamentals` | `7.18.2` | `IInstancesOf<T>`, `IImplementationsOf<T>`, `[Singleton]`, `[Scoped]`, `[IgnoreConvention]`, the convention bindings |
|
|
22
|
+
|
|
23
|
+
Reverify against the Cratis Fundamentals repository before claiming support for
|
|
24
|
+
another version.
|
|
25
|
+
|
|
26
|
+
## Route near misses
|
|
27
|
+
|
|
28
|
+
- The consumer needs **one** implementation chosen at composition time:
|
|
29
|
+
constructor-inject the interface and let the `IFoo → Foo` convention resolve
|
|
30
|
+
it. This skill does not apply.
|
|
31
|
+
- The method **returns** a sequence of values to a caller: `IEnumerable<T>` (or
|
|
32
|
+
`IReadOnlyList<T>`) is still the right return type. The rule is about
|
|
33
|
+
*injecting* implementations of an abstraction, not about returning values.
|
|
34
|
+
- You only need the **types**, never instances: inject `IImplementationsOf<T>`,
|
|
35
|
+
which enumerates `Type` rather than `T`.
|
|
36
|
+
|
|
37
|
+
## When you need this
|
|
38
|
+
|
|
39
|
+
The consumer of an abstraction has to iterate, filter, or fan out to **every**
|
|
40
|
+
registered implementation:
|
|
41
|
+
|
|
42
|
+
- Anything plural that delegates to a set — `*Handlers`, `*Filters`,
|
|
43
|
+
`*Validators`, `*Formatters`, `*Strategies`, `*Providers`, `*Resolvers`.
|
|
44
|
+
- A dispatcher that asks each implementation `CanHandle(...)` and forwards to
|
|
45
|
+
the one that says yes.
|
|
46
|
+
- A composite that fans one input out to all implementations and aggregates.
|
|
47
|
+
|
|
48
|
+
## Step 1 — Confirm the implementations are discoverable
|
|
49
|
+
|
|
50
|
+
`IInstancesOf<T>` finds types by convention across the loaded assemblies. The
|
|
51
|
+
requirements are only:
|
|
52
|
+
|
|
53
|
+
- Each implementation is a non-abstract `public class`.
|
|
54
|
+
- It implements the interface directly, not through a layer that hides it.
|
|
55
|
+
- `T` is an interface or abstract class — the interface is declared
|
|
56
|
+
`where T : class`.
|
|
57
|
+
|
|
58
|
+
No assembly attribute and no registration call is needed.
|
|
59
|
+
|
|
60
|
+
## Step 2 — Choose the lifetime with an attribute
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
using Cratis.DependencyInjection;
|
|
64
|
+
|
|
65
|
+
[Singleton]
|
|
66
|
+
public class CsvReportFormatter(IClock clock) : IReportFormatter
|
|
67
|
+
{
|
|
68
|
+
public bool CanHandle(ReportRequest request) => request.Format == "csv";
|
|
69
|
+
public string Format(Report report) => /* … */;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
⚠️ `[Singleton]` lives in **`Cratis.DependencyInjection`**, not `Cratis`. That
|
|
74
|
+
is the using directive to add when it does not resolve.
|
|
75
|
+
|
|
76
|
+
The convention reads exactly three attributes:
|
|
77
|
+
|
|
78
|
+
| Attribute | Lifetime |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `[Singleton]` | Singleton |
|
|
81
|
+
| `[Scoped]` | Scoped |
|
|
82
|
+
| neither | **Transient** |
|
|
83
|
+
|
|
84
|
+
`[IgnoreConvention]` opts a type out of convention binding entirely.
|
|
85
|
+
|
|
86
|
+
Skip `[Singleton]` only when the implementation genuinely holds per-call state.
|
|
87
|
+
The `IFoo → Foo` convention still applies to transients — do not register them
|
|
88
|
+
explicitly either.
|
|
89
|
+
|
|
90
|
+
## Step 3 — Inject `IInstancesOf<T>` in the consumer
|
|
91
|
+
|
|
92
|
+
```csharp
|
|
93
|
+
using Cratis.DependencyInjection;
|
|
94
|
+
using Cratis.Types;
|
|
95
|
+
|
|
96
|
+
[Singleton]
|
|
97
|
+
public class ReportFormatters(IInstancesOf<IReportFormatter> formatters) : IReportFormatters
|
|
98
|
+
{
|
|
99
|
+
public bool CanHandle(ReportRequest request) =>
|
|
100
|
+
formatters.Any(formatter => formatter.CanHandle(request));
|
|
101
|
+
|
|
102
|
+
public string Format(ReportRequest request, Report report) =>
|
|
103
|
+
formatters.First(formatter => formatter.CanHandle(request)).Format(report);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`IInstancesOf<T>` implements `IEnumerable<T>`, so LINQ works on it directly.
|
|
108
|
+
|
|
109
|
+
**It resolves an instance from the service provider on every enumeration**, not
|
|
110
|
+
once at construction. That is what makes a scoped or transient implementation
|
|
111
|
+
behave correctly, and it is why materializing with `.ToArray()` is worth doing
|
|
112
|
+
only when you genuinely need a stable snapshot within one operation.
|
|
113
|
+
|
|
114
|
+
## Step 4 — Delete the dead registrations
|
|
115
|
+
|
|
116
|
+
Find every line in a composition root or service-collection extension that
|
|
117
|
+
registered the implementations or the consumer, and remove it:
|
|
118
|
+
|
|
119
|
+
```csharp
|
|
120
|
+
// Delete — IInstancesOf<T> discovers them, [Singleton] gives them their lifetime
|
|
121
|
+
services.AddSingleton<IReportFormatter, CsvReportFormatter>();
|
|
122
|
+
services.AddSingleton<IReportFormatter, JsonReportFormatter>();
|
|
123
|
+
services.AddSingleton<IReportFormatters, ReportFormatters>();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Search for every remaining reference before deleting, so nothing else was
|
|
127
|
+
relying on a registration for a different reason.
|
|
128
|
+
|
|
129
|
+
## What breaks
|
|
130
|
+
|
|
131
|
+
- **`[Singleton]` does not resolve.** The using directive is
|
|
132
|
+
`Cratis.DependencyInjection`, not `Cratis`.
|
|
133
|
+
- **An implementation is silently absent from the set.** Its assembly is not
|
|
134
|
+
loaded at the point of enumeration, it is not `public`, it is abstract, or it
|
|
135
|
+
carries `[IgnoreConvention]`.
|
|
136
|
+
- **An implementation is a new instance every time it is touched.** It has no
|
|
137
|
+
lifetime attribute, so the convention made it transient. That is correct
|
|
138
|
+
behavior for the convention and usually the wrong intent — add `[Singleton]`.
|
|
139
|
+
- **A stale registration fails at startup.** A hand-written
|
|
140
|
+
`AddSingleton<TInterface, Impl>()` survived a type being removed. Step 4 is
|
|
141
|
+
what prevents it.
|
|
142
|
+
- **Injecting `IEnumerable<T>` seems to work and returns nothing.** That
|
|
143
|
+
signature only sees hand-registered implementations. It is the failure mode
|
|
144
|
+
this skill exists to remove — it fails silently and empty, not loudly.
|
|
145
|
+
|
|
146
|
+
## How it is proven
|
|
147
|
+
|
|
148
|
+
`dotnet build` — zero warnings, zero errors — then run the specifications for
|
|
149
|
+
the affected behavior. The non-vacuity check matters here: assert the **count**
|
|
150
|
+
of discovered implementations, not just that iterating them threw nothing. A
|
|
151
|
+
fan-out over an empty set passes every assertion that only says "nothing went
|
|
152
|
+
wrong".
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-governed-release-methodology
|
|
3
|
+
description: Choose the assurance tier a release needs, gather the evidence that tier requires, label release intent by semantic-version impact, and structure a canary-before-promotion path with an explicit recovery disposition. Use when planning a release or judging whether checksums, provenance, an SBOM, or a claim of support are warranted. Do not use for product code, publish-command syntax, or release credentials.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Governed release methodology
|
|
8
|
+
|
|
9
|
+
Release engineering is the discipline of matching **ceremony to consequence**.
|
|
10
|
+
Too little, and a consumer upgrades into a broken build with no way back. Too
|
|
11
|
+
much, and every documentation fix drags a supply-chain ritual behind it until
|
|
12
|
+
the ritual is performed without belief and stops meaning anything.
|
|
13
|
+
|
|
14
|
+
This skill is product-, language-, and registry-independent. It applies to a
|
|
15
|
+
Cratis repository publishing NuGet packages, one publishing npm packages, one
|
|
16
|
+
pushing container images, and one whose whole delivery is a merge to `main`. It
|
|
17
|
+
grants no release authority of its own: it tells you what a claim costs, not
|
|
18
|
+
that you may make it.
|
|
19
|
+
|
|
20
|
+
## Start with the artifact, not the process
|
|
21
|
+
|
|
22
|
+
The single most expensive mistake is applying package-release ceremony to
|
|
23
|
+
something that is not a package. Classify the artifact first.
|
|
24
|
+
|
|
25
|
+
| Artifact | What the consumer receives | Ceremony it earns |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Compiled package (NuGet, npm, Maven, Hex), container image, installer | Opaque bytes built on infrastructure the consumer cannot inspect | Full supply-chain receipts |
|
|
28
|
+
| Generated or bundled tree assembled by a pipeline | Bytes that do not exist in any reviewed source tree | Full supply-chain receipts |
|
|
29
|
+
| Source files a host reads directly from a git ref (rules, skills, templates) | Exactly the reviewed content at a commit | Review and versioning only |
|
|
30
|
+
|
|
31
|
+
The dividing question is **"can the consumer verify what they got against what
|
|
32
|
+
was reviewed, without trusting our build?"** If the host clones a git ref and
|
|
33
|
+
reads the files, git's own content addressing already answers that: the tree
|
|
34
|
+
hash *is* the checksum, the commit *is* the provenance, and the reviewed diff
|
|
35
|
+
*is* the artifact. Generating a checksum file for that content, publishing an
|
|
36
|
+
SBOM of markdown, and demanding a signed attestation before a folder may be read
|
|
37
|
+
adds process without adding a guarantee. Say so plainly rather than performing
|
|
38
|
+
it.
|
|
39
|
+
|
|
40
|
+
If the artifact is compiled or assembled, the opposite holds, and the
|
|
41
|
+
supply-chain receipts below are not optional decoration.
|
|
42
|
+
|
|
43
|
+
## Pick the assurance tier before you pick the checks
|
|
44
|
+
|
|
45
|
+
Three tiers, each earning strictly more claim than the one below it. Default to
|
|
46
|
+
the lowest tier that supports the claim you intend to make, and never let a
|
|
47
|
+
lower tier's evidence be reinterpreted as a higher tier's.
|
|
48
|
+
|
|
49
|
+
**Candidate review** — an internal, non-published build for review. Claim:
|
|
50
|
+
"this builds deterministically from an immutable source and contains nothing it
|
|
51
|
+
should not." Requires deterministic regeneration from an exact revision, static
|
|
52
|
+
contract and schema validation, secret and path scanning, and an inventory
|
|
53
|
+
digest. It may never be installed by a consumer or described as available.
|
|
54
|
+
|
|
55
|
+
**Preview** — a real, published, low-commitment version (a `0.x.y`, a
|
|
56
|
+
prerelease channel). Claim: "a consumer can install this, use it, and get back
|
|
57
|
+
out." Requires everything above, plus an independent human review and a real
|
|
58
|
+
**exact-artifact** lifecycle smoke: pack, install, discover, use, uninstall, and
|
|
59
|
+
roll back to a prior exact version. A preview may be withdrawn, may break, and
|
|
60
|
+
must never be described as supported.
|
|
61
|
+
|
|
62
|
+
**Governed support** — the claim that a consumer may depend on this. Requires
|
|
63
|
+
everything above, plus the full lifecycle phase set on a real host, external
|
|
64
|
+
control attestations, a canary against a real consumer, a recorded recovery
|
|
65
|
+
disposition, and a named human approval for that exact released artifact.
|
|
66
|
+
|
|
67
|
+
Graduation between tiers does not change the artifact's format. It changes
|
|
68
|
+
what has been observed about it. A preview that later graduates re-runs the
|
|
69
|
+
evidence for the exact released version; earlier observations of an earlier
|
|
70
|
+
build do not transfer.
|
|
71
|
+
|
|
72
|
+
## Evidence is an observation, not an intention
|
|
73
|
+
|
|
74
|
+
"Evidence" degrades into paperwork the moment it is allowed to mean "we
|
|
75
|
+
implemented the thing that would produce it." Hold the line with a technical
|
|
76
|
+
ladder where each rung names an observation someone actually made:
|
|
77
|
+
|
|
78
|
+
1. **documented** — the behavior is written down.
|
|
79
|
+
2. **generated** — an artifact is produced deterministically.
|
|
80
|
+
3. **statically validated** — schemas, contracts, and scans pass on it.
|
|
81
|
+
4. **install-tested** — it installs into a real host.
|
|
82
|
+
5. **behavior-tested** — it is discovered, does the right thing on a positive
|
|
83
|
+
case, and correctly refuses a negative case.
|
|
84
|
+
6. **lifecycle-tested** — install, update, rollback, uninstall, and preservation
|
|
85
|
+
of consumer-owned state all pass on that exact artifact.
|
|
86
|
+
7. **release-tested** — the *released* artifact (not a local build of the same
|
|
87
|
+
commit) passed a canary.
|
|
88
|
+
8. **supported** — a named human approved that exact released artifact.
|
|
89
|
+
|
|
90
|
+
Two rules keep the ladder honest:
|
|
91
|
+
|
|
92
|
+
- **Synthetic evidence caps out at statically validated.** A fixture, simulator,
|
|
93
|
+
or mocked host proves the pipeline, never the host. Classify every observation
|
|
94
|
+
as synthetic, local, hosted, or real-consumer, and never let a synthetic
|
|
95
|
+
observation satisfy an execution requirement.
|
|
96
|
+
- **A missing or version-mismatched host is a blocked outcome, not a skip.** If
|
|
97
|
+
the canary needed host 2.1.245 and the runner had 2.1.235, the phase did not
|
|
98
|
+
pass; it did not run. A skipped phase that reports green is worse than a red
|
|
99
|
+
one, because it retires the question.
|
|
100
|
+
|
|
101
|
+
## The lifecycle phases a real install cycle must cover
|
|
102
|
+
|
|
103
|
+
When you claim a consumer can adopt and un-adopt your artifact, these are the
|
|
104
|
+
phases that claim decomposes into. Run them against the exact artifact a
|
|
105
|
+
consumer would receive.
|
|
106
|
+
|
|
107
|
+
- **collision-negative** — with the artifact absent, nothing already present
|
|
108
|
+
answers to its name. This is the baseline that makes the positive result mean
|
|
109
|
+
something.
|
|
110
|
+
- **install** — it installs from the published location, not a local path.
|
|
111
|
+
- **discovery** — the host actually finds and lists it afterwards.
|
|
112
|
+
- **behavior-positive** — it does the thing it exists to do.
|
|
113
|
+
- **behavior-negative** — it declines the case it must decline. An artifact that
|
|
114
|
+
never says no has not been tested for judgment.
|
|
115
|
+
- **update** — moving from the previous version to this one succeeds.
|
|
116
|
+
- **rollback** — moving back to the previous exact version succeeds. This is the
|
|
117
|
+
phase teams skip and the one consumers need most.
|
|
118
|
+
- **uninstall** — it removes cleanly.
|
|
119
|
+
- **project-context-preservation** — consumer-owned files it never owned are
|
|
120
|
+
untouched by install, update, rollback, and uninstall. Destroying local
|
|
121
|
+
configuration during an upgrade is the most expensive failure in this list.
|
|
122
|
+
- **cleanup** — no residue in caches, registries, or host state.
|
|
123
|
+
|
|
124
|
+
Rollback and preservation are what convert "it works" into "it is safe to try."
|
|
125
|
+
A release path without a proven way back is not a release path; it is a
|
|
126
|
+
one-way door.
|
|
127
|
+
|
|
128
|
+
## Supply-chain receipts and what each one proves
|
|
129
|
+
|
|
130
|
+
For compiled or assembled artifacts only, and each for a distinct reason:
|
|
131
|
+
|
|
132
|
+
- **Checksums** prove the bytes a consumer downloaded are the bytes that were
|
|
133
|
+
built. They defend against a corrupted or substituted download.
|
|
134
|
+
- **Provenance** proves *which* build, from which source revision, on which
|
|
135
|
+
workflow, produced those bytes. It defends against a package that matches its
|
|
136
|
+
own checksum but was built from something nobody reviewed.
|
|
137
|
+
- **SBOM** proves what is *inside* the artifact. It is what makes a downstream
|
|
138
|
+
vulnerability answerable at all: without it, "are we affected?" requires
|
|
139
|
+
re-deriving the dependency closure of a shipped binary.
|
|
140
|
+
- **Canary receipt** proves the released artifact worked for a real consumer.
|
|
141
|
+
- **Promotion receipt** proves the decision to widen the audience came after the
|
|
142
|
+
canary, and records what it was based on.
|
|
143
|
+
- **Recovery disposition** records, before publication, exactly what can be
|
|
144
|
+
undone and what cannot.
|
|
145
|
+
- **Support approval** is a named human accepting responsibility for that exact
|
|
146
|
+
released artifact.
|
|
147
|
+
|
|
148
|
+
Each receipt is written **at the stage it describes and never backdated**. A
|
|
149
|
+
publication receipt cannot be a prepublication prerequisite; a promotion receipt
|
|
150
|
+
cannot be written before the canary it cites. If an earlier record has to be
|
|
151
|
+
rewritten to make a later claim true, the later claim is false.
|
|
152
|
+
|
|
153
|
+
Prefer registry-native provenance (trusted publishing / OIDC) over
|
|
154
|
+
hand-assembled attestations. It removes the long-lived credential, and the
|
|
155
|
+
consumer can verify it without trusting a file you wrote.
|
|
156
|
+
|
|
157
|
+
## Automation is not a control
|
|
158
|
+
|
|
159
|
+
The most common false claim in release engineering is that implemented
|
|
160
|
+
automation proves a control is configured. It does not. A workflow that *would*
|
|
161
|
+
publish through a protected environment says nothing about whether the
|
|
162
|
+
environment exists, who can approve it, or who owns the package name.
|
|
163
|
+
|
|
164
|
+
Before claiming a control, record an attestation binding: the exact subject
|
|
165
|
+
(repository, branch, workflow, environment, package, or publisher account), who
|
|
166
|
+
observed it and with what authority, when, how long the observation is good
|
|
167
|
+
for, where revocation would be visible, and when revocation was last checked.
|
|
168
|
+
An attestation without an expiry and a revocation check is a screenshot.
|
|
169
|
+
|
|
170
|
+
Controls worth attesting for a real publishing pipeline: protected default
|
|
171
|
+
branch with required status checks, a release-specific required status, a
|
|
172
|
+
protected publication environment, repository-scoped credentials, exact package
|
|
173
|
+
name ownership in the registry, and exact trusted-publisher registration.
|
|
174
|
+
|
|
175
|
+
## Label release intent by outward-facing effect
|
|
176
|
+
|
|
177
|
+
The version label on a pull request is not paperwork describing the change; in
|
|
178
|
+
a label-driven pipeline it **is the decision to ship**. Exactly one of four
|
|
179
|
+
labels belongs on every pull request:
|
|
180
|
+
|
|
181
|
+
- **major** — a breaking change to public API or observable behavior.
|
|
182
|
+
- **minor** — new capability, backward compatible.
|
|
183
|
+
- **patch** — a fix, or a refactor with identical observable behavior.
|
|
184
|
+
- **no-release** — nothing a consumer could observe by upgrading.
|
|
185
|
+
|
|
186
|
+
The test is **outward-facing effect, not file location**. A change under a
|
|
187
|
+
source directory that only touches tests is not shippable; a one-line change to
|
|
188
|
+
a shipped package's behavior is, however small. Documentation, CI workflows,
|
|
189
|
+
test-only changes, and local tooling carry `no-release`.
|
|
190
|
+
|
|
191
|
+
`no-release` is a decision, not an omission — leaving the label off is
|
|
192
|
+
indistinguishable from forgetting it, so an unlabeled pull request stays an
|
|
193
|
+
error. Never default to `patch` when unsure: an unnecessary release burns a
|
|
194
|
+
version number, ships release notes describing nothing, and buries the releases
|
|
195
|
+
that matter.
|
|
196
|
+
|
|
197
|
+
Two consequences worth internalizing:
|
|
198
|
+
|
|
199
|
+
- **Group related small work into one pull request.** Several small merges
|
|
200
|
+
become several releases, and a stream of near-empty patch releases makes the
|
|
201
|
+
release history useless to the people it is written for. The pull request is
|
|
202
|
+
the release boundary; make it a coherent, describable change.
|
|
203
|
+
- **Folding one pull request into another means relabeling it.** When branch X
|
|
204
|
+
is merged as part of Y, X auto-closes *as merged* and fires its own publish
|
|
205
|
+
run under its own label. Relabel X to `no-release`; do not close it by hand,
|
|
206
|
+
or its author loses the attribution.
|
|
207
|
+
|
|
208
|
+
## Structure the release path so no step can vouch for itself
|
|
209
|
+
|
|
210
|
+
The ordering below is what makes the evidence non-circular. Each step consumes
|
|
211
|
+
only records that already existed when it started.
|
|
212
|
+
|
|
213
|
+
1. **Prerequisites first.** Every approval, evidence record, and control
|
|
214
|
+
attestation the release will cite already exists on the protected base
|
|
215
|
+
branch. A release request may not introduce, weaken, or grant its own
|
|
216
|
+
prerequisites.
|
|
217
|
+
2. **Freeze a preflight snapshot.** Bind the exact source revision, the exact
|
|
218
|
+
candidate artifact digest, and the exact prerequisite set — without including
|
|
219
|
+
the request that will cite it.
|
|
220
|
+
3. **One append-only request.** One version, one artifact, referencing the
|
|
221
|
+
preflight digest. Requests are never rewritten or reused; a defect in a
|
|
222
|
+
released version is corrected by a new version, never by editing the record
|
|
223
|
+
of the old one.
|
|
224
|
+
4. **Named review, then merge.** Merging is the human approval. Keep the merge
|
|
225
|
+
a true merge commit so the branch's real history survives, and bind the
|
|
226
|
+
approval to the parent commit so unrelated authority changes cannot be
|
|
227
|
+
batched in ahead of it.
|
|
228
|
+
5. **Publish, then write the publication receipt** with package identity,
|
|
229
|
+
artifact digest, provenance, SBOM, and checksums.
|
|
230
|
+
6. **Canary the released artifact against a real consumer** — a real downstream
|
|
231
|
+
repository or sample that actually depends on it, running its own build and
|
|
232
|
+
tests against the published version. A canary against a fixture proves the
|
|
233
|
+
pipeline; a canary against a real consumer proves the release. Publication
|
|
234
|
+
jobs must stop before promotion if it fails.
|
|
235
|
+
7. **Promote only on canary evidence**, and record the promotion separately.
|
|
236
|
+
8. **Support requires its own final named approval** for that exact released
|
|
237
|
+
artifact. Publication is not promotion, promotion is not support, and a
|
|
238
|
+
marketplace or registry listing is none of the three.
|
|
239
|
+
|
|
240
|
+
## Write the recovery disposition before you publish
|
|
241
|
+
|
|
242
|
+
Decide and record, per stage, what failure means — while you still have the
|
|
243
|
+
choice.
|
|
244
|
+
|
|
245
|
+
- **Before publication**, everything is reversible: delete the draft release,
|
|
246
|
+
close the index or subscriber pull request, remove the branch and tag.
|
|
247
|
+
- **After publication, most registries are immutable.** A published npm, NuGet,
|
|
248
|
+
or Maven version cannot be un-published in any way a consumer can rely on.
|
|
249
|
+
The honest recovery is roll-*forward*: publish a corrected version and move
|
|
250
|
+
the moving pointer (`latest`, a floating tag) back to a known-good exact
|
|
251
|
+
version. Never describe an immutable version as rolled back.
|
|
252
|
+
- **Automatic rollback is a capability, not an assumption.** Claim it only where
|
|
253
|
+
it is implemented and canaried; otherwise record it as disabled and name the
|
|
254
|
+
manual procedure.
|
|
255
|
+
- **Keep failed intermediate state for inspection** rather than deleting the
|
|
256
|
+
evidence of the failure.
|
|
257
|
+
|
|
258
|
+
State the disposition in terms a consumer can act on: which exact version to
|
|
259
|
+
pin to, and what the pinned version does not have.
|
|
260
|
+
|
|
261
|
+
## Never claim a tier you have not paid for
|
|
262
|
+
|
|
263
|
+
The final discipline is linguistic. "Available", "published", "preview",
|
|
264
|
+
"listed", and "supported" are different claims with different costs, and the
|
|
265
|
+
gap between them is where trust is lost.
|
|
266
|
+
|
|
267
|
+
- Publishing a package makes it **available**, not supported.
|
|
268
|
+
- A marketplace submission is not a listing; a listing is not support.
|
|
269
|
+
- Passing static validation makes an artifact **statically validated**, not
|
|
270
|
+
install-tested.
|
|
271
|
+
- Automation that could produce a receipt has not produced one.
|
|
272
|
+
|
|
273
|
+
When the evidence for a claim is missing, say which evidence is missing and what
|
|
274
|
+
the artifact *is* — not a hedged version of the claim you wanted to make.
|
|
275
|
+
|
|
276
|
+
## Verify
|
|
277
|
+
|
|
278
|
+
- The artifact is classified as compiled/assembled or as source read from a git
|
|
279
|
+
ref, and the ceremony matches that classification.
|
|
280
|
+
- The chosen tier is the lowest one that supports the intended claim.
|
|
281
|
+
- Every phase result is pass, fail, or explicitly blocked — never a silent skip.
|
|
282
|
+
- No synthetic or fixture observation is counted as install, behavior,
|
|
283
|
+
lifecycle, or canary evidence.
|
|
284
|
+
- Rollback to a prior exact version and preservation of consumer-owned files
|
|
285
|
+
were both actually exercised.
|
|
286
|
+
- Checksums, provenance, and SBOM exist for compiled or assembled artifacts and
|
|
287
|
+
are absent, deliberately and explainably, for git-ref source delivery.
|
|
288
|
+
- Every claimed external control has an attestation with subject, issuer,
|
|
289
|
+
validity, and a revocation check.
|
|
290
|
+
- The pull request carries exactly one of `major`, `minor`, `patch`, or
|
|
291
|
+
`no-release`, chosen by outward-facing effect.
|
|
292
|
+
- No record was rewritten or backdated to satisfy a later stage.
|
|
293
|
+
- A canary ran against a real consumer before promotion.
|
|
294
|
+
- The recovery disposition is written down and does not claim an immutable
|
|
295
|
+
version can be rolled back.
|
|
296
|
+
- Nothing is described as supported without a named approval of that exact
|
|
297
|
+
released artifact.
|