@ontrails/regrade 1.0.0-beta.41 → 1.0.0-beta.43

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.
@@ -6,6 +6,7 @@ import {
6
6
  } from '@ontrails/core';
7
7
  import type { ScanTargets } from '@ontrails/core';
8
8
  import type {
9
+ GovernedVocabularyHistoryProvenance,
9
10
  WardenDiagnostic,
10
11
  WardenFixEdit,
11
12
  WardenGuidance,
@@ -13,6 +14,7 @@ import type {
13
14
  } from '@ontrails/warden';
14
15
  import {
15
16
  getWardenRuleMetadata,
17
+ governedVocabularyHistoryProvenanceSchema,
16
18
  isWardenSourceScanTarget,
17
19
  loadProjectWardenRules,
18
20
  wardenRules,
@@ -756,7 +758,9 @@ export interface RegradeReport {
756
758
  };
757
759
  /** Saved applied Regrade history evidence for vocabulary regrades. */
758
760
  readonly history?: {
761
+ readonly id?: string;
759
762
  readonly path: string;
763
+ readonly provenance?: GovernedVocabularyHistoryProvenance;
760
764
  readonly schemaVersion: number;
761
765
  readonly status: 'applied' | 'checked' | 'replay';
762
766
  };
@@ -1464,7 +1468,14 @@ export const regradeReportOutput = z.object({
1464
1468
  ),
1465
1469
  history: z
1466
1470
  .object({
1471
+ id: z
1472
+ .string()
1473
+ .optional()
1474
+ .describe('Stable consolidated Regrade history identity'),
1467
1475
  path: z.string().describe('Root-relative applied history entry path'),
1476
+ provenance: governedVocabularyHistoryProvenanceSchema
1477
+ .optional()
1478
+ .describe('Governed transition evidence attached to the applied run'),
1468
1479
  schemaVersion: z.number().describe('Regrade history schema version'),
1469
1480
  status: z
1470
1481
  .enum(['applied', 'checked', 'replay'])
@@ -1522,7 +1533,7 @@ export const regradeReportOutput = z.object({
1522
1533
  /**
1523
1534
  * Built-in regrade classes available to the report trail.
1524
1535
  *
1525
- * Warden owns term detection and fix metadata. Regrade projects Warden rules
1536
+ * Warden owns term detection and fix metadata. Regrade derives Warden rules
1526
1537
  * that advertise `term-rewrite` capability into reportable classes and then
1527
1538
  * owns application/reporting of the resulting rewrite or review outcomes.
1528
1539
  */
@@ -31,12 +31,25 @@ const scopeFromTransition = (
31
31
  return undefined;
32
32
  }
33
33
  return {
34
- ...(scope.exclude === undefined ? {} : { exclude: scope.exclude }),
35
- ...(scope.extensions === undefined ? {} : { extensions: scope.extensions }),
34
+ ...(scope.exclude === undefined ? {} : { exclude: [...scope.exclude] }),
35
+ ...(scope.extensions === undefined
36
+ ? {}
37
+ : { extensions: [...scope.extensions] }),
36
38
  ...(scope.ignoredDirectories === undefined
37
39
  ? {}
38
- : { ignoredDirectories: scope.ignoredDirectories }),
39
- ...(scope.include === undefined ? {} : { include: scope.include }),
40
+ : { ignoredDirectories: [...scope.ignoredDirectories] }),
41
+ ...(scope.include === undefined ? {} : { include: [...scope.include] }),
42
+ ...(scope.policyClassified === undefined
43
+ ? {}
44
+ : {
45
+ policyClassified: scope.policyClassified.map((policy) => ({
46
+ ...policy,
47
+ paths: [...policy.paths],
48
+ })),
49
+ }),
50
+ ...(scope.teachingSurfaces === undefined
51
+ ? {}
52
+ : { teachingSurfaces: [...scope.teachingSurfaces] }),
40
53
  };
41
54
  };
42
55
 
@@ -53,37 +66,122 @@ const defaultFormsAreRegistrySafe = (
53
66
  });
54
67
  };
55
68
 
69
+ const classifiedTargetMatches = (
70
+ transition: GovernedVocabularyTransition,
71
+ to: string
72
+ ): boolean =>
73
+ transition.target.kind === 'classified' &&
74
+ transition.target.options.some((option) => option.to === to);
75
+
76
+ const uniqueForms = (forms: readonly string[]): readonly string[] => [
77
+ ...new Set(forms),
78
+ ];
79
+
56
80
  export const vocabularyRegradePlanFromTransition = (
57
- transition: GovernedVocabularyTransition
81
+ transition: GovernedVocabularyTransition,
82
+ classifiedTarget?: string
58
83
  ): VocabularyRegradePlan | null => {
84
+ const isClassifiedPlan =
85
+ classifiedTarget !== undefined &&
86
+ classifiedTargetMatches(transition, classifiedTarget);
87
+ if (transition.target.kind === 'classified' && !isClassifiedPlan) {
88
+ return null;
89
+ }
59
90
  if (
60
- transition.target.kind !== 'single' ||
91
+ transition.target.kind === 'single' &&
61
92
  !defaultFormsAreRegistrySafe(transition)
62
93
  ) {
63
94
  return null;
64
95
  }
65
96
 
66
97
  const scope = scopeFromTransition(transition);
98
+ const to =
99
+ transition.target.kind === 'single'
100
+ ? transition.target.to
101
+ : classifiedTarget;
102
+ if (to === undefined) {
103
+ return null;
104
+ }
67
105
  return {
68
106
  caseSensitive: true,
69
- deferForms: transition.reviewForms,
107
+ deferForms: isClassifiedPlan
108
+ ? uniqueForms([...transition.oldForms, ...transition.reviewForms])
109
+ : transition.reviewForms,
110
+ ...(transition.fileRenames.length === 0
111
+ ? {}
112
+ : {
113
+ fileRenames: transition.fileRenames.map((rename) => ({ ...rename })),
114
+ }),
70
115
  from: transition.from,
71
116
  id: transition.id,
72
117
  intent: transition.intent,
73
118
  kind: 'vocabulary',
74
- overrides: transition.safeRewriteForms,
119
+ ...(isClassifiedPlan ? {} : { overrides: transition.safeRewriteForms }),
75
120
  preserve: preserveRulesFromTransition(transition),
76
121
  ...(scope === undefined ? {} : { scope }),
77
- to: transition.target.to,
122
+ to,
78
123
  };
79
124
  };
80
125
 
126
+ export const vocabularyRegradePlanForInput = (
127
+ from: string,
128
+ to: string
129
+ ): VocabularyRegradePlan | null => {
130
+ const transition = listGovernedVocabularyTransitions().find(
131
+ (candidate) => candidate.from === from
132
+ );
133
+ if (transition === undefined) {
134
+ return null;
135
+ }
136
+ if (transition.target.kind === 'single' && transition.target.to !== to) {
137
+ return null;
138
+ }
139
+ return vocabularyRegradePlanFromTransition(transition, to);
140
+ };
141
+
81
142
  export const listVocabularyRegradePlansFromRegistry =
82
143
  (): readonly VocabularyRegradePlan[] =>
83
144
  listGovernedVocabularyTransitions()
84
- .map(vocabularyRegradePlanFromTransition)
145
+ .map((transition) => vocabularyRegradePlanFromTransition(transition))
85
146
  .filter((plan): plan is VocabularyRegradePlan => plan !== null);
86
147
 
148
+ /**
149
+ * Derive conservative current-tree audit plans for every single-target
150
+ * governed transition. Transitions that are unsafe to rewrite mechanically
151
+ * still participate with every known form deferred to review.
152
+ */
153
+ export const listVocabularyRegradeAuditPlansFromRegistry =
154
+ (): readonly VocabularyRegradePlan[] =>
155
+ listGovernedVocabularyTransitions().flatMap((transition) => {
156
+ if (
157
+ transition.status === 'planned' ||
158
+ transition.target.kind !== 'single'
159
+ ) {
160
+ return [];
161
+ }
162
+ const runnable = vocabularyRegradePlanFromTransition(transition);
163
+ if (runnable !== null) {
164
+ return [runnable];
165
+ }
166
+ const scope = scopeFromTransition(transition);
167
+ return [
168
+ {
169
+ caseSensitive: true,
170
+ deferForms: uniqueForms([
171
+ ...transition.oldForms,
172
+ ...transition.reviewForms,
173
+ ]),
174
+ from: transition.from,
175
+ id: transition.id,
176
+ intent: transition.intent,
177
+ kind: 'vocabulary' as const,
178
+ preserve: preserveRulesFromTransition(transition),
179
+ ...(scope === undefined ? {} : { scope }),
180
+ to: transition.target.to,
181
+ },
182
+ ];
183
+ });
184
+
87
185
  export const vocabularyRegradeTransitionForInput = (
88
186
  from: string,
89
187
  to: string
@@ -91,6 +189,7 @@ export const vocabularyRegradeTransitionForInput = (
91
189
  listGovernedVocabularyTransitions().find(
92
190
  (transition) =>
93
191
  transition.from === from &&
94
- transition.target.kind === 'single' &&
95
- transition.target.to === to
192
+ (transition.target.kind === 'single'
193
+ ? transition.target.to === to
194
+ : transition.target.options.some((option) => option.to === to))
96
195
  );