@ontrails/warden 1.0.0-beta.42 → 1.0.0-beta.45

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 (36) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/package.json +15 -10
  3. package/src/adapter-check.ts +1 -1
  4. package/src/cli.ts +81 -5
  5. package/src/index.ts +17 -4
  6. package/src/regrade-history.ts +599 -0
  7. package/src/rules/cli-command-route-coherence.ts +6 -6
  8. package/src/rules/draft-visible-debt.ts +1 -1
  9. package/src/rules/duplicate-exported-symbol.ts +4 -43
  10. package/src/rules/governed-symbol-residue.ts +146 -53
  11. package/src/rules/governed-vocabulary-permutation-watch.ts +76 -0
  12. package/src/rules/implementation-returns-result.ts +99 -9
  13. package/src/rules/index.ts +17 -6
  14. package/src/rules/layer-field-name-drift.ts +2 -2
  15. package/src/rules/{library-projection-coherence.ts → library-render-coherence.ts} +11 -14
  16. package/src/rules/metadata.ts +51 -14
  17. package/src/rules/no-redundant-result-error-wrap.ts +15 -17
  18. package/src/rules/no-retired-cross-vocabulary.ts +0 -1
  19. package/src/rules/{owner-projection-parity.ts → owner-render-parity.ts} +18 -18
  20. package/src/rules/public-internal-deep-imports.ts +1 -3
  21. package/src/rules/public-output-schema.ts +1 -1
  22. package/src/rules/registry-names.ts +6 -4
  23. package/src/rules/retired-vocabulary.ts +775 -41
  24. package/src/rules/surface-overlay-coherence.ts +1 -1
  25. package/src/rules/trail-versioning-source.ts +1 -1
  26. package/src/rules/trailhead-override-divergence.ts +4 -4
  27. package/src/rules/types.ts +51 -5
  28. package/src/trails/governed-vocabulary-permutation-watch.trail.ts +16 -0
  29. package/src/trails/index.ts +3 -2
  30. package/src/trails/layer-field-name-drift.trail.ts +1 -1
  31. package/src/trails/{library-projection-coherence.trail.ts → library-render-coherence.trail.ts} +6 -6
  32. package/src/trails/{owner-projection-parity.trail.ts → owner-render-parity.trail.ts} +4 -4
  33. package/src/trails/public-output-schema.trail.ts +1 -1
  34. package/src/trails/run.ts +44 -2
  35. package/src/trails/schema.ts +41 -0
  36. package/src/trails/wrap-rule.ts +44 -2
@@ -12,7 +12,13 @@ import type {
12
12
  GovernedVocabularySymbolRename,
13
13
  GovernedVocabularyTransition,
14
14
  } from './retired-vocabulary.js';
15
- import type { WardenDiagnostic, WardenFix, WardenRule } from './types.js';
15
+ import type {
16
+ GovernedVocabularyHistoryEvidence,
17
+ ProjectAwareWardenRule,
18
+ ProjectContext,
19
+ WardenDiagnostic,
20
+ WardenFix,
21
+ } from './types.js';
16
22
 
17
23
  const RULE_NAME = 'governed-symbol-residue';
18
24
 
@@ -37,11 +43,15 @@ const isAllowedSourcePath = (filePath: string): boolean =>
37
43
  );
38
44
 
39
45
  interface ActiveSymbolRename {
46
+ readonly history?: GovernedVocabularyHistoryEvidence;
40
47
  readonly rename: GovernedVocabularySymbolRename;
41
48
  readonly targetSegments: readonly string[];
49
+ readonly transition: GovernedVocabularyTransition;
42
50
  }
43
51
 
44
- const activeSymbolRenames = (): readonly ActiveSymbolRename[] =>
52
+ const activeSymbolRenames = (
53
+ context?: ProjectContext
54
+ ): readonly ActiveSymbolRename[] =>
45
55
  listGovernedVocabularyTransitions()
46
56
  .filter(
47
57
  (transition) =>
@@ -52,9 +62,14 @@ const activeSymbolRenames = (): readonly ActiveSymbolRename[] =>
52
62
  const targetSegments = [
53
63
  ...new Set(transition.symbolRenames.map((rename) => rename.to)),
54
64
  ];
65
+ const history = context?.governedVocabularyHistoryByTransitionId?.get(
66
+ transition.id
67
+ );
55
68
  return transition.symbolRenames.map((rename) => ({
69
+ ...(history === undefined ? {} : { history }),
56
70
  rename,
57
71
  targetSegments,
72
+ transition,
58
73
  }));
59
74
  });
60
75
 
@@ -353,12 +368,28 @@ const safeFixFor = (match: SymbolRenameMatch): WardenFix | undefined => {
353
368
  };
354
369
  };
355
370
 
371
+ const diagnosticMessageFor = (
372
+ match: SymbolRenameMatch,
373
+ transition?: GovernedVocabularyTransition,
374
+ history?: GovernedVocabularyHistoryEvidence
375
+ ): string => {
376
+ if (history !== undefined) {
377
+ return `Retired governed symbol '${match.from}' was reintroduced after governed transition '${history.transitionId}' in '${history.path}' (${history.id}); migrate to '${match.to}'.`;
378
+ }
379
+ if (transition?.provenance.mode === 'regrade-history') {
380
+ return `Retired governed symbol '${match.from}' belongs to governed transition '${transition.id}', but no committed Regrade history proves the migration ran.`;
381
+ }
382
+ return `Retired governed symbol '${match.from}' should migrate to '${match.to}'.`;
383
+ };
384
+
356
385
  const diagnosticFor = (
357
386
  sourceCode: string,
358
387
  filePath: string,
359
388
  node: AstNode,
360
389
  match: SymbolRenameMatch,
361
- reviewReason: string | null
390
+ reviewReason: string | null,
391
+ transition?: GovernedVocabularyTransition,
392
+ history?: GovernedVocabularyHistoryEvidence
362
393
  ): WardenDiagnostic => {
363
394
  const fix =
364
395
  reviewReason === null
@@ -368,71 +399,133 @@ const diagnosticFor = (
368
399
  filePath,
369
400
  ...(fix === undefined ? {} : { fix }),
370
401
  line: offsetToLine(sourceCode, node.start),
371
- message: `Retired governed symbol '${match.from}' should migrate to '${match.to}'.`,
402
+ message: diagnosticMessageFor(match, transition, history),
372
403
  rule: RULE_NAME,
373
404
  severity: 'error',
374
405
  };
375
406
  };
376
407
 
377
- export const governedSymbolResidue: WardenRule = {
378
- check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
379
- if (isAllowedSourcePath(filePath)) {
380
- return [];
381
- }
408
+ const provenanceDiagnostics = (
409
+ context: ProjectContext
410
+ ): readonly WardenDiagnostic[] => {
411
+ const historyIssues = context.governedVocabularyHistoryIssues ?? [];
412
+ const invalidTransitionIds = new Set(
413
+ historyIssues.flatMap((issue) =>
414
+ issue.transitionId === undefined ? [] : [issue.transitionId]
415
+ )
416
+ );
417
+ const issues = historyIssues.map((issue) => ({
418
+ filePath: issue.path,
419
+ line: 1,
420
+ message: issue.message,
421
+ rule: RULE_NAME,
422
+ severity: 'error' as const,
423
+ }));
424
+ const missing = listGovernedVocabularyTransitions()
425
+ .filter(
426
+ (transition) =>
427
+ context.governedVocabularyHistoryRequired === true &&
428
+ transition.status === 'complete' &&
429
+ transition.provenance.mode === 'regrade-history' &&
430
+ !invalidTransitionIds.has(transition.id) &&
431
+ !context.governedVocabularyHistoryByTransitionId?.has(transition.id)
432
+ )
433
+ .map((transition) => ({
434
+ filePath: '<governed-vocabulary-history>',
435
+ line: 1,
436
+ message: `Governed transition '${transition.id}' is complete but has no valid committed Regrade history provenance. Run Regrade as the primary migration path before manual review or cleanup.`,
437
+ rule: RULE_NAME,
438
+ severity: 'error' as const,
439
+ }));
440
+ return [...issues, ...missing];
441
+ };
382
442
 
383
- const renames = activeSymbolRenames();
384
- if (renames.length === 0) {
385
- return [];
386
- }
443
+ const checkSource = (
444
+ sourceCode: string,
445
+ filePath: string,
446
+ projectContext?: ProjectContext
447
+ ): readonly WardenDiagnostic[] => {
448
+ if (isAllowedSourcePath(filePath)) {
449
+ return [];
450
+ }
387
451
 
388
- const parsed = parseWithDiagnostics(filePath, sourceCode);
389
- if (!parsed.ast || parsed.diagnostics.length > 0) {
390
- return [];
391
- }
452
+ const renames = activeSymbolRenames(projectContext);
453
+ if (renames.length === 0) {
454
+ return [];
455
+ }
392
456
 
393
- const shorthandIdentifiers = new Set<string>();
394
- walkWithScopeContext(parsed.ast, (node, context) => {
395
- if (!isShorthandPropertyKeyVisit(node, context)) {
396
- return;
397
- }
398
- const name = identifierName(node);
399
- if (name !== null) {
400
- shorthandIdentifiers.add(name);
401
- }
402
- });
457
+ const parsed = parseWithDiagnostics(filePath, sourceCode);
458
+ if (!parsed.ast || parsed.diagnostics.length > 0) {
459
+ return [];
460
+ }
403
461
 
404
- const diagnostics: WardenDiagnostic[] = [];
405
- walkWithScopeContext(parsed.ast, (node, context) => {
406
- if (isDuplicateShorthandValueVisit(node, context)) {
407
- return;
408
- }
462
+ const shorthandIdentifiers = new Set<string>();
463
+ walkWithScopeContext(parsed.ast, (node, astContext) => {
464
+ if (!isShorthandPropertyKeyVisit(node, astContext)) {
465
+ return;
466
+ }
467
+ const name = identifierName(node);
468
+ if (name !== null) {
469
+ shorthandIdentifiers.add(name);
470
+ }
471
+ });
409
472
 
410
- const activeMatch = renames
411
- .map(({ rename, targetSegments }) => ({
412
- match: resolveSymbolRenameMatch(node, sourceCode, rename),
413
- targetSegments,
414
- }))
415
- .find((candidate) => candidate.match !== null);
416
- if (activeMatch === undefined || activeMatch.match === null) {
417
- return;
418
- }
419
- const { match, targetSegments } = activeMatch;
473
+ const diagnostics: WardenDiagnostic[] = [];
474
+ walkWithScopeContext(parsed.ast, (node, astContext) => {
475
+ if (isDuplicateShorthandValueVisit(node, astContext)) {
476
+ return;
477
+ }
420
478
 
421
- diagnostics.push(
422
- diagnosticFor(
423
- sourceCode,
424
- filePath,
425
- node,
479
+ const activeMatch = renames
480
+ .map(({ history, rename, targetSegments, transition }) => ({
481
+ history,
482
+ match: resolveSymbolRenameMatch(node, sourceCode, rename),
483
+ targetSegments,
484
+ transition,
485
+ }))
486
+ .find((candidate) => candidate.match !== null);
487
+ if (activeMatch === undefined || activeMatch.match === null) {
488
+ return;
489
+ }
490
+ const { history, match, targetSegments, transition } = activeMatch;
491
+
492
+ diagnostics.push(
493
+ diagnosticFor(
494
+ sourceCode,
495
+ filePath,
496
+ node,
497
+ match,
498
+ reviewReasonFor(
499
+ astContext,
426
500
  match,
427
- reviewReasonFor(context, match, shorthandIdentifiers, targetSegments)
428
- )
429
- );
430
- });
501
+ shorthandIdentifiers,
502
+ targetSegments
503
+ ),
504
+ transition,
505
+ history
506
+ )
507
+ );
508
+ });
431
509
 
432
- return diagnostics;
510
+ return diagnostics;
511
+ };
512
+
513
+ export const governedSymbolResidue: ProjectAwareWardenRule = {
514
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
515
+ return checkSource(sourceCode, filePath);
516
+ },
517
+ checkProject(context: ProjectContext): readonly WardenDiagnostic[] {
518
+ return provenanceDiagnostics(context);
519
+ },
520
+ checkWithContext(
521
+ sourceCode: string,
522
+ filePath: string,
523
+ context: ProjectContext
524
+ ): readonly WardenDiagnostic[] {
525
+ return checkSource(sourceCode, filePath, context);
433
526
  },
434
527
  description:
435
- 'Detect active governed vocabulary symbols that remain in source code.',
528
+ 'Require committed Regrade provenance for governed transitions and detect retired symbols that remain or return.',
436
529
  name: RULE_NAME,
437
530
  severity: 'error',
438
531
  };
@@ -0,0 +1,76 @@
1
+ import type {
2
+ GovernedVocabularyHistoryEvidence,
3
+ GovernedVocabularyHistoryFormObservation,
4
+ ProjectAwareWardenRule,
5
+ ProjectContext,
6
+ WardenDiagnostic,
7
+ } from './types.js';
8
+
9
+ const RULE_NAME = 'governed-vocabulary-permutation-watch';
10
+
11
+ const formIdentity = (
12
+ form: string,
13
+ evidence: GovernedVocabularyHistoryEvidence
14
+ ): string => (evidence.caseSensitive ? form : form.toLowerCase());
15
+
16
+ const isUnknownPermutation = (
17
+ observation: GovernedVocabularyHistoryFormObservation
18
+ ): boolean =>
19
+ observation.reason === 'unclassified-neighbor' &&
20
+ observation.verdict === 'deferred' &&
21
+ (observation.scopeTier === undefined || observation.scopeTier === 'in-scope');
22
+
23
+ const compareObservation = (
24
+ left: GovernedVocabularyHistoryFormObservation,
25
+ right: GovernedVocabularyHistoryFormObservation
26
+ ): number =>
27
+ left.path.localeCompare(right.path) ||
28
+ left.line - right.line ||
29
+ left.form.localeCompare(right.form);
30
+
31
+ const diagnosticsForHistory = (
32
+ evidence: GovernedVocabularyHistoryEvidence
33
+ ): readonly WardenDiagnostic[] => {
34
+ const unknownByIdentity = new Map<
35
+ string,
36
+ GovernedVocabularyHistoryFormObservation
37
+ >();
38
+
39
+ for (const observation of evidence.latestFormObservations) {
40
+ if (!isUnknownPermutation(observation)) {
41
+ continue;
42
+ }
43
+ const identity = formIdentity(observation.form, evidence);
44
+ const current = unknownByIdentity.get(identity);
45
+ if (current === undefined || compareObservation(observation, current) < 0) {
46
+ unknownByIdentity.set(identity, observation);
47
+ }
48
+ }
49
+
50
+ return [...unknownByIdentity.entries()]
51
+ .toSorted(([left], [right]) => left.localeCompare(right))
52
+ .map(([, observation]) => ({
53
+ filePath: observation.path,
54
+ line: observation.line,
55
+ message: `Governed transition '${evidence.transitionId}' recorded unknown vocabulary form '${observation.form}' in committed Regrade history '${evidence.path}' (${evidence.id}). Add the form and run an incremental plan, or classify it as out-of-family or preserved.`,
56
+ rule: RULE_NAME,
57
+ severity: 'warn' as const,
58
+ }));
59
+ };
60
+
61
+ const checkProject = (context: ProjectContext): readonly WardenDiagnostic[] =>
62
+ [...(context.governedVocabularyHistoryByTransitionId?.values() ?? [])]
63
+ .toSorted((left, right) =>
64
+ left.transitionId.localeCompare(right.transitionId)
65
+ )
66
+ .flatMap(diagnosticsForHistory);
67
+
68
+ export const governedVocabularyPermutationWatch: ProjectAwareWardenRule = {
69
+ check: () => [],
70
+ checkProject,
71
+ checkWithContext: () => [],
72
+ description:
73
+ 'Advise when committed Regrade history records unclassified permutations of governed vocabulary.',
74
+ name: RULE_NAME,
75
+ severity: 'warn',
76
+ };
@@ -25,8 +25,11 @@ import {
25
25
  getNodeImported,
26
26
  getNodeInit,
27
27
  getNodeLocal,
28
+ getNodeLeft,
28
29
  getNodeName,
30
+ getNodeOperator,
29
31
  getNodeReturnType,
32
+ getNodeRight,
30
33
  getNodeSource,
31
34
  getNodeTypeAnnotation,
32
35
  getNodeValue,
@@ -97,12 +100,15 @@ export type ScopedHelperMap = ReadonlyMap<
97
100
 
98
101
  export type MutableScopedHelperMap = Map<ReadonlySet<string>, Set<string>>;
99
102
 
100
- type ScopedResultVariableMap = ReadonlyMap<
103
+ export type ScopedResultVariableMap = ReadonlyMap<
101
104
  ReadonlySet<string>,
102
105
  ReadonlySet<string>
103
106
  >;
104
107
 
105
- type MutableScopedResultVariableMap = Map<ReadonlySet<string>, Set<string>>;
108
+ export type MutableScopedResultVariableMap = Map<
109
+ ReadonlySet<string>,
110
+ Set<string>
111
+ >;
106
112
 
107
113
  export const findNearestBindingScope = (
108
114
  name: string,
@@ -224,8 +230,8 @@ const unwrapReturnExpression = (node: AstNode): AstNode => {
224
230
  return current;
225
231
  };
226
232
 
227
- /** Check if a return argument is an allowed Result value. */
228
- const isAllowedReturnArgument = (
233
+ /** Check whether an expression has visible Result-producing provenance. */
234
+ export const isResultProducingExpression = (
229
235
  argument: AstNode,
230
236
  helperNames: ReadonlySet<string>,
231
237
  resultVars: ScopedResultVariableMap,
@@ -240,7 +246,7 @@ const isAllowedReturnArgument = (
240
246
  return (
241
247
  consequent !== undefined &&
242
248
  alternate !== undefined &&
243
- isAllowedReturnArgument(
249
+ isResultProducingExpression(
244
250
  consequent,
245
251
  helperNames,
246
252
  resultVars,
@@ -248,7 +254,7 @@ const isAllowedReturnArgument = (
248
254
  scopes,
249
255
  scopedHelpers
250
256
  ) &&
251
- isAllowedReturnArgument(
257
+ isResultProducingExpression(
252
258
  alternate,
253
259
  helperNames,
254
260
  resultVars,
@@ -369,6 +375,14 @@ const addScopedResultVariable = (
369
375
  resultVars.set(scope, new Set([name]));
370
376
  };
371
377
 
378
+ const clearScopedResultVariable = (
379
+ resultVars: MutableScopedResultVariableMap,
380
+ scope: ReadonlySet<string>,
381
+ name: string
382
+ ): void => {
383
+ resultVars.get(scope)?.delete(name);
384
+ };
385
+
372
386
  /** Record `const helper = (): Result<...> => ...` declarations for the current lexical scope. */
373
387
  export const trackScopedResultHelperDeclaration = (
374
388
  node: AstNode,
@@ -440,8 +454,14 @@ const trackResultVariable = (
440
454
  }
441
455
  if (
442
456
  hasResultVariableAnnotation(id, sourceCode, resultTypeNames) ||
443
- isResultExpression(init) ||
444
- isHelperCall(init, helperNames, namespaceHelpers, scopes, scopedHelpers)
457
+ isResultProducingExpression(
458
+ init,
459
+ helperNames,
460
+ resultVars,
461
+ namespaceHelpers,
462
+ scopes,
463
+ scopedHelpers
464
+ )
445
465
  ) {
446
466
  const bindingScope = findNearestBindingScope(name, scopes);
447
467
  if (bindingScope) {
@@ -451,6 +471,63 @@ const trackResultVariable = (
451
471
  }
452
472
  };
453
473
 
474
+ export const collectDirectResultAssignments = (
475
+ body: AstNode
476
+ ): ReadonlySet<AstNode> => {
477
+ const assignments = new Set<AstNode>();
478
+ for (const statement of getNodeBodyStatements(body)) {
479
+ if (statement.type !== 'ExpressionStatement') {
480
+ continue;
481
+ }
482
+ const expression = getNodeExpression(statement);
483
+ if (expression?.type === 'AssignmentExpression') {
484
+ assignments.add(expression);
485
+ }
486
+ }
487
+ return assignments;
488
+ };
489
+
490
+ const trackResultAssignment = (
491
+ node: AstNode,
492
+ resultVars: MutableScopedResultVariableMap,
493
+ helperNames: ReadonlySet<string>,
494
+ namespaceHelpers: NamespaceHelperMap,
495
+ directAssignments: ReadonlySet<AstNode>,
496
+ scopes: readonly ReadonlySet<string>[],
497
+ scopedHelpers: ScopedHelperMap
498
+ ): void => {
499
+ const left = getNodeLeft(node);
500
+ const name = identifierName(left);
501
+ if (!name) {
502
+ return;
503
+ }
504
+ const bindingScope = findNearestBindingScope(name, scopes);
505
+ if (!bindingScope) {
506
+ return;
507
+ }
508
+ const right = getNodeRight(node);
509
+ const resultRhs =
510
+ getNodeOperator(node) === '=' &&
511
+ right &&
512
+ isResultProducingExpression(
513
+ right,
514
+ helperNames,
515
+ resultVars,
516
+ namespaceHelpers,
517
+ scopes,
518
+ scopedHelpers
519
+ );
520
+ if (
521
+ resultRhs &&
522
+ (isScopedResultVariableBinding(name, scopes, resultVars) ||
523
+ directAssignments.has(node))
524
+ ) {
525
+ addScopedResultVariable(resultVars, bindingScope, name);
526
+ return;
527
+ }
528
+ clearScopedResultVariable(resultVars, bindingScope, name);
529
+ };
530
+
454
531
  // ---------------------------------------------------------------------------
455
532
  // Return statement checking
456
533
  // ---------------------------------------------------------------------------
@@ -470,6 +547,7 @@ const checkReturnStatements = (
470
547
  const resultVars: MutableScopedResultVariableMap = new Map();
471
548
  const scopedHelpers: MutableScopedHelperMap = new Map();
472
549
  const initialScopes = implScope.size > 0 ? [implScope] : [];
550
+ const directAssignments = collectDirectResultAssignments(blockBody);
473
551
 
474
552
  walkWithScopes(
475
553
  blockBody,
@@ -494,6 +572,18 @@ const checkReturnStatements = (
494
572
  );
495
573
  }
496
574
 
575
+ if (node.type === 'AssignmentExpression') {
576
+ trackResultAssignment(
577
+ node,
578
+ resultVars,
579
+ helperNames,
580
+ namespaceHelpers,
581
+ directAssignments,
582
+ currentScopes,
583
+ scopedHelpers
584
+ );
585
+ }
586
+
497
587
  if (node.type !== 'ReturnStatement') {
498
588
  return;
499
589
  }
@@ -505,7 +595,7 @@ const checkReturnStatements = (
505
595
  }
506
596
 
507
597
  if (
508
- isAllowedReturnArgument(
598
+ isResultProducingExpression(
509
599
  argument,
510
600
  helperNames,
511
601
  resultVars,
@@ -15,12 +15,13 @@ import { errorMappingCompleteness } from './error-mapping-completeness.js';
15
15
  import { exampleValid } from './example-valid.js';
16
16
  import { firesDeclarations } from './fires-declarations.js';
17
17
  import { governedSymbolResidue } from './governed-symbol-residue.js';
18
+ import { governedVocabularyPermutationWatch } from './governed-vocabulary-permutation-watch.js';
18
19
  import { implementationReturnsResult } from './implementation-returns-result.js';
19
20
  import { incompleteAccessorForStandardOp } from './incomplete-accessor-for-standard-op.js';
20
21
  import { incompleteCrud } from './incomplete-crud.js';
21
22
  import { intentPropagation } from './intent-propagation.js';
22
23
  import { layerFieldNameDrift } from './layer-field-name-drift.js';
23
- import { libraryProjectionCoherence } from './library-projection-coherence.js';
24
+ import { libraryRenderCoherence } from './library-render-coherence.js';
24
25
  import { missingVisibility } from './missing-visibility.js';
25
26
  import { missingReconcile } from './missing-reconcile.js';
26
27
  import { noDevPermitInSource } from './no-dev-permit-in-source.js';
@@ -37,7 +38,7 @@ import { noThrowInImplementation } from './no-throw-in-implementation.js';
37
38
  import { noTopLevelSurface } from './no-top-level-surface.js';
38
39
  import { onReferencesExist } from './on-references-exist.js';
39
40
  import { orphanedSignal } from './orphaned-signal.js';
40
- import { ownerProjectionParity } from './owner-projection-parity.js';
41
+ import { ownerRenderParity } from './owner-render-parity.js';
41
42
  import { permitGovernance } from './permit-governance.js';
42
43
  import { preferSchemaInference } from './prefer-schema-inference.js';
43
44
  import { publicExportExampleCoverage } from './public-export-example-coverage.js';
@@ -79,6 +80,9 @@ import { wardenRulesUseAst } from './warden-rules-use-ast.js';
79
80
  import { webhookRouteCollision } from './webhook-route-collision.js';
80
81
 
81
82
  export type {
83
+ GovernedVocabularyHistoryEvidence,
84
+ GovernedVocabularyHistoryFormObservation,
85
+ GovernedVocabularyHistoryIssue,
82
86
  WardenFix,
83
87
  WardenFixCapability,
84
88
  WardenFixClass,
@@ -106,7 +110,10 @@ export {
106
110
  formatGovernedVocabularyTransitionGuide,
107
111
  getGovernedVocabularyTransition,
108
112
  governedVocabularyLiteralRenameSchema,
113
+ governedVocabularyFileRenameSchema,
114
+ governedVocabularyHistoryProvenanceSchema,
109
115
  governedVocabularyPreserveRuleSchema,
116
+ governedVocabularyProvenancePolicySchema,
110
117
  governedVocabularyRegistrySchema,
111
118
  governedVocabularyScopeSchema,
112
119
  governedVocabularySymbolRenameSchema,
@@ -119,7 +126,9 @@ export {
119
126
  } from './retired-vocabulary.js';
120
127
  export type {
121
128
  GovernedVocabularyLiteralRename,
129
+ GovernedVocabularyHistoryProvenance,
122
130
  GovernedVocabularyPreserveRule,
131
+ GovernedVocabularyProvenancePolicy,
123
132
  GovernedVocabularyScope,
124
133
  GovernedVocabularySymbolRename,
125
134
  GovernedVocabularyTarget,
@@ -157,10 +166,11 @@ export { errorMappingCompleteness } from './error-mapping-completeness.js';
157
166
  export { exampleValid } from './example-valid.js';
158
167
  export { firesDeclarations } from './fires-declarations.js';
159
168
  export { governedSymbolResidue } from './governed-symbol-residue.js';
169
+ export { governedVocabularyPermutationWatch } from './governed-vocabulary-permutation-watch.js';
160
170
  export { incompleteAccessorForStandardOp } from './incomplete-accessor-for-standard-op.js';
161
171
  export { incompleteCrud } from './incomplete-crud.js';
162
172
  export { intentPropagation } from './intent-propagation.js';
163
- export { libraryProjectionCoherence } from './library-projection-coherence.js';
173
+ export { libraryRenderCoherence } from './library-render-coherence.js';
164
174
  export { layerFieldNameDrift } from './layer-field-name-drift.js';
165
175
  export { missingVisibility } from './missing-visibility.js';
166
176
  export { missingReconcile } from './missing-reconcile.js';
@@ -178,7 +188,7 @@ export { implementationReturnsResult } from './implementation-returns-result.js'
178
188
  export { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
179
189
  export { noTopLevelSurface } from './no-top-level-surface.js';
180
190
  export { orphanedSignal } from './orphaned-signal.js';
181
- export { ownerProjectionParity } from './owner-projection-parity.js';
191
+ export { ownerRenderParity } from './owner-render-parity.js';
182
192
  export { permitGovernance } from './permit-governance.js';
183
193
  export { preferSchemaInference } from './prefer-schema-inference.js';
184
194
  export { publicInternalDeepImports } from './public-internal-deep-imports.js';
@@ -235,6 +245,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
235
245
  [exampleValid.name, exampleValid],
236
246
  [firesDeclarations.name, firesDeclarations],
237
247
  [governedSymbolResidue.name, governedSymbolResidue],
248
+ [governedVocabularyPermutationWatch.name, governedVocabularyPermutationWatch],
238
249
  [incompleteCrud.name, incompleteCrud],
239
250
  [intentPropagation.name, intentPropagation],
240
251
  [layerFieldNameDrift.name, layerFieldNameDrift],
@@ -242,7 +253,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
242
253
  [missingReconcile.name, missingReconcile],
243
254
  [onReferencesExist.name, onReferencesExist],
244
255
  [orphanedSignal.name, orphanedSignal],
245
- [ownerProjectionParity.name, ownerProjectionParity],
256
+ [ownerRenderParity.name, ownerRenderParity],
246
257
  [publicExportExampleCoverage.name, publicExportExampleCoverage],
247
258
  [publicInternalDeepImports.name, publicInternalDeepImports],
248
259
  [resourceDeclarations.name, resourceDeclarations],
@@ -297,7 +308,7 @@ export const wardenTopoRules: ReadonlyMap<string, TopoAwareWardenRule> =
297
308
  [cliCommandRouteCoherence.name, cliCommandRouteCoherence],
298
309
  [duplicatePublicContract.name, duplicatePublicContract],
299
310
  [incompleteAccessorForStandardOp.name, incompleteAccessorForStandardOp],
300
- [libraryProjectionCoherence.name, libraryProjectionCoherence],
311
+ [libraryRenderCoherence.name, libraryRenderCoherence],
301
312
  [permitGovernance.name, permitGovernance],
302
313
  [publicOutputSchema.name, publicOutputSchema],
303
314
  [publicUnionOutputDiscriminants.name, publicUnionOutputDiscriminants],
@@ -60,7 +60,7 @@ const buildDiagnostic = (
60
60
  ): WardenDiagnostic => ({
61
61
  filePath,
62
62
  line: offsetToLine(sourceCode, node.start),
63
- message: `layer-field-name-drift: surface-local reserved name set "${name}" can make layer input fields project differently across surfaces. Import LAYER_FIELD_RESERVED_NAMES from @ontrails/core instead.`,
63
+ message: `layer-field-name-drift: surface-local reserved name set "${name}" can make layer input fields render differently across surfaces. Import LAYER_FIELD_RESERVED_NAMES from @ontrails/core instead.`,
64
64
  rule: RULE_NAME,
65
65
  severity: 'error',
66
66
  });
@@ -96,7 +96,7 @@ export const layerFieldNameDrift: WardenRule = {
96
96
  return diagnostics;
97
97
  },
98
98
  description:
99
- 'Prevent surface-local reserved-name sets from drifting layer input field projection across surfaces.',
99
+ 'Prevent surface-local reserved-name sets from drifting layer input field rendering across surfaces.',
100
100
  name: RULE_NAME,
101
101
  severity: 'error',
102
102
  };