@ontrails/warden 1.0.0-beta.18 → 1.0.0-beta.19

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 (73) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +12 -30
  3. package/bin/warden.ts +29 -1
  4. package/package.json +9 -8
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +238 -60
  7. package/src/command.ts +26 -0
  8. package/src/drift.ts +1 -1
  9. package/src/fix.ts +120 -0
  10. package/src/formatters.ts +14 -2
  11. package/src/guide.ts +11 -0
  12. package/src/index.ts +31 -1
  13. package/src/rules/ast.ts +84 -25
  14. package/src/rules/circular-refs.ts +1 -1
  15. package/src/rules/{cross-declarations.ts → composes-declarations.ts} +198 -89
  16. package/src/rules/context-no-surface-types.ts +4 -4
  17. package/src/rules/contour-exists.ts +1 -1
  18. package/src/rules/dead-internal-trail.ts +22 -9
  19. package/src/rules/fires-declarations.ts +3 -3
  20. package/src/rules/implementation-returns-result.ts +269 -76
  21. package/src/rules/index.ts +51 -3
  22. package/src/rules/intent-propagation.ts +6 -6
  23. package/src/rules/metadata.ts +117 -12
  24. package/src/rules/missing-visibility.ts +14 -14
  25. package/src/rules/no-destructured-compose.ts +192 -0
  26. package/src/rules/no-direct-implementation-call.ts +2 -2
  27. package/src/rules/no-legacy-layer-imports.ts +19 -1
  28. package/src/rules/no-redundant-result-error-wrap.ts +331 -0
  29. package/src/rules/no-sync-result-assumption.ts +2 -2
  30. package/src/rules/no-throw-in-implementation.ts +2 -3
  31. package/src/rules/no-top-level-surface.ts +389 -0
  32. package/src/rules/on-references-exist.ts +1 -1
  33. package/src/rules/reference-exists.ts +1 -1
  34. package/src/rules/registry-names.ts +28 -2
  35. package/src/rules/resolved-import-boundary.ts +2 -2
  36. package/src/rules/resource-declarations.ts +4 -4
  37. package/src/rules/resource-exists.ts +1 -1
  38. package/src/rules/resource-mock-coverage.ts +115 -0
  39. package/src/rules/scan.ts +39 -0
  40. package/src/rules/trail-versioning-source.ts +1094 -0
  41. package/src/rules/trail-versioning-topo.ts +172 -0
  42. package/src/rules/types.ts +87 -5
  43. package/src/rules/valid-detour-contract.ts +1 -1
  44. package/src/rules/warden-export-symmetry.ts +1 -1
  45. package/src/rules/warden-rules-use-ast.ts +2 -2
  46. package/src/trails/activation-orphan.trail.ts +4 -1
  47. package/src/trails/composes-declarations.trail.ts +22 -0
  48. package/src/trails/dead-internal-trail.trail.ts +4 -4
  49. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  50. package/src/trails/fork-without-preserved-blaze.trail.ts +31 -0
  51. package/src/trails/index.ts +12 -1
  52. package/src/trails/intent-propagation.trail.ts +3 -3
  53. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  54. package/src/trails/missing-visibility.trail.ts +2 -2
  55. package/src/trails/no-destructured-compose.trail.ts +44 -0
  56. package/src/trails/no-direct-implementation-call.trail.ts +2 -2
  57. package/src/trails/no-legacy-layer-imports.trail.ts +6 -0
  58. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  59. package/src/trails/no-top-level-surface.trail.ts +43 -0
  60. package/src/trails/pending-force.trail.ts +21 -0
  61. package/src/trails/public-internal-deep-imports.trail.ts +1 -1
  62. package/src/trails/resolved-import-boundary.trail.ts +4 -4
  63. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  64. package/src/trails/run.ts +2 -2
  65. package/src/trails/schema.ts +32 -6
  66. package/src/trails/signal-graph-coaching.trail.ts +4 -1
  67. package/src/trails/unmaterialized-activation-source.trail.ts +4 -1
  68. package/src/trails/valid-detour-contract.trail.ts +1 -1
  69. package/src/trails/version-gap.trail.ts +35 -0
  70. package/src/trails/version-pinned-compose.trail.ts +23 -0
  71. package/src/trails/version-without-examples.trail.ts +38 -0
  72. package/src/trails/wrap-rule.ts +5 -3
  73. package/src/trails/cross-declarations.trail.ts +0 -22
@@ -2,7 +2,7 @@ import { activationOrphan } from './activation-orphan.js';
2
2
  import { circularRefs } from './circular-refs.js';
3
3
  import { contourExists } from './contour-exists.js';
4
4
  import { contextNoSurfaceTypes } from './context-no-surface-types.js';
5
- import { crossDeclarations } from './cross-declarations.js';
5
+ import { composesDeclarations } from './composes-declarations.js';
6
6
  import { deadInternalTrail } from './dead-internal-trail.js';
7
7
  import { draftFileMarking } from './draft-file-marking.js';
8
8
  import { draftVisibleDebt } from './draft-visible-debt.js';
@@ -17,12 +17,15 @@ import { layerFieldNameDrift } from './layer-field-name-drift.js';
17
17
  import { missingVisibility } from './missing-visibility.js';
18
18
  import { missingReconcile } from './missing-reconcile.js';
19
19
  import { noDevPermitInSource } from './no-dev-permit-in-source.js';
20
+ import { noDestructuredCompose } from './no-destructured-compose.js';
20
21
  import { noLegacyLayerImports } from './no-legacy-layer-imports.js';
21
22
  import { noDirectImplementationCall } from './no-direct-implementation-call.js';
22
23
  import { noNativeErrorResult } from './no-native-error-result.js';
24
+ import { noRedundantResultErrorWrap } from './no-redundant-result-error-wrap.js';
23
25
  import { noSyncResultAssumption } from './no-sync-result-assumption.js';
24
26
  import { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
25
27
  import { noThrowInImplementation } from './no-throw-in-implementation.js';
28
+ import { noTopLevelSurface } from './no-top-level-surface.js';
26
29
  import { onReferencesExist } from './on-references-exist.js';
27
30
  import { orphanedSignal } from './orphaned-signal.js';
28
31
  import { ownerProjectionParity } from './owner-projection-parity.js';
@@ -37,9 +40,21 @@ import { resolvedImportBoundary } from './resolved-import-boundary.js';
37
40
  import { resourceDeclarations } from './resource-declarations.js';
38
41
  import { resourceExists } from './resource-exists.js';
39
42
  import { resourceIdGrammar } from './resource-id-grammar.js';
43
+ import { resourceMockCoverage } from './resource-mock-coverage.js';
40
44
  import { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
41
45
  import { signalGraphCoaching } from './signal-graph-coaching.js';
42
46
  import { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
47
+ import {
48
+ forkWithoutPreservedBlaze,
49
+ markerSchemaUnsupported,
50
+ versionPinnedCompose,
51
+ } from './trail-versioning-source.js';
52
+ import {
53
+ deprecationWithoutGuidance,
54
+ pendingForce,
55
+ versionGap,
56
+ versionWithoutExamples,
57
+ } from './trail-versioning-topo.js';
43
58
  import type { TopoAwareWardenRule, WardenRule } from './types.js';
44
59
  import { unmaterializedActivationSource } from './unmaterialized-activation-source.js';
45
60
  import { unreachableDetourShadowing } from './unreachable-detour-shadowing.js';
@@ -50,6 +65,11 @@ import { wardenRulesUseAst } from './warden-rules-use-ast.js';
50
65
  import { webhookRouteCollision } from './webhook-route-collision.js';
51
66
 
52
67
  export type {
68
+ WardenFix,
69
+ WardenFixCapability,
70
+ WardenFixClass,
71
+ WardenFixEdit,
72
+ WardenFixSafety,
53
73
  WardenGuidance,
54
74
  WardenGuidanceLink,
55
75
  WardenRuleLifecycle,
@@ -70,6 +90,8 @@ export {
70
90
  builtinWardenRuleMetadata,
71
91
  getWardenRuleMetadata,
72
92
  listWardenRuleMetadata,
93
+ wardenFixClasses,
94
+ wardenFixSafeties,
73
95
  wardenRuleConcerns,
74
96
  wardenRuleLifecycleStates,
75
97
  wardenRuleScopes,
@@ -82,7 +104,7 @@ export { noThrowInImplementation } from './no-throw-in-implementation.js';
82
104
  export { circularRefs } from './circular-refs.js';
83
105
  export { contourExists } from './contour-exists.js';
84
106
  export { contextNoSurfaceTypes } from './context-no-surface-types.js';
85
- export { crossDeclarations } from './cross-declarations.js';
107
+ export { composesDeclarations } from './composes-declarations.js';
86
108
  export { deadInternalTrail } from './dead-internal-trail.js';
87
109
  export { draftFileMarking } from './draft-file-marking.js';
88
110
  export { draftVisibleDebt } from './draft-visible-debt.js';
@@ -97,12 +119,15 @@ export { missingVisibility } from './missing-visibility.js';
97
119
  export { missingReconcile } from './missing-reconcile.js';
98
120
  export { onReferencesExist } from './on-references-exist.js';
99
121
  export { noDevPermitInSource } from './no-dev-permit-in-source.js';
122
+ export { noDestructuredCompose } from './no-destructured-compose.js';
100
123
  export { noLegacyLayerImports } from './no-legacy-layer-imports.js';
101
124
  export { noDirectImplementationCall } from './no-direct-implementation-call.js';
102
125
  export { noNativeErrorResult } from './no-native-error-result.js';
126
+ export { noRedundantResultErrorWrap } from './no-redundant-result-error-wrap.js';
103
127
  export { noSyncResultAssumption } from './no-sync-result-assumption.js';
104
128
  export { implementationReturnsResult } from './implementation-returns-result.js';
105
129
  export { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
130
+ export { noTopLevelSurface } from './no-top-level-surface.js';
106
131
  export { orphanedSignal } from './orphaned-signal.js';
107
132
  export { ownerProjectionParity } from './owner-projection-parity.js';
108
133
  export { permitGovernance } from './permit-governance.js';
@@ -116,9 +141,21 @@ export { resolvedImportBoundary } from './resolved-import-boundary.js';
116
141
  export { resourceDeclarations } from './resource-declarations.js';
117
142
  export { resourceExists } from './resource-exists.js';
118
143
  export { resourceIdGrammar } from './resource-id-grammar.js';
144
+ export { resourceMockCoverage } from './resource-mock-coverage.js';
119
145
  export { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
120
146
  export { signalGraphCoaching } from './signal-graph-coaching.js';
121
147
  export { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
148
+ export {
149
+ forkWithoutPreservedBlaze,
150
+ markerSchemaUnsupported,
151
+ versionPinnedCompose,
152
+ } from './trail-versioning-source.js';
153
+ export {
154
+ deprecationWithoutGuidance,
155
+ pendingForce,
156
+ versionGap,
157
+ versionWithoutExamples,
158
+ } from './trail-versioning-topo.js';
122
159
  export { unmaterializedActivationSource } from './unmaterialized-activation-source.js';
123
160
  export { unreachableDetourShadowing } from './unreachable-detour-shadowing.js';
124
161
  export { validDetourContract } from './valid-detour-contract.js';
@@ -134,7 +171,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
134
171
  [circularRefs.name, circularRefs],
135
172
  [contourExists.name, contourExists],
136
173
  [contextNoSurfaceTypes.name, contextNoSurfaceTypes],
137
- [crossDeclarations.name, crossDeclarations],
174
+ [composesDeclarations.name, composesDeclarations],
138
175
  [deadInternalTrail.name, deadInternalTrail],
139
176
  [draftFileMarking.name, draftFileMarking],
140
177
  [draftVisibleDebt.name, draftVisibleDebt],
@@ -156,19 +193,26 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
156
193
  [resolvedImportBoundary.name, resolvedImportBoundary],
157
194
  [resourceIdGrammar.name, resourceIdGrammar],
158
195
  [resourceExists.name, resourceExists],
196
+ [resourceMockCoverage.name, resourceMockCoverage],
159
197
  [preferSchemaInference.name, preferSchemaInference],
160
198
  [staticResourceAccessorPreference.name, staticResourceAccessorPreference],
161
199
  [validDescribeRefs.name, validDescribeRefs],
162
200
  [noDevPermitInSource.name, noDevPermitInSource],
201
+ [noDestructuredCompose.name, noDestructuredCompose],
163
202
  [noDirectImplementationCall.name, noDirectImplementationCall],
164
203
  [noLegacyLayerImports.name, noLegacyLayerImports],
165
204
  [noNativeErrorResult.name, noNativeErrorResult],
205
+ [noRedundantResultErrorWrap.name, noRedundantResultErrorWrap],
166
206
  [noSyncResultAssumption.name, noSyncResultAssumption],
167
207
  [implementationReturnsResult.name, implementationReturnsResult],
168
208
  [noThrowInDetourRecover.name, noThrowInDetourRecover],
209
+ [noTopLevelSurface.name, noTopLevelSurface],
169
210
  [unreachableDetourShadowing.name, unreachableDetourShadowing],
170
211
  [wardenExportSymmetry.name, wardenExportSymmetry],
171
212
  [wardenRulesUseAst.name, wardenRulesUseAst],
213
+ [forkWithoutPreservedBlaze.name, forkWithoutPreservedBlaze],
214
+ [markerSchemaUnsupported.name, markerSchemaUnsupported],
215
+ [versionPinnedCompose.name, versionPinnedCompose],
172
216
  ]);
173
217
 
174
218
  /**
@@ -195,5 +239,9 @@ export const wardenTopoRules: ReadonlyMap<string, TopoAwareWardenRule> =
195
239
  [signalGraphCoaching.name, signalGraphCoaching],
196
240
  [unmaterializedActivationSource.name, unmaterializedActivationSource],
197
241
  [validDetourContract.name, validDetourContract],
242
+ [deprecationWithoutGuidance.name, deprecationWithoutGuidance],
243
+ [pendingForce.name, pendingForce],
244
+ [versionGap.name, versionGap],
245
+ [versionWithoutExamples.name, versionWithoutExamples],
198
246
  [webhookRouteCollision.name, webhookRouteCollision],
199
247
  ]);
@@ -2,7 +2,7 @@ import type { Intent } from '@ontrails/core';
2
2
  import {
3
3
  collectNamedTrailIds,
4
4
  collectTrailIntentsById,
5
- extractDefinitionCrossTargetIds,
5
+ extractDefinitionComposeTargetIds,
6
6
  findTrailDefinitions,
7
7
  offsetToLine,
8
8
  parse,
@@ -24,12 +24,12 @@ const buildIntentPropagationDiagnostic = (
24
24
  ): WardenDiagnostic => ({
25
25
  filePath,
26
26
  line,
27
- message: `Trail "${trailId}" declares intent: 'read' but crosses "${targetTrailId}" with intent: '${targetIntent}'. Read trails must not compose write or destroy side effects.`,
27
+ message: `Trail "${trailId}" declares intent: 'read' but composes "${targetTrailId}" with intent: '${targetIntent}'. Read trails must not compose write or destroy side effects.`,
28
28
  rule: 'intent-propagation',
29
29
  severity: 'warn',
30
30
  });
31
31
 
32
- const buildDiagnosticsForCrossTargets = (
32
+ const buildDiagnosticsForComposeTargets = (
33
33
  trailId: string,
34
34
  targetTrailIds: readonly string[],
35
35
  filePath: string,
@@ -64,9 +64,9 @@ const buildDiagnosticsForTrail = (
64
64
  return [];
65
65
  }
66
66
 
67
- return buildDiagnosticsForCrossTargets(
67
+ return buildDiagnosticsForComposeTargets(
68
68
  def.id,
69
- extractDefinitionCrossTargetIds(def.config, sourceCode, namedTrailIds),
69
+ extractDefinitionComposeTargetIds(def.config, sourceCode, namedTrailIds),
70
70
  filePath,
71
71
  offsetToLine(sourceCode, def.start),
72
72
  trailIntentsById
@@ -121,7 +121,7 @@ export const intentPropagation: ProjectAwareWardenRule = {
121
121
  return checkIntentPropagation(ast, sourceCode, filePath, trailIntentsById);
122
122
  },
123
123
  description:
124
- "Warn when a trail declaring intent: 'read' crosses a trail whose normalized intent is write or destroy.",
124
+ "Warn when a trail declaring intent: 'read' composes a trail whose normalized intent is write or destroy.",
125
125
  name: 'intent-propagation',
126
126
  severity: 'warn',
127
127
  };
@@ -1,4 +1,6 @@
1
1
  import type {
2
+ WardenFixClass,
3
+ WardenFixSafety,
2
4
  WardenRule,
3
5
  WardenRuleConcern,
4
6
  WardenRuleLifecycleState,
@@ -41,6 +43,15 @@ export const wardenRuleLifecycleStates = [
41
43
  'deprecated',
42
44
  ] as const satisfies readonly WardenRuleLifecycleState[];
43
45
 
46
+ export const wardenFixClasses = [
47
+ 'term-rewrite',
48
+ ] as const satisfies readonly WardenFixClass[];
49
+
50
+ export const wardenFixSafeties = [
51
+ 'review',
52
+ 'safe',
53
+ ] as const satisfies readonly WardenFixSafety[];
54
+
44
55
  type BuiltinWardenRuleMetadataInput = Omit<
45
56
  WardenRuleMetadata,
46
57
  'concern' | 'depth'
@@ -57,25 +68,31 @@ const depthByTier = {
57
68
 
58
69
  const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
59
70
  'activation-orphan': 'signals',
71
+ 'composes-declarations': 'composition',
60
72
  'context-no-surface-types': 'composition',
61
- 'cross-declarations': 'composition',
62
73
  'dead-internal-trail': 'composition',
74
+ 'deprecation-without-guidance': 'lifecycle',
63
75
  'draft-file-marking': 'lifecycle',
64
76
  'draft-visible-debt': 'lifecycle',
65
77
  'error-mapping-completeness': 'results',
66
78
  'fires-declarations': 'signals',
79
+ 'fork-without-preserved-blaze': 'lifecycle',
67
80
  'implementation-returns-result': 'results',
68
81
  'intent-propagation': 'composition',
82
+ 'marker-schema-unsupported': 'lifecycle',
69
83
  'missing-reconcile': 'resources',
70
84
  'missing-visibility': 'composition',
85
+ 'no-destructured-compose': 'composition',
71
86
  'no-dev-permit-in-source': 'permits',
72
87
  'no-direct-implementation-call': 'composition',
73
88
  'no-native-error-result': 'results',
89
+ 'no-redundant-result-error-wrap': 'results',
74
90
  'no-sync-result-assumption': 'results',
75
91
  'no-throw-in-detour-recover': 'results',
76
92
  'no-throw-in-implementation': 'results',
77
93
  'on-references-exist': 'signals',
78
94
  'orphaned-signal': 'signals',
95
+ 'pending-force': 'lifecycle',
79
96
  'permit-governance': 'permits',
80
97
  'public-output-schema': 'results',
81
98
  'read-intent-fires': 'signals',
@@ -83,11 +100,15 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
83
100
  'resource-declarations': 'resources',
84
101
  'resource-exists': 'resources',
85
102
  'resource-id-grammar': 'resources',
103
+ 'resource-mock-coverage': 'resources',
86
104
  'scheduled-destroy-intent': 'lifecycle',
87
105
  'signal-graph-coaching': 'signals',
88
106
  'static-resource-accessor-preference': 'resources',
89
107
  'unmaterialized-activation-source': 'lifecycle',
90
108
  'valid-detour-contract': 'results',
109
+ 'version-gap': 'lifecycle',
110
+ 'version-pinned-compose': 'composition',
111
+ 'version-without-examples': 'lifecycle',
91
112
  'webhook-route-collision': 'composition',
92
113
  };
93
114
 
@@ -128,6 +149,11 @@ const builtinWardenRuleMetadataInput = {
128
149
  invariant: 'Contour reference graphs must be acyclic.',
129
150
  tier: 'project-static',
130
151
  },
152
+ 'composes-declarations': {
153
+ ...durableExternal,
154
+ invariant: 'Declared composes stay aligned with ctx.compose() usage.',
155
+ tier: 'source-static',
156
+ },
131
157
  'context-no-surface-types': {
132
158
  ...durableExternal,
133
159
  invariant: 'Trail logic stays surface-agnostic.',
@@ -138,16 +164,17 @@ const builtinWardenRuleMetadataInput = {
138
164
  invariant: 'Declared contour references resolve to known contours.',
139
165
  tier: 'project-static',
140
166
  },
141
- 'cross-declarations': {
142
- ...durableExternal,
143
- invariant: 'Declared crosses stay aligned with ctx.cross() usage.',
144
- tier: 'source-static',
145
- },
146
167
  'dead-internal-trail': {
147
168
  ...durableExternal,
148
- invariant: 'Internal trails should be reachable through declared crosses.',
169
+ invariant: 'Internal trails should be reachable through declared composes.',
149
170
  tier: 'project-static',
150
171
  },
172
+ 'deprecation-without-guidance': {
173
+ ...durableExternal,
174
+ invariant:
175
+ 'Deprecated trail version entries carry successor, migration, or note guidance.',
176
+ tier: 'topo-aware',
177
+ },
151
178
  'draft-file-marking': {
152
179
  ...durableExternal,
153
180
  invariant: 'Draft-authored state is visibly marked in filenames.',
@@ -181,9 +208,14 @@ const builtinWardenRuleMetadataInput = {
181
208
  invariant: 'Declared fires stay aligned with signal firing usage.',
182
209
  tier: 'source-static',
183
210
  },
211
+ 'fork-without-preserved-blaze': {
212
+ ...durableExternal,
213
+ invariant: 'Fork version entries preserve their historical blaze.',
214
+ tier: 'source-static',
215
+ },
184
216
  'implementation-returns-result': {
185
217
  ...durableExternal,
186
- invariant: 'Trail implementations return Result values.',
218
+ invariant: 'Blazes return Result values.',
187
219
  tier: 'source-static',
188
220
  },
189
221
  'incomplete-accessor-for-standard-op': {
@@ -198,7 +230,7 @@ const builtinWardenRuleMetadataInput = {
198
230
  },
199
231
  'intent-propagation': {
200
232
  ...durableExternal,
201
- invariant: 'Composite trail intent cannot be safer than crossed trails.',
233
+ invariant: 'Composite trail intent cannot be safer than composed trails.',
202
234
  tier: 'project-static',
203
235
  },
204
236
  'layer-field-name-drift': {
@@ -207,6 +239,12 @@ const builtinWardenRuleMetadataInput = {
207
239
  'Layer input field reserved names are shared across surface projections.',
208
240
  tier: 'source-static',
209
241
  },
242
+ 'marker-schema-unsupported': {
243
+ ...durableExternal,
244
+ invariant:
245
+ 'Versioned schemas stay inside the supported marker projection subset.',
246
+ tier: 'source-static',
247
+ },
210
248
  'missing-reconcile': {
211
249
  ...durableExternal,
212
250
  invariant: 'Versioned CRUD store tables provide reconcile coverage.',
@@ -217,6 +255,12 @@ const builtinWardenRuleMetadataInput = {
217
255
  invariant: 'Composition-only trails declare internal visibility.',
218
256
  tier: 'project-static',
219
257
  },
258
+ 'no-destructured-compose': {
259
+ ...durableExternal,
260
+ invariant:
261
+ 'Trail blazes compose through ctx.compose() directly instead of destructuring compose from the context.',
262
+ tier: 'source-static',
263
+ },
220
264
  'no-dev-permit-in-source': {
221
265
  ...durableExternal,
222
266
  invariant:
@@ -225,10 +269,11 @@ const builtinWardenRuleMetadataInput = {
225
269
  },
226
270
  'no-direct-implementation-call': {
227
271
  ...durableExternal,
228
- invariant: 'Application code composes trails through ctx.cross().',
272
+ invariant: 'Application code composes trails through ctx.compose().',
229
273
  tier: 'source-static',
230
274
  },
231
275
  'no-legacy-layer-imports': {
276
+ fix: { class: 'term-rewrite', safety: 'review' },
232
277
  invariant:
233
278
  'Legacy layer exports removed across TRL-475/TRL-476 (authLayer, autoIterateLayer, dateShortcutsLayer) do not reappear in committed source.',
234
279
  lifecycle: {
@@ -244,6 +289,12 @@ const builtinWardenRuleMetadataInput = {
244
289
  invariant: 'Result error boundaries carry specific TrailsError subclasses.',
245
290
  tier: 'source-static',
246
291
  },
292
+ 'no-redundant-result-error-wrap': {
293
+ ...durableExternal,
294
+ invariant:
295
+ 'Result error pass-throughs preserve the original Result boundary.',
296
+ tier: 'source-static',
297
+ },
247
298
  'no-sync-result-assumption': {
248
299
  ...durableExternal,
249
300
  invariant:
@@ -265,9 +316,24 @@ const builtinWardenRuleMetadataInput = {
265
316
  'Use detours for recoverable runtime strategies instead of throwing inside the blaze.',
266
317
  ],
267
318
  summary:
268
- 'Convert thrown implementation failures into explicit Result.err() outcomes.',
319
+ 'Convert thrown failures in blazes into explicit Result.err() outcomes.',
269
320
  },
270
- invariant: 'Trail implementations return Result.err() instead of throwing.',
321
+ invariant: 'Blazes return Result.err() instead of throwing.',
322
+ tier: 'source-static',
323
+ },
324
+ 'no-top-level-surface': {
325
+ ...durableExternal,
326
+ guidance: {
327
+ docs: [{ label: 'Architecture', path: 'docs/architecture.md' }],
328
+ relatedRules: ['context-no-surface-types'],
329
+ steps: [
330
+ 'Keep the topo-export module focused on exporting `topo(...)` as `default`, `graph`, or `app`.',
331
+ 'Move `surface(...)`, `connectStdio(...)`, server start, or `.listen(...)` calls into a separate entry or bin module.',
332
+ ],
333
+ summary:
334
+ 'Keep topo entry modules side-effect-free for survey, guide, compile, and lock generation.',
335
+ },
336
+ invariant: 'Topo export modules do not open surfaces at module top level.',
271
337
  tier: 'source-static',
272
338
  },
273
339
  'on-references-exist': {
@@ -287,6 +353,12 @@ const builtinWardenRuleMetadataInput = {
287
353
  scope: 'internal',
288
354
  tier: 'source-static',
289
355
  },
356
+ 'pending-force': {
357
+ ...durableExternal,
358
+ invariant:
359
+ 'Forced topo break audit events do not remain pending indefinitely.',
360
+ tier: 'topo-aware',
361
+ },
290
362
  'permit-governance': {
291
363
  ...durableExternal,
292
364
  guidance: {
@@ -395,6 +467,22 @@ const builtinWardenRuleMetadataInput = {
395
467
  invariant: 'Resource identifiers stay out of the scope separator grammar.',
396
468
  tier: 'source-static',
397
469
  },
470
+ 'resource-mock-coverage': {
471
+ ...durableExternal,
472
+ guidance: {
473
+ docs: [trailContractDocs],
474
+ relatedRules: ['resource-declarations', 'resource-exists'],
475
+ steps: [
476
+ 'Add a mock() factory so testAll(app) can provision the resource without production configuration.',
477
+ 'If the resource genuinely cannot be mocked, declare unmockable: { reason } to record that intent.',
478
+ ],
479
+ summary:
480
+ 'Make each resource declare a test mock or an explicit unmockable reason.',
481
+ },
482
+ invariant:
483
+ 'Resource definitions declare a mock factory or an explicit unmockable reason.',
484
+ tier: 'source-static',
485
+ },
398
486
  'scheduled-destroy-intent': {
399
487
  ...durableExternal,
400
488
  invariant:
@@ -448,6 +536,23 @@ const builtinWardenRuleMetadataInput = {
448
536
  'Runtime detour contracts use error constructors and recover functions.',
449
537
  tier: 'topo-aware',
450
538
  },
539
+ 'version-gap': {
540
+ ...durableExternal,
541
+ invariant:
542
+ 'Trail version coverage remains contiguous through the current version.',
543
+ tier: 'topo-aware',
544
+ },
545
+ 'version-pinned-compose': {
546
+ ...durableExternal,
547
+ invariant:
548
+ 'Version-pinned ctx.compose() calls stay visible migration debt.',
549
+ tier: 'source-static',
550
+ },
551
+ 'version-without-examples': {
552
+ ...durableExternal,
553
+ invariant: 'Live historical version entries include examples.',
554
+ tier: 'topo-aware',
555
+ },
451
556
  'warden-export-symmetry': {
452
557
  ...durableRepoLocal,
453
558
  invariant: 'The Warden package exports trail wrappers, not raw rules.',
@@ -1,4 +1,4 @@
1
- import { collectCrossTargetTrailIds, parse } from './ast.js';
1
+ import { collectComposeTargetTrailIds, parse } from './ast.js';
2
2
  import { isTestFile } from './scan.js';
3
3
  import {
4
4
  findTrailLikeSpecs,
@@ -28,13 +28,13 @@ const trailVisibility = (spec: TrailLikeSpec): 'internal' | 'public' => {
28
28
  return hasLegacyMetaInternal(spec) ? 'internal' : 'public';
29
29
  };
30
30
 
31
- const hasRequiredCrossInput = (spec: TrailLikeSpec): boolean => {
32
- const crossInput = spec.properties.get('crossInput');
33
- if (!crossInput) {
31
+ const hasRequiredComposeInput = (spec: TrailLikeSpec): boolean => {
32
+ const composeInput = spec.properties.get('composeInput');
33
+ if (!composeInput) {
34
34
  return false;
35
35
  }
36
36
 
37
- const fields = parseZodObjectShape(crossInput.value);
37
+ const fields = parseZodObjectShape(composeInput.value);
38
38
  return [...fields.values()].some((field) => field.required);
39
39
  };
40
40
 
@@ -45,7 +45,7 @@ const buildMissingVisibilityDiagnostic = (
45
45
  ): WardenDiagnostic => ({
46
46
  filePath,
47
47
  line,
48
- message: `Trail "${trailId}" is crossed elsewhere and declares required crossInput fields, but it is still public. Consider visibility: 'internal' so surfaces do not expose a trail that only works through ctx.cross().`,
48
+ message: `Trail "${trailId}" is composed elsewhere and declares required composeInput fields, but it is still public. Consider visibility: 'internal' so surfaces do not expose a trail that only works through ctx.compose().`,
49
49
  rule: 'missing-visibility',
50
50
  severity: 'warn',
51
51
  });
@@ -53,7 +53,7 @@ const buildMissingVisibilityDiagnostic = (
53
53
  const checkMissingVisibility = (
54
54
  sourceCode: string,
55
55
  filePath: string,
56
- crossedTrailIds: ReadonlySet<string>
56
+ composedTrailIds: ReadonlySet<string>
57
57
  ): readonly WardenDiagnostic[] => {
58
58
  if (isTestFile(filePath)) {
59
59
  return [];
@@ -65,8 +65,8 @@ const checkMissingVisibility = (
65
65
  if (
66
66
  spec.kind !== 'trail' ||
67
67
  trailVisibility(spec) === 'internal' ||
68
- !crossedTrailIds.has(spec.id) ||
69
- !hasRequiredCrossInput(spec)
68
+ !composedTrailIds.has(spec.id) ||
69
+ !hasRequiredComposeInput(spec)
70
70
  ) {
71
71
  continue;
72
72
  }
@@ -85,7 +85,7 @@ export const missingVisibility: ProjectAwareWardenRule = {
85
85
  return checkMissingVisibility(
86
86
  sourceCode,
87
87
  filePath,
88
- ast ? collectCrossTargetTrailIds(ast, sourceCode) : new Set<string>()
88
+ ast ? collectComposeTargetTrailIds(ast, sourceCode) : new Set<string>()
89
89
  );
90
90
  },
91
91
  checkWithContext(
@@ -94,17 +94,17 @@ export const missingVisibility: ProjectAwareWardenRule = {
94
94
  context: ProjectContext
95
95
  ): readonly WardenDiagnostic[] {
96
96
  const ast = parse(filePath, sourceCode);
97
- const localCrossTargetTrailIds = ast
98
- ? collectCrossTargetTrailIds(ast, sourceCode)
97
+ const localComposeTargetTrailIds = ast
98
+ ? collectComposeTargetTrailIds(ast, sourceCode)
99
99
  : new Set<string>();
100
100
  return checkMissingVisibility(
101
101
  sourceCode,
102
102
  filePath,
103
- context.crossTargetTrailIds ?? localCrossTargetTrailIds
103
+ context.composeTargetTrailIds ?? localComposeTargetTrailIds
104
104
  );
105
105
  },
106
106
  description:
107
- 'Coach when a crossed trail still looks composition-only because it declares required crossInput but remains public.',
107
+ 'Coach when a composed trail still looks composition-only because it declares required composeInput but remains public.',
108
108
  name: 'missing-visibility',
109
109
  severity: 'warn',
110
110
  };