@ontrails/testing 1.0.0-beta.39 → 1.0.0-beta.41

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @ontrails/testing
2
2
 
3
+ ## 1.0.0-beta.41
4
+
5
+ ## 1.0.0-beta.40
6
+
7
+ ### Minor Changes
8
+
9
+ - [`5adb995`](https://github.com/outfitter-dev/trails/commit/5adb99551c2dda6190d46cce7f60bb08d63c99aa): Complete the v1 hard cutover from the authored `blaze` field to
10
+ `implementation` across trail contracts, surface projections, tests, examples,
11
+ and public source-analysis helpers. Existing applications must rename authored
12
+ trail behavior fields and direct trail-object access before upgrading.
13
+ - [`6712075`](https://github.com/outfitter-dev/trails/commit/67120754df3f614c7f4dd98be1fa0ba9d69b7765): Complete the v1 hard cutover from the `contour` domain-object declaration
14
+ vocabulary to `entity` across contracts, topo facts, store helpers, Warden,
15
+ Wayfinder, operator surfaces, examples, and generated locks. Existing
16
+ applications must rename contour APIs, run `trails dev reset --yes` to discard
17
+ pre-cutover local Topographer snapshots, and then recompile committed
18
+ `trails.lock` artifacts before upgrading. Those derived snapshots are
19
+ intentionally not read through a compatibility layer.
20
+ The entity-shaped wire contract advances `TopoGraph` and split lock manifests
21
+ from schema version 3 to 4; old split artifacts fail with regeneration guidance,
22
+ while the canonical root `trails.lock` remains schema version 5.
23
+ Wayfinder reports those stale rows as topo-store drift while keeping current
24
+ committed lock facts available for inspection.
25
+
3
26
  ## 1.0.0-beta.39
4
27
 
5
28
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/testing",
3
- "version": "1.0.0-beta.39",
3
+ "version": "1.0.0-beta.41",
4
4
  "files": [
5
5
  "src/**/*.ts",
6
6
  "!src/**/__tests__/**",
@@ -27,15 +27,15 @@
27
27
  "clean": "rm -rf dist *.tsbuildinfo"
28
28
  },
29
29
  "devDependencies": {
30
- "@ontrails/drizzle": "^1.0.0-beta.39",
31
- "@ontrails/store": "^1.0.0-beta.39"
30
+ "@ontrails/drizzle": "^1.0.0-beta.41",
31
+ "@ontrails/store": "^1.0.0-beta.41"
32
32
  },
33
33
  "peerDependencies": {
34
- "@ontrails/cli": "^1.0.0-beta.39",
35
- "@ontrails/core": "^1.0.0-beta.39",
36
- "@ontrails/http": "^1.0.0-beta.39",
37
- "@ontrails/mcp": "^1.0.0-beta.39",
38
- "@ontrails/observe": "^1.0.0-beta.39",
34
+ "@ontrails/cli": "^1.0.0-beta.41",
35
+ "@ontrails/core": "^1.0.0-beta.41",
36
+ "@ontrails/http": "^1.0.0-beta.41",
37
+ "@ontrails/mcp": "^1.0.0-beta.41",
38
+ "@ontrails/observe": "^1.0.0-beta.41",
39
39
  "zod": "^4.3.5"
40
40
  },
41
41
  "peerDependenciesMeta": {
package/src/detours.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * testDetours — validate the live detour contract for every trail.
3
3
  *
4
- * Pure structural validation. No blaze or detour recovery execution needed.
4
+ * Pure structural validation. No implementation or detour recovery execution needed.
5
5
  */
6
6
 
7
7
  import { describe, test } from 'bun:test';
@@ -1,6 +1,6 @@
1
- import type { AnyContour, Trail, TrailExample } from '@ontrails/core';
1
+ import type { AnyEntity, Trail, TrailExample } from '@ontrails/core';
2
2
  import {
3
- getContourReferences,
3
+ getEntityReferences,
4
4
  getTrailVersionEntryKind,
5
5
  isArchivedTrailVersionEntry,
6
6
  } from '@ontrails/core';
@@ -23,7 +23,7 @@ const normalizeComposeRef = (value: string | { readonly id: string }): string =>
23
23
  typeof value === 'string' ? value : value.id;
24
24
 
25
25
  /**
26
- * Tracks examples that `deriveTrailExamples` synthesizes from contour
26
+ * Tracks examples that `deriveTrailExamples` synthesizes from entity
27
27
  * fixtures. Authored examples are passed through untouched and never
28
28
  * appear here, so consumers can distinguish the two by identity.
29
29
  *
@@ -34,15 +34,15 @@ const normalizeComposeRef = (value: string | { readonly id: string }): string =>
34
34
  const derivedExamples = new WeakSet<TrailExample<unknown, unknown>>();
35
35
 
36
36
  /**
37
- * Returns `true` if the given example was synthesized from contour fixtures
37
+ * Returns `true` if the given example was synthesized from entity fixtures
38
38
  * by `deriveTrailExamples`, `false` if it was authored on the trail.
39
39
  */
40
40
  export const isDerivedExample = (
41
41
  example: TrailExample<unknown, unknown>
42
42
  ): boolean => derivedExamples.has(example);
43
43
 
44
- interface ContourFixture {
45
- readonly contour: AnyContour;
44
+ interface EntityFixture {
45
+ readonly entity: AnyEntity;
46
46
  readonly example: ExampleRecord;
47
47
  readonly index: number;
48
48
  }
@@ -51,51 +51,51 @@ const capitalize = (value: string): string =>
51
51
  value.length === 0 ? value : value.slice(0, 1).toUpperCase() + value.slice(1);
52
52
 
53
53
  const collectReferenceMap = (
54
- contours: readonly AnyContour[]
55
- ): ReadonlyMap<string, ReturnType<typeof getContourReferences>> => {
56
- const contourNames = new Set(contours.map((contour) => contour.name));
54
+ entities: readonly AnyEntity[]
55
+ ): ReadonlyMap<string, ReturnType<typeof getEntityReferences>> => {
56
+ const entityNames = new Set(entities.map((entity) => entity.name));
57
57
 
58
58
  return new Map(
59
- contours.map((contour) => [
60
- contour.name,
61
- getContourReferences(contour).filter((reference) =>
62
- contourNames.has(reference.contour)
59
+ entities.map((entity) => [
60
+ entity.name,
61
+ getEntityReferences(entity).filter((reference) =>
62
+ entityNames.has(reference.entity)
63
63
  ),
64
64
  ])
65
65
  );
66
66
  };
67
67
 
68
- const getIdentityValue = (fixture: ContourFixture): unknown =>
69
- fixture.example[fixture.contour.identity];
68
+ const getIdentityValue = (fixture: EntityFixture): unknown =>
69
+ fixture.example[fixture.entity.identity];
70
70
 
71
71
  const candidateMatchesSelectedReference = (
72
- candidate: ContourFixture,
73
- target: ContourFixture,
74
- reference: ReturnType<typeof getContourReferences>[number]
72
+ candidate: EntityFixture,
73
+ target: EntityFixture,
74
+ reference: ReturnType<typeof getEntityReferences>[number]
75
75
  ): boolean =>
76
76
  Object.is(candidate.example[reference.field], getIdentityValue(target));
77
77
 
78
78
  const selectedMatchesCandidateReference = (
79
- fixture: ContourFixture,
80
- candidate: ContourFixture,
81
- reference: ReturnType<typeof getContourReferences>[number]
79
+ fixture: EntityFixture,
80
+ candidate: EntityFixture,
81
+ reference: ReturnType<typeof getEntityReferences>[number]
82
82
  ): boolean =>
83
83
  Object.is(fixture.example[reference.field], getIdentityValue(candidate));
84
84
 
85
85
  const matchesCandidateReferences = (
86
- candidate: ContourFixture,
87
- selected: readonly ContourFixture[],
88
- referencesByContour: ReadonlyMap<
86
+ candidate: EntityFixture,
87
+ selected: readonly EntityFixture[],
88
+ referencesByEntity: ReadonlyMap<
89
89
  string,
90
- ReturnType<typeof getContourReferences>
90
+ ReturnType<typeof getEntityReferences>
91
91
  >
92
92
  ): boolean => {
93
93
  const candidateReferences =
94
- referencesByContour.get(candidate.contour.name) ?? [];
94
+ referencesByEntity.get(candidate.entity.name) ?? [];
95
95
 
96
96
  for (const reference of candidateReferences) {
97
97
  const target = selected.find(
98
- (fixture) => fixture.contour.name === reference.contour
98
+ (fixture) => fixture.entity.name === reference.entity
99
99
  );
100
100
  if (target === undefined) {
101
101
  continue;
@@ -109,18 +109,17 @@ const matchesCandidateReferences = (
109
109
  };
110
110
 
111
111
  const matchesSelectedReferences = (
112
- candidate: ContourFixture,
113
- selected: readonly ContourFixture[],
114
- referencesByContour: ReadonlyMap<
112
+ candidate: EntityFixture,
113
+ selected: readonly EntityFixture[],
114
+ referencesByEntity: ReadonlyMap<
115
115
  string,
116
- ReturnType<typeof getContourReferences>
116
+ ReturnType<typeof getEntityReferences>
117
117
  >
118
118
  ): boolean => {
119
119
  for (const fixture of selected) {
120
- const fixtureReferences =
121
- referencesByContour.get(fixture.contour.name) ?? [];
120
+ const fixtureReferences = referencesByEntity.get(fixture.entity.name) ?? [];
122
121
  for (const reference of fixtureReferences) {
123
- if (reference.contour !== candidate.contour.name) {
122
+ if (reference.entity !== candidate.entity.name) {
124
123
  continue;
125
124
  }
126
125
  if (!selectedMatchesCandidateReference(fixture, candidate, reference)) {
@@ -133,43 +132,43 @@ const matchesSelectedReferences = (
133
132
  };
134
133
 
135
134
  const matchesKnownReferences = (
136
- candidate: ContourFixture,
137
- selected: readonly ContourFixture[],
138
- referencesByContour: ReadonlyMap<
135
+ candidate: EntityFixture,
136
+ selected: readonly EntityFixture[],
137
+ referencesByEntity: ReadonlyMap<
139
138
  string,
140
- ReturnType<typeof getContourReferences>
139
+ ReturnType<typeof getEntityReferences>
141
140
  >
142
141
  ): boolean =>
143
- matchesCandidateReferences(candidate, selected, referencesByContour) &&
144
- matchesSelectedReferences(candidate, selected, referencesByContour);
142
+ matchesCandidateReferences(candidate, selected, referencesByEntity) &&
143
+ matchesSelectedReferences(candidate, selected, referencesByEntity);
145
144
 
146
- const selectContourFixtures = (
147
- contours: readonly AnyContour[],
148
- referencesByContour: ReadonlyMap<
145
+ const selectEntityFixtures = (
146
+ entities: readonly AnyEntity[],
147
+ referencesByEntity: ReadonlyMap<
149
148
  string,
150
- ReturnType<typeof getContourReferences>
149
+ ReturnType<typeof getEntityReferences>
151
150
  >,
152
151
  index = 0,
153
- selected: readonly ContourFixture[] = []
154
- ): readonly (readonly ContourFixture[])[] => {
155
- const contour = contours[index];
156
- if (contour === undefined) {
152
+ selected: readonly EntityFixture[] = []
153
+ ): readonly (readonly EntityFixture[])[] => {
154
+ const entity = entities[index];
155
+ if (entity === undefined) {
157
156
  return [selected];
158
157
  }
159
158
 
160
- const examples = contour.examples ?? [];
159
+ const examples = entity.examples ?? [];
161
160
  const matchingFixtures = examples.flatMap((example, exampleIndex) => {
162
161
  const fixture = {
163
- contour,
162
+ entity,
164
163
  example: example as ExampleRecord,
165
164
  index: exampleIndex,
166
- } satisfies ContourFixture;
165
+ } satisfies EntityFixture;
167
166
 
168
- if (!matchesKnownReferences(fixture, selected, referencesByContour)) {
167
+ if (!matchesKnownReferences(fixture, selected, referencesByEntity)) {
169
168
  return [];
170
169
  }
171
170
 
172
- return selectContourFixtures(contours, referencesByContour, index + 1, [
171
+ return selectEntityFixtures(entities, referencesByEntity, index + 1, [
173
172
  ...selected,
174
173
  fixture,
175
174
  ]);
@@ -179,38 +178,37 @@ const selectContourFixtures = (
179
178
  };
180
179
 
181
180
  /**
182
- * Merge selected contour fixtures into a single candidate input object.
181
+ * Merge selected entity fixtures into a single candidate input object.
183
182
  *
184
183
  * The resulting record contains:
185
- * - `<contour>`: the full fixture payload keyed by contour name.
186
- * - `<contour><Identity>`: the fixture's identity value on a prefixed key.
187
- * - `<contour><Field>`: every fixture field on a prefixed key.
188
- * - Unqualified `<field>` keys: first-write-wins across contours.
184
+ * - `<entity>`: the full fixture payload keyed by entity name.
185
+ * - `<entity><Identity>`: the fixture's identity value on a prefixed key.
186
+ * - `<entity><Field>`: every fixture field on a prefixed key.
187
+ * - Unqualified `<field>` keys: first-write-wins across entities.
189
188
  *
190
189
  * The first-write-wins behaviour on unqualified keys is intentional but can
191
- * silently drop a later contour's value when two contours share a field name
190
+ * silently drop a later entity's value when two entities share a field name
192
191
  * (e.g. both declare `id`). The prefixed aliases above are unambiguous and
193
192
  * always written, so schemas that consume the prefixed form are unaffected;
194
193
  * schemas that rely on the bare field name should disambiguate via the
195
194
  * prefixed alias instead.
196
195
  */
197
196
  const buildDerivedInput = (
198
- fixtures: readonly ContourFixture[]
197
+ fixtures: readonly EntityFixture[]
199
198
  ): Record<string, unknown> => {
200
199
  const candidate: Record<string, unknown> = {};
201
200
 
202
201
  for (const fixture of fixtures) {
203
- candidate[fixture.contour.name] = fixture.example;
204
- candidate[
205
- `${fixture.contour.name}${capitalize(fixture.contour.identity)}`
206
- ] = getIdentityValue(fixture);
202
+ candidate[fixture.entity.name] = fixture.example;
203
+ candidate[`${fixture.entity.name}${capitalize(fixture.entity.identity)}`] =
204
+ getIdentityValue(fixture);
207
205
 
208
206
  for (const [field, value] of Object.entries(fixture.example)) {
209
207
  if (!Object.hasOwn(candidate, field)) {
210
208
  candidate[field] = value;
211
209
  }
212
210
 
213
- candidate[`${fixture.contour.name}${capitalize(field)}`] = value;
211
+ candidate[`${fixture.entity.name}${capitalize(field)}`] = value;
214
212
  }
215
213
  }
216
214
 
@@ -248,7 +246,7 @@ const projectInputForSchema = (
248
246
  };
249
247
 
250
248
  /**
251
- * Derive an expected output value from the selected contour fixtures when
249
+ * Derive an expected output value from the selected entity fixtures when
252
250
  * exactly one fixture's payload satisfies the trail's output schema.
253
251
  *
254
252
  * Returns `undefined` when the trail has no output schema, when no fixture
@@ -261,22 +259,22 @@ const projectInputForSchema = (
261
259
  */
262
260
  const deriveExpectedValue = (
263
261
  trail: Trail<unknown, unknown, unknown>,
264
- fixtures: readonly ContourFixture[]
262
+ fixtures: readonly EntityFixture[]
265
263
  ): unknown => {
266
264
  if (trail.output === undefined) {
267
265
  return undefined;
268
266
  }
269
267
 
270
268
  const outputSchema = trail.output;
271
- const contourMatches = fixtures
269
+ const entityMatches = fixtures
272
270
  .map((fixture) => outputSchema.safeParse(fixture.example))
273
271
  .filter((candidate) => candidate.success);
274
272
 
275
- if (contourMatches.length !== 1) {
273
+ if (entityMatches.length !== 1) {
276
274
  return undefined;
277
275
  }
278
276
 
279
- const [singleMatch] = contourMatches;
277
+ const [singleMatch] = entityMatches;
280
278
  if (singleMatch === undefined) {
281
279
  return undefined;
282
280
  }
@@ -284,14 +282,14 @@ const deriveExpectedValue = (
284
282
  };
285
283
 
286
284
  const formatFixtureName = (
287
- fixtures: readonly ContourFixture[],
285
+ fixtures: readonly EntityFixture[],
288
286
  index: number
289
287
  ): string => {
290
288
  const label = fixtures
291
289
  .map((fixture) => {
292
290
  const identity = getIdentityValue(fixture);
293
291
  const fallback = fixture.index + 1;
294
- return `${fixture.contour.name}:${String(identity ?? fallback)}`;
292
+ return `${fixture.entity.name}:${String(identity ?? fallback)}`;
295
293
  })
296
294
  .join(', ');
297
295
 
@@ -301,7 +299,7 @@ const formatFixtureName = (
301
299
  };
302
300
 
303
301
  /**
304
- * Prefer authored trail examples and fall back to contour-derived fixtures.
302
+ * Prefer authored trail examples and fall back to entity-derived fixtures.
305
303
  *
306
304
  * Examples returned by this helper come from one of two provenances:
307
305
  * - **Authored.** When `trail.examples` is non-empty, its entries are
@@ -309,14 +307,14 @@ const formatFixtureName = (
309
307
  * full invariants — including composing-coverage assertions in
310
308
  * `testExamples`.
311
309
  * - **Derived.** When there are no authored examples but the trail has
312
- * contours with examples, candidate inputs are synthesized from contour
310
+ * entities with examples, candidate inputs are synthesized from entity
313
311
  * fixtures and validated against `trail.input`. These are opportunistic
314
- * coverage that exists to let `testAll(app)` exercise contour-backed
312
+ * coverage that exists to let `testAll(app)` exercise entity-backed
315
313
  * trails without per-test setup; they are not guaranteed to exercise
316
314
  * every composition branch, so consumers should relax invariants that
317
315
  * only make sense for authored inputs (see `isDerivedExample`).
318
316
  *
319
- * Contour examples stay as the raw input payload so Trails validation /
317
+ * Entity examples stay as the raw input payload so Trails validation /
320
318
  * transforms still happen exactly once inside the normal test execution
321
319
  * path. Derived examples are additionally tagged via a module-level
322
320
  * `WeakSet` so consumers can detect them without widening the public
@@ -329,24 +327,20 @@ export const deriveTrailExamples = (
329
327
  return trail.examples;
330
328
  }
331
329
 
332
- if (trail.contours.length === 0) {
330
+ if (trail.entities.length === 0) {
333
331
  return [];
334
332
  }
335
333
 
336
334
  if (
337
- trail.contours.some(
338
- (contour) =>
339
- contour.examples === undefined || contour.examples.length === 0
335
+ trail.entities.some(
336
+ (entity) => entity.examples === undefined || entity.examples.length === 0
340
337
  )
341
338
  ) {
342
339
  return [];
343
340
  }
344
341
 
345
- const referencesByContour = collectReferenceMap(trail.contours);
346
- const fixtureSets = selectContourFixtures(
347
- trail.contours,
348
- referencesByContour
349
- );
342
+ const referencesByEntity = collectReferenceMap(trail.entities);
343
+ const fixtureSets = selectEntityFixtures(trail.entities, referencesByEntity);
350
344
 
351
345
  return fixtureSets.flatMap((fixtures, index) => {
352
346
  const merged = buildDerivedInput(fixtures);
package/src/examples.ts CHANGED
@@ -375,7 +375,7 @@ export const testExamples = (
375
375
 
376
376
  // Composition trails: use recording compose and check coverage.
377
377
  //
378
- // Composing coverage only runs against AUTHORED examples. Contour-derived
378
+ // Composing coverage only runs against AUTHORED examples. Entity-derived
379
379
  // fixtures are opportunistic coverage that may not exercise every
380
380
  // `ctx.compose()` branch in the trail, so asserting coverage against them
381
381
  // would produce false failures for trails whose authored intent was a
package/src/scenario.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Scenarios express multi-trail flows as structured data — arrays of steps
5
5
  * with compose-step references via `ref()`. Each step invokes a trail through
6
- * the normal execution pipeline (validation, layers, blaze, Result).
6
+ * the normal execution pipeline (validation, layers, implementation, Result).
7
7
  */
8
8
 
9
9
  import { describe, test } from 'bun:test';
package/src/trail.ts CHANGED
@@ -82,7 +82,7 @@ const runScenario = async (
82
82
  }
83
83
  const validatedInput = expectOk(validated);
84
84
 
85
- const result = await trailDef.blaze(validatedInput, testCtx);
85
+ const result = await trailDef.implementation(validatedInput, testCtx);
86
86
  assertScenarioResult(result, scenario, trailDef);
87
87
  };
88
88