@ontrails/trails 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.
- package/CHANGELOG.md +57 -0
- package/README.md +1 -1
- package/package.json +16 -17
- package/src/mcp-options.ts +1 -0
- package/src/regrade/audit.ts +482 -0
- package/src/regrade/history.ts +250 -42
- package/src/regrade/live-api-preserve.ts +117 -2
- package/src/regrade/plan-artifact.ts +192 -13
- package/src/regrade/plan-derivation.ts +301 -0
- package/src/regrade/source-transaction.ts +75 -0
- package/src/release/check.ts +312 -10
- package/src/release/config.ts +10 -0
- package/src/release/index.ts +5 -0
- package/src/release/package-route-facts.ts +146 -0
- package/src/run-completions-install.ts +4 -4
- package/src/run-trace.ts +4 -4
- package/src/trails/dev-support.ts +1 -1
- package/src/trails/regrade.ts +802 -125
- package/src/trails/release-check.ts +8 -0
- package/src/trails/run-example.ts +2 -2
- package/src/trails/survey.ts +6 -6
- package/src/trails/topo-output-schemas.ts +8 -8
- package/src/trails/topo-reports.ts +44 -44
- package/src/trails/topo-store-support.ts +2 -4
- package/src/trails/warden-guide.ts +1 -1
package/src/trails/regrade.ts
CHANGED
|
@@ -15,19 +15,21 @@ import {
|
|
|
15
15
|
import type { PathScope, Result as TrailsResult } from '@ontrails/core';
|
|
16
16
|
import {
|
|
17
17
|
createGovernedAstIdentifierRenameClasses,
|
|
18
|
-
listVocabularyRegradePlansFromRegistry,
|
|
19
18
|
loadWardenRegradeClasses,
|
|
20
19
|
readVocabularyTransitionRecord,
|
|
21
20
|
regradeReportOutput,
|
|
21
|
+
runFileRenameRegrade,
|
|
22
22
|
runRegrade,
|
|
23
23
|
runVocabularyRegrade,
|
|
24
24
|
transitionRecordReportWithSummary,
|
|
25
25
|
vocabularyRegradeTransitionForInput,
|
|
26
26
|
vocabularyDispositionValues,
|
|
27
27
|
vocabularyRegradePlanSchema,
|
|
28
|
+
vocabularyRegradePlanForInput,
|
|
28
29
|
writeVocabularyTransitionRecord,
|
|
29
30
|
} from '@ontrails/regrade';
|
|
30
31
|
import type {
|
|
32
|
+
FileRenameRegradeRun,
|
|
31
33
|
RegradeApplySummary,
|
|
32
34
|
RegradeReport,
|
|
33
35
|
RegradeReportEntry,
|
|
@@ -37,6 +39,7 @@ import type {
|
|
|
37
39
|
VocabularyRegradePlan,
|
|
38
40
|
VocabularyPreserveInventoryEntry,
|
|
39
41
|
} from '@ontrails/regrade';
|
|
42
|
+
import { listGovernedVocabularyTransitions } from '@ontrails/warden';
|
|
40
43
|
import { execFileSync } from 'node:child_process';
|
|
41
44
|
import {
|
|
42
45
|
existsSync,
|
|
@@ -47,9 +50,14 @@ import {
|
|
|
47
50
|
writeFileSync,
|
|
48
51
|
} from 'node:fs';
|
|
49
52
|
import type { Dirent } from 'node:fs';
|
|
50
|
-
import { basename, dirname, isAbsolute, join } from 'node:path';
|
|
53
|
+
import { basename, dirname, extname, isAbsolute, join, posix } from 'node:path';
|
|
51
54
|
import { z } from 'zod';
|
|
52
55
|
|
|
56
|
+
import {
|
|
57
|
+
auditRegradeHistory,
|
|
58
|
+
regradeAuditInputSchema,
|
|
59
|
+
regradeAuditOutputSchema,
|
|
60
|
+
} from '../regrade/audit.js';
|
|
53
61
|
import { loadRegradeConfig } from '../regrade/config.js';
|
|
54
62
|
import {
|
|
55
63
|
REGRADE_HISTORY_SCHEMA_VERSION,
|
|
@@ -57,16 +65,24 @@ import {
|
|
|
57
65
|
readRegradeHistoryArtifact,
|
|
58
66
|
regradeHistoryPathForPlan,
|
|
59
67
|
resolveRegradeHistoryPath,
|
|
68
|
+
validateGovernedRegradePlan,
|
|
60
69
|
verifyRegradeHistoryRuns,
|
|
61
70
|
} from '../regrade/history.js';
|
|
62
71
|
import type { RegradeHistorySummary } from '../regrade/history.js';
|
|
63
72
|
import { deriveLiveApiPreserveInventory } from '../regrade/live-api-preserve.js';
|
|
73
|
+
import { deriveRegradePlanDerivation } from '../regrade/plan-derivation.js';
|
|
74
|
+
import {
|
|
75
|
+
regradeApplyErrorAfterRollback,
|
|
76
|
+
snapshotRegradeSources,
|
|
77
|
+
} from '../regrade/source-transaction.js';
|
|
64
78
|
import {
|
|
65
79
|
REGRADE_PLAN_SCHEMA_VERSION,
|
|
80
|
+
canonicalJsonStringify,
|
|
81
|
+
currentRegradeSourceHashMatches,
|
|
82
|
+
isGeneratedRegradeArtifactPath,
|
|
66
83
|
regradePlanArtifactSchema,
|
|
67
84
|
regradePlanPathForPlan,
|
|
68
85
|
regradeSourceHash,
|
|
69
|
-
regradeSourceHashMatches,
|
|
70
86
|
rootRelativePath,
|
|
71
87
|
} from '../regrade/plan-artifact.js';
|
|
72
88
|
import type {
|
|
@@ -88,6 +104,21 @@ const regradePathScopeInputSchema = pathScopeSchema.extend({
|
|
|
88
104
|
include: pathScopeSchema.shape.include.describe(
|
|
89
105
|
'Root-relative path patterns to include in vocabulary regrade mode'
|
|
90
106
|
),
|
|
107
|
+
policyClassified: z
|
|
108
|
+
.array(
|
|
109
|
+
z.object({
|
|
110
|
+
disposition: z.enum(vocabularyDispositionValues),
|
|
111
|
+
expectMatches: z.boolean().optional(),
|
|
112
|
+
paths: z.array(z.string().min(1)).min(1),
|
|
113
|
+
reason: z.string().min(1),
|
|
114
|
+
})
|
|
115
|
+
)
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('Protected paths scanned and counted without default rewrites'),
|
|
118
|
+
teachingSurfaces: z
|
|
119
|
+
.array(z.string().min(1))
|
|
120
|
+
.optional()
|
|
121
|
+
.describe('Expected current teaching-surface path patterns'),
|
|
91
122
|
});
|
|
92
123
|
|
|
93
124
|
const regradePreserveRuleInputSchema = z.object({
|
|
@@ -112,6 +143,11 @@ const regradePreserveInputSchema = z.union([
|
|
|
112
143
|
regradePreserveRuleInputSchema,
|
|
113
144
|
]);
|
|
114
145
|
|
|
146
|
+
const regradeFileRenameInputSchema = z.object({
|
|
147
|
+
from: z.string().min(1).describe('Root-relative source file path'),
|
|
148
|
+
to: z.string().min(1).describe('Root-relative target file path'),
|
|
149
|
+
});
|
|
150
|
+
|
|
115
151
|
const regradeInputSchema = regradePathScopeInputSchema.extend({
|
|
116
152
|
apply: z
|
|
117
153
|
.boolean()
|
|
@@ -131,6 +167,10 @@ const regradeInputSchema = regradePathScopeInputSchema.extend({
|
|
|
131
167
|
.string()
|
|
132
168
|
.optional()
|
|
133
169
|
.describe('Path to a Trails config file with regrade defaults'),
|
|
170
|
+
fileRenames: z
|
|
171
|
+
.array(regradeFileRenameInputSchema)
|
|
172
|
+
.optional()
|
|
173
|
+
.describe('Governed file moves with references derived from scope'),
|
|
134
174
|
from: z
|
|
135
175
|
.string()
|
|
136
176
|
.min(1)
|
|
@@ -227,6 +267,10 @@ const regradePlanInputSchema = regradePathScopeInputSchema.extend({
|
|
|
227
267
|
.boolean()
|
|
228
268
|
.default(false)
|
|
229
269
|
.describe('Stage wide-net review candidates in the saved plan'),
|
|
270
|
+
fileRenames: z
|
|
271
|
+
.array(regradeFileRenameInputSchema)
|
|
272
|
+
.optional()
|
|
273
|
+
.describe('Governed file moves with references derived from scope'),
|
|
230
274
|
fresh: z
|
|
231
275
|
.boolean()
|
|
232
276
|
.default(false)
|
|
@@ -297,7 +341,7 @@ const regradeAdjustInputSchema = z.object({
|
|
|
297
341
|
transition: z
|
|
298
342
|
.string()
|
|
299
343
|
.min(1)
|
|
300
|
-
.describe('Graduated transition name, e.g.
|
|
344
|
+
.describe('Graduated transition name, e.g. <transition-name>'),
|
|
301
345
|
});
|
|
302
346
|
|
|
303
347
|
type RegradePlanInput = z.output<typeof regradePlanInputSchema>;
|
|
@@ -308,6 +352,7 @@ type RegradeApplyPlanInput = z.output<typeof regradeApplyPlanInputSchema>;
|
|
|
308
352
|
type RegradeAdjustInput = z.output<typeof regradeAdjustInputSchema>;
|
|
309
353
|
|
|
310
354
|
const hasVocabularyInput = (input: RegradeInput) =>
|
|
355
|
+
input.fileRenames !== undefined ||
|
|
311
356
|
input.from !== undefined ||
|
|
312
357
|
input.check ||
|
|
313
358
|
input.include !== undefined ||
|
|
@@ -315,6 +360,8 @@ const hasVocabularyInput = (input: RegradeInput) =>
|
|
|
315
360
|
input.overrides !== undefined ||
|
|
316
361
|
input.planRecord !== undefined ||
|
|
317
362
|
input.preserve !== undefined ||
|
|
363
|
+
input.policyClassified !== undefined ||
|
|
364
|
+
input.teachingSurfaces !== undefined ||
|
|
318
365
|
input.to !== undefined;
|
|
319
366
|
|
|
320
367
|
const classModeCollection = (
|
|
@@ -376,6 +423,15 @@ const vocabularyProseExtensions: readonly string[] = [
|
|
|
376
423
|
'.txt',
|
|
377
424
|
] as const;
|
|
378
425
|
|
|
426
|
+
const vocabularyEvidenceExtensions: readonly string[] = [
|
|
427
|
+
...symbolSourceExtensions,
|
|
428
|
+
...vocabularyProseExtensions,
|
|
429
|
+
'.json',
|
|
430
|
+
'.jsonc',
|
|
431
|
+
'.yaml',
|
|
432
|
+
'.yml',
|
|
433
|
+
] as const;
|
|
434
|
+
|
|
379
435
|
const normalizeExtension = (extension: string): string =>
|
|
380
436
|
extension === '' || extension.startsWith('.') ? extension : `.${extension}`;
|
|
381
437
|
|
|
@@ -457,6 +513,14 @@ const symbolOccurrenceIsPreserved = (
|
|
|
457
513
|
preserveRuleMatchesSymbolOccurrence(rule, occurrence)
|
|
458
514
|
) ?? false;
|
|
459
515
|
|
|
516
|
+
const symbolOccurrenceIsPolicyClassified = (
|
|
517
|
+
scope: VocabularyRegradePlan['scope'] | undefined,
|
|
518
|
+
path: string
|
|
519
|
+
): boolean =>
|
|
520
|
+
scope?.policyClassified?.some((policy) =>
|
|
521
|
+
matchesAnyPathGlob(path, policy.paths)
|
|
522
|
+
) ?? false;
|
|
523
|
+
|
|
460
524
|
const vocabularyScopeFromConfig = (
|
|
461
525
|
scope: RegradeConfigScope | undefined
|
|
462
526
|
): VocabularyRegradePlan['scope'] | undefined =>
|
|
@@ -492,9 +556,9 @@ const vocabularyPreserveFromInput = (
|
|
|
492
556
|
const vocabularyRegistryPlanForInput = (
|
|
493
557
|
input: RegradeInput
|
|
494
558
|
): VocabularyRegradePlan | undefined =>
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
559
|
+
input.from === undefined || input.to === undefined
|
|
560
|
+
? undefined
|
|
561
|
+
: (vocabularyRegradePlanForInput(input.from, input.to) ?? undefined);
|
|
498
562
|
|
|
499
563
|
const uniqueSorted = (values: readonly string[]): readonly string[] =>
|
|
500
564
|
[...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
|
@@ -511,10 +575,22 @@ const mergeScopeList = (
|
|
|
511
575
|
return merged.length === 0 ? undefined : merged;
|
|
512
576
|
};
|
|
513
577
|
|
|
578
|
+
const scopePathsOverlap = (left: string, right: string): boolean =>
|
|
579
|
+
left === right ||
|
|
580
|
+
matchesAnyPathGlob(left, [right]) ||
|
|
581
|
+
matchesAnyPathGlob(right, [left]);
|
|
582
|
+
|
|
514
583
|
const mergeVocabularyScope = (
|
|
515
584
|
registryScope: VocabularyRegradePlan['scope'] | undefined,
|
|
516
585
|
configScope: VocabularyRegradePlan['scope'] | undefined,
|
|
517
|
-
input: Pick<
|
|
586
|
+
input: Pick<
|
|
587
|
+
RegradeInput,
|
|
588
|
+
| 'exclude'
|
|
589
|
+
| 'extensions'
|
|
590
|
+
| 'include'
|
|
591
|
+
| 'policyClassified'
|
|
592
|
+
| 'teachingSurfaces'
|
|
593
|
+
>
|
|
518
594
|
): VocabularyRegradePlan['scope'] | undefined => {
|
|
519
595
|
const callerExclude = input.exclude ?? configScope?.exclude;
|
|
520
596
|
const callerInclude = input.include ?? configScope?.include;
|
|
@@ -522,20 +598,37 @@ const mergeVocabularyScope = (
|
|
|
522
598
|
input.extensions ?? configScope?.extensions ?? registryScope?.extensions;
|
|
523
599
|
const exclude = mergeScopeList(registryScope?.exclude, callerExclude);
|
|
524
600
|
const include = mergeScopeList(registryScope?.include, callerInclude);
|
|
601
|
+
const policyClassified = [
|
|
602
|
+
...(registryScope?.policyClassified ?? [])
|
|
603
|
+
.map((policy) => ({
|
|
604
|
+
...policy,
|
|
605
|
+
paths: policy.paths.filter(
|
|
606
|
+
(path) =>
|
|
607
|
+
!callerExclude?.some((excludedPath) =>
|
|
608
|
+
scopePathsOverlap(path, excludedPath)
|
|
609
|
+
)
|
|
610
|
+
),
|
|
611
|
+
}))
|
|
612
|
+
.filter((policy) => policy.paths.length > 0),
|
|
613
|
+
...(input.policyClassified ?? []),
|
|
614
|
+
];
|
|
615
|
+
const teachingSurfaces = mergeScopeList(
|
|
616
|
+
registryScope?.teachingSurfaces,
|
|
617
|
+
input.teachingSurfaces
|
|
618
|
+
);
|
|
525
619
|
|
|
526
|
-
|
|
527
|
-
exclude
|
|
528
|
-
extensions
|
|
529
|
-
include
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return {
|
|
535
|
-
...(exclude === undefined ? {} : { exclude }),
|
|
536
|
-
...(extensions === undefined ? {} : { extensions }),
|
|
537
|
-
...(include === undefined ? {} : { include }),
|
|
620
|
+
const fields = {
|
|
621
|
+
exclude,
|
|
622
|
+
extensions,
|
|
623
|
+
include,
|
|
624
|
+
policyClassified:
|
|
625
|
+
policyClassified.length === 0 ? undefined : policyClassified,
|
|
626
|
+
teachingSurfaces,
|
|
538
627
|
};
|
|
628
|
+
const scope = Object.fromEntries(
|
|
629
|
+
Object.entries(fields).filter(([, value]) => value !== undefined)
|
|
630
|
+
) as NonNullable<VocabularyRegradePlan['scope']>;
|
|
631
|
+
return Object.keys(scope).length === 0 ? undefined : scope;
|
|
539
632
|
};
|
|
540
633
|
|
|
541
634
|
const mergeNumericRecords = (
|
|
@@ -723,11 +816,16 @@ const transitionRunReportForRegradeReport = (
|
|
|
723
816
|
reasons,
|
|
724
817
|
remaining: open,
|
|
725
818
|
remainingByDisposition,
|
|
726
|
-
status: open === 0 ? 'green' : 'open',
|
|
819
|
+
status: open === 0 && reasons.length === 0 ? 'green' : 'open',
|
|
727
820
|
},
|
|
728
821
|
modified,
|
|
729
822
|
open,
|
|
823
|
+
scopeTiers: {
|
|
824
|
+
'in-scope': report.rewritten + report.review,
|
|
825
|
+
'policy-classified': 0,
|
|
826
|
+
},
|
|
730
827
|
skipped: report.skipped,
|
|
828
|
+
teachingSurfaces: { expected: [], missing: [], touched: [] },
|
|
731
829
|
};
|
|
732
830
|
};
|
|
733
831
|
|
|
@@ -760,11 +858,20 @@ const mergeTransitionRunReportWithSymbol = (
|
|
|
760
858
|
reasons,
|
|
761
859
|
remaining: open,
|
|
762
860
|
remainingByDisposition,
|
|
763
|
-
status: open === 0 ? 'green' : 'open',
|
|
861
|
+
status: open === 0 && reasons.length === 0 ? 'green' : 'open',
|
|
764
862
|
},
|
|
765
863
|
modified,
|
|
766
864
|
open,
|
|
865
|
+
scopeTiers: {
|
|
866
|
+
'in-scope':
|
|
867
|
+
vocabularyReport.scopeTiers['in-scope'] +
|
|
868
|
+
symbolRunReport.scopeTiers['in-scope'],
|
|
869
|
+
'policy-classified':
|
|
870
|
+
vocabularyReport.scopeTiers['policy-classified'] +
|
|
871
|
+
symbolRunReport.scopeTiers['policy-classified'],
|
|
872
|
+
},
|
|
767
873
|
skipped: vocabularyReport.skipped + symbolRunReport.skipped,
|
|
874
|
+
teachingSurfaces: vocabularyReport.teachingSurfaces,
|
|
768
875
|
};
|
|
769
876
|
};
|
|
770
877
|
|
|
@@ -877,18 +984,18 @@ const vocabularySymbolCollection = (
|
|
|
877
984
|
};
|
|
878
985
|
};
|
|
879
986
|
|
|
880
|
-
const
|
|
987
|
+
const vocabularyEvidenceScope = (
|
|
881
988
|
scope: VocabularyRegradePlan['scope'] | undefined
|
|
882
989
|
): NonNullable<VocabularyRegradePlan['scope']> | null => {
|
|
883
990
|
const explicitExtensions = scope?.extensions !== undefined;
|
|
884
991
|
const extensions =
|
|
885
992
|
scope?.extensions === undefined
|
|
886
|
-
?
|
|
993
|
+
? vocabularyEvidenceExtensions
|
|
887
994
|
: uniqueSorted(
|
|
888
995
|
scope.extensions
|
|
889
996
|
.map(normalizeExtension)
|
|
890
997
|
.filter((extension) =>
|
|
891
|
-
|
|
998
|
+
vocabularyEvidenceExtensions.includes(extension)
|
|
892
999
|
)
|
|
893
1000
|
);
|
|
894
1001
|
|
|
@@ -903,13 +1010,19 @@ const vocabularyProseScope = (
|
|
|
903
1010
|
? {}
|
|
904
1011
|
: { ignoredDirectories: scope.ignoredDirectories }),
|
|
905
1012
|
...(scope?.include === undefined ? {} : { include: scope.include }),
|
|
1013
|
+
...(scope?.policyClassified === undefined
|
|
1014
|
+
? {}
|
|
1015
|
+
: { policyClassified: scope.policyClassified }),
|
|
1016
|
+
...(scope?.teachingSurfaces === undefined
|
|
1017
|
+
? {}
|
|
1018
|
+
: { teachingSurfaces: scope.teachingSurfaces }),
|
|
906
1019
|
};
|
|
907
1020
|
};
|
|
908
1021
|
|
|
909
|
-
const
|
|
1022
|
+
const vocabularyEvidencePlan = (
|
|
910
1023
|
plan: VocabularyRegradePlan
|
|
911
1024
|
): VocabularyRegradePlan | null => {
|
|
912
|
-
const scope =
|
|
1025
|
+
const scope = vocabularyEvidenceScope(plan.scope);
|
|
913
1026
|
if (scope === null) {
|
|
914
1027
|
return null;
|
|
915
1028
|
}
|
|
@@ -917,6 +1030,65 @@ const vocabularyProsePlan = (
|
|
|
917
1030
|
return { ...plan, scope };
|
|
918
1031
|
};
|
|
919
1032
|
|
|
1033
|
+
const withoutNotSelectedSourceCount = (
|
|
1034
|
+
counts: Readonly<Record<string, number>>
|
|
1035
|
+
): Readonly<Record<string, number>> =>
|
|
1036
|
+
Object.fromEntries(
|
|
1037
|
+
Object.entries(counts).filter(
|
|
1038
|
+
([reason]) => reason !== 'not-selected-source'
|
|
1039
|
+
)
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
const withoutVocabularySourceFilterSkips = (
|
|
1043
|
+
report: RegradeReport | null
|
|
1044
|
+
): RegradeReport | null => {
|
|
1045
|
+
const rejected = report?.skipsByReason['not-selected-source'] ?? 0;
|
|
1046
|
+
if (report === null || rejected === 0) {
|
|
1047
|
+
return report;
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
...report,
|
|
1051
|
+
...(report.apply === undefined
|
|
1052
|
+
? {}
|
|
1053
|
+
: {
|
|
1054
|
+
apply: {
|
|
1055
|
+
...report.apply,
|
|
1056
|
+
skipped: Math.max(0, report.apply.skipped - rejected),
|
|
1057
|
+
},
|
|
1058
|
+
}),
|
|
1059
|
+
entries: report.entries.filter(
|
|
1060
|
+
(entry) => entry.reason !== 'not-selected-source'
|
|
1061
|
+
),
|
|
1062
|
+
scan: {
|
|
1063
|
+
...report.scan,
|
|
1064
|
+
files: {
|
|
1065
|
+
...report.scan.files,
|
|
1066
|
+
skipped: Math.max(0, report.scan.files.skipped - rejected),
|
|
1067
|
+
},
|
|
1068
|
+
skippedByReason: withoutNotSelectedSourceCount(
|
|
1069
|
+
report.scan.skippedByReason
|
|
1070
|
+
),
|
|
1071
|
+
},
|
|
1072
|
+
skipped: Math.max(0, report.skipped - rejected),
|
|
1073
|
+
skipsByReason: withoutNotSelectedSourceCount(report.skipsByReason),
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
const vocabularyEvidenceSource = (
|
|
1078
|
+
path: string,
|
|
1079
|
+
scope: VocabularyRegradePlan['scope'] | undefined
|
|
1080
|
+
): boolean =>
|
|
1081
|
+
vocabularyProseExtensions.includes(extname(path)) ||
|
|
1082
|
+
symbolOccurrenceIsPolicyClassified(scope, path);
|
|
1083
|
+
|
|
1084
|
+
const vocabularyProseEngineApplies = (
|
|
1085
|
+
scope: VocabularyRegradePlan['scope'] | undefined
|
|
1086
|
+
): boolean =>
|
|
1087
|
+
scope?.extensions === undefined ||
|
|
1088
|
+
scope.extensions.some((extension) =>
|
|
1089
|
+
vocabularyProseExtensions.includes(normalizeExtension(extension))
|
|
1090
|
+
);
|
|
1091
|
+
|
|
920
1092
|
const mergeVocabularyOverrides = (
|
|
921
1093
|
registryPlan: VocabularyRegradePlan | undefined,
|
|
922
1094
|
input: z.output<typeof regradeInputSchema>
|
|
@@ -946,9 +1118,67 @@ const vocabularyIntentForInput = (
|
|
|
946
1118
|
return registryPlan?.intent;
|
|
947
1119
|
};
|
|
948
1120
|
|
|
1121
|
+
const classifiedOverrideError = (
|
|
1122
|
+
input: RegradeInput & { readonly from: string; readonly to: string }
|
|
1123
|
+
): ValidationError | undefined => {
|
|
1124
|
+
const transition = vocabularyRegradeTransitionForInput(input.from, input.to);
|
|
1125
|
+
return transition?.target.kind === 'classified' &&
|
|
1126
|
+
input.overrides !== undefined
|
|
1127
|
+
? new ValidationError(
|
|
1128
|
+
'Classified governed vocabulary transitions are review-only and cannot accept rewrite overrides.'
|
|
1129
|
+
)
|
|
1130
|
+
: undefined;
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
const governedTargetError = (
|
|
1134
|
+
input: RegradeInput & { readonly from: string; readonly to: string }
|
|
1135
|
+
): ValidationError | undefined => {
|
|
1136
|
+
const governedFormTransition = listGovernedVocabularyTransitions().find(
|
|
1137
|
+
(candidate) =>
|
|
1138
|
+
candidate.from !== input.from &&
|
|
1139
|
+
(candidate.oldForms.includes(input.from) ||
|
|
1140
|
+
candidate.reviewForms.includes(input.from))
|
|
1141
|
+
);
|
|
1142
|
+
if (governedFormTransition !== undefined) {
|
|
1143
|
+
return new ValidationError(
|
|
1144
|
+
`Governed vocabulary form "${input.from}" belongs to transition "${governedFormTransition.id}". Plan from its canonical source "${governedFormTransition.from}" instead.`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
const transition = listGovernedVocabularyTransitions().find(
|
|
1148
|
+
(candidate) => candidate.from === input.from
|
|
1149
|
+
);
|
|
1150
|
+
if (
|
|
1151
|
+
transition === undefined ||
|
|
1152
|
+
vocabularyRegradeTransitionForInput(input.from, input.to) !== undefined
|
|
1153
|
+
) {
|
|
1154
|
+
return undefined;
|
|
1155
|
+
}
|
|
1156
|
+
const expectedTargets =
|
|
1157
|
+
transition.target.kind === 'single'
|
|
1158
|
+
? [transition.target.to]
|
|
1159
|
+
: transition.target.options.map((option) => option.to);
|
|
1160
|
+
return new ValidationError(
|
|
1161
|
+
`Governed vocabulary transition "${transition.id}" does not define target "${input.to}". Expected ${expectedTargets.map((target) => `"${target}"`).join(' or ')}`
|
|
1162
|
+
);
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
const registryFileRenamesForRoot = (
|
|
1166
|
+
registryPlan: VocabularyRegradePlan | undefined,
|
|
1167
|
+
rootDir: string | undefined
|
|
1168
|
+
): VocabularyRegradePlan['fileRenames'] | undefined => {
|
|
1169
|
+
const fileRenames = registryPlan?.fileRenames?.filter(
|
|
1170
|
+
(rename) =>
|
|
1171
|
+
rootDir === undefined ||
|
|
1172
|
+
existsSync(join(rootDir, rename.from)) ||
|
|
1173
|
+
existsSync(join(rootDir, rename.to))
|
|
1174
|
+
);
|
|
1175
|
+
return fileRenames?.length === 0 ? undefined : fileRenames;
|
|
1176
|
+
};
|
|
1177
|
+
|
|
949
1178
|
const buildVocabularyPlan = (
|
|
950
1179
|
input: RegradeInput,
|
|
951
|
-
configScope?: VocabularyRegradePlan['scope']
|
|
1180
|
+
configScope?: VocabularyRegradePlan['scope'],
|
|
1181
|
+
rootDir?: string
|
|
952
1182
|
): TrailsResult<VocabularyRegradePlan, ValidationError> => {
|
|
953
1183
|
if (input.from === undefined || input.to === undefined) {
|
|
954
1184
|
return Result.err(
|
|
@@ -964,11 +1194,29 @@ const buildVocabularyPlan = (
|
|
|
964
1194
|
}
|
|
965
1195
|
|
|
966
1196
|
const preserve = vocabularyPreserveFromInput(input.preserve);
|
|
1197
|
+
const targetError = governedTargetError({
|
|
1198
|
+
...input,
|
|
1199
|
+
from: input.from,
|
|
1200
|
+
to: input.to,
|
|
1201
|
+
});
|
|
1202
|
+
if (targetError !== undefined) {
|
|
1203
|
+
return Result.err(targetError);
|
|
1204
|
+
}
|
|
967
1205
|
const registryPlan = vocabularyRegistryPlanForInput(input);
|
|
1206
|
+
const overrideError = classifiedOverrideError({
|
|
1207
|
+
...input,
|
|
1208
|
+
from: input.from,
|
|
1209
|
+
to: input.to,
|
|
1210
|
+
});
|
|
1211
|
+
if (overrideError !== undefined) {
|
|
1212
|
+
return Result.err(overrideError);
|
|
1213
|
+
}
|
|
968
1214
|
const intent = vocabularyIntentForInput(input, registryPlan);
|
|
969
1215
|
const overrides = mergeVocabularyOverrides(registryPlan, input);
|
|
970
1216
|
const preserveRules = mergeVocabularyPreserveRules(registryPlan, preserve);
|
|
971
1217
|
const scope = mergeVocabularyScope(registryPlan?.scope, configScope, input);
|
|
1218
|
+
const fileRenames =
|
|
1219
|
+
input.fileRenames ?? registryFileRenamesForRoot(registryPlan, rootDir);
|
|
972
1220
|
|
|
973
1221
|
return Result.ok({
|
|
974
1222
|
...(registryPlan?.caseSensitive === undefined
|
|
@@ -977,6 +1225,7 @@ const buildVocabularyPlan = (
|
|
|
977
1225
|
...(registryPlan?.deferForms === undefined
|
|
978
1226
|
? {}
|
|
979
1227
|
: { deferForms: registryPlan.deferForms }),
|
|
1228
|
+
...(fileRenames === undefined ? {} : { fileRenames }),
|
|
980
1229
|
from: input.from,
|
|
981
1230
|
id: registryPlan?.id ?? `vocabulary:${input.from}->${input.to}`,
|
|
982
1231
|
kind: 'vocabulary',
|
|
@@ -988,6 +1237,33 @@ const buildVocabularyPlan = (
|
|
|
988
1237
|
});
|
|
989
1238
|
};
|
|
990
1239
|
|
|
1240
|
+
const withDerivedTeachingSurfaceInventory = (params: {
|
|
1241
|
+
readonly plan: VocabularyRegradePlan;
|
|
1242
|
+
readonly report: RegradeReport;
|
|
1243
|
+
}): VocabularyRegradePlan => {
|
|
1244
|
+
const expected = params.plan.scope?.teachingSurfaces;
|
|
1245
|
+
if (expected === undefined) {
|
|
1246
|
+
return params.plan;
|
|
1247
|
+
}
|
|
1248
|
+
const teachingSurfaces = uniqueSorted(
|
|
1249
|
+
(params.report.run?.ledger.occurrences ?? [])
|
|
1250
|
+
.filter(
|
|
1251
|
+
(occurrence) =>
|
|
1252
|
+
occurrence.scopeTier === 'in-scope' &&
|
|
1253
|
+
!isGeneratedRegradeArtifactPath(occurrence.path) &&
|
|
1254
|
+
matchesAnyPathGlob(occurrence.path, expected)
|
|
1255
|
+
)
|
|
1256
|
+
.map((occurrence) => occurrence.path)
|
|
1257
|
+
);
|
|
1258
|
+
const scope = { ...params.plan.scope };
|
|
1259
|
+
if (teachingSurfaces.length === 0) {
|
|
1260
|
+
delete scope.teachingSurfaces;
|
|
1261
|
+
} else {
|
|
1262
|
+
scope.teachingSurfaces = teachingSurfaces;
|
|
1263
|
+
}
|
|
1264
|
+
return { ...params.plan, scope };
|
|
1265
|
+
};
|
|
1266
|
+
|
|
991
1267
|
const regradeRootNotFound = (rootDir: string) =>
|
|
992
1268
|
Result.err(
|
|
993
1269
|
new NotFoundError(
|
|
@@ -1078,6 +1354,10 @@ const runGovernedSymbolRegrade = (params: {
|
|
|
1078
1354
|
},
|
|
1079
1355
|
{
|
|
1080
1356
|
shouldPreserve: (occurrence) =>
|
|
1357
|
+
symbolOccurrenceIsPolicyClassified(
|
|
1358
|
+
params.plan.scope,
|
|
1359
|
+
occurrence.path
|
|
1360
|
+
) ||
|
|
1081
1361
|
symbolOccurrenceIsPreserved(params.plan.preserve, {
|
|
1082
1362
|
end: occurrence.end,
|
|
1083
1363
|
form: occurrence.from,
|
|
@@ -1157,7 +1437,11 @@ const reportWithHistorySummary = (
|
|
|
1157
1437
|
): RegradeReport => ({
|
|
1158
1438
|
...report,
|
|
1159
1439
|
history: {
|
|
1440
|
+
id: params.id,
|
|
1160
1441
|
path: params.path,
|
|
1442
|
+
...(params.provenance === undefined
|
|
1443
|
+
? {}
|
|
1444
|
+
: { provenance: params.provenance }),
|
|
1161
1445
|
schemaVersion: params.schemaVersion,
|
|
1162
1446
|
status: params.status,
|
|
1163
1447
|
},
|
|
@@ -1166,6 +1450,7 @@ const reportWithHistorySummary = (
|
|
|
1166
1450
|
const authoredPlanFieldKeys = [
|
|
1167
1451
|
'caseSensitive',
|
|
1168
1452
|
'deferForms',
|
|
1453
|
+
'fileRenames',
|
|
1169
1454
|
'id',
|
|
1170
1455
|
'intent',
|
|
1171
1456
|
'overrides',
|
|
@@ -1179,6 +1464,7 @@ const isAuthoredPlanField = (
|
|
|
1179
1464
|
): boolean => {
|
|
1180
1465
|
switch (key) {
|
|
1181
1466
|
case 'intent':
|
|
1467
|
+
case 'fileRenames':
|
|
1182
1468
|
case 'overrides':
|
|
1183
1469
|
case 'preserve': {
|
|
1184
1470
|
return input[key] !== undefined;
|
|
@@ -1187,7 +1473,9 @@ const isAuthoredPlanField = (
|
|
|
1187
1473
|
return (
|
|
1188
1474
|
input.exclude !== undefined ||
|
|
1189
1475
|
input.extensions !== undefined ||
|
|
1190
|
-
input.include !== undefined
|
|
1476
|
+
input.include !== undefined ||
|
|
1477
|
+
input.policyClassified !== undefined ||
|
|
1478
|
+
input.teachingSurfaces !== undefined
|
|
1191
1479
|
);
|
|
1192
1480
|
}
|
|
1193
1481
|
default: {
|
|
@@ -1209,6 +1497,7 @@ const regradePlanProvenanceForInput = (
|
|
|
1209
1497
|
for (const key of [
|
|
1210
1498
|
'caseSensitive',
|
|
1211
1499
|
'deferForms',
|
|
1500
|
+
'fileRenames',
|
|
1212
1501
|
'id',
|
|
1213
1502
|
'intent',
|
|
1214
1503
|
'overrides',
|
|
@@ -1256,6 +1545,7 @@ const preserveAuthoredPlanProvenance = (
|
|
|
1256
1545
|
};
|
|
1257
1546
|
|
|
1258
1547
|
const buildRegradePlanArtifact = (params: {
|
|
1548
|
+
readonly derivation?: RegradePlanArtifact['derivation'];
|
|
1259
1549
|
readonly expansion?: RegradePlanArtifact['expansion'];
|
|
1260
1550
|
readonly input: RegradePlanInput;
|
|
1261
1551
|
readonly plan: VocabularyRegradePlan;
|
|
@@ -1265,6 +1555,9 @@ const buildRegradePlanArtifact = (params: {
|
|
|
1265
1555
|
}): RegradePlanArtifact => {
|
|
1266
1556
|
const absolutePath = regradePlanPathForPlan(params.rootDir, params.plan);
|
|
1267
1557
|
return {
|
|
1558
|
+
...(params.derivation === undefined
|
|
1559
|
+
? {}
|
|
1560
|
+
: { derivation: params.derivation }),
|
|
1268
1561
|
...(params.expansion === undefined ? {} : { expansion: params.expansion }),
|
|
1269
1562
|
kind: 'regrade-plan',
|
|
1270
1563
|
path: rootRelativePath(params.rootDir, absolutePath),
|
|
@@ -1479,9 +1772,27 @@ const resolveRegradePlanPath = (
|
|
|
1479
1772
|
|
|
1480
1773
|
const planStatusForReport = (
|
|
1481
1774
|
artifact: RegradePlanArtifact,
|
|
1482
|
-
report: RegradeReport
|
|
1483
|
-
|
|
1484
|
-
|
|
1775
|
+
report: RegradeReport,
|
|
1776
|
+
rootDir: string
|
|
1777
|
+
): 'active' | 'stale' => {
|
|
1778
|
+
if (!currentRegradeSourceHashMatches(artifact.sourceHash, report)) {
|
|
1779
|
+
return 'stale';
|
|
1780
|
+
}
|
|
1781
|
+
if (artifact.plan.kind === 'class' || artifact.derivation === undefined) {
|
|
1782
|
+
return 'active';
|
|
1783
|
+
}
|
|
1784
|
+
const current = deriveRegradePlanDerivation({
|
|
1785
|
+
plan: artifact.plan,
|
|
1786
|
+
preserveInventory: report.run?.preserveInventory ?? [],
|
|
1787
|
+
provenance: artifact.provenance,
|
|
1788
|
+
report,
|
|
1789
|
+
rootDir,
|
|
1790
|
+
});
|
|
1791
|
+
return canonicalJsonStringify(current) ===
|
|
1792
|
+
canonicalJsonStringify(artifact.derivation)
|
|
1793
|
+
? 'active'
|
|
1794
|
+
: 'stale';
|
|
1795
|
+
};
|
|
1485
1796
|
|
|
1486
1797
|
const regradePlanGateContext = (
|
|
1487
1798
|
report: RegradeReport
|
|
@@ -1529,6 +1840,182 @@ const persistVocabularyRecord = (params: {
|
|
|
1529
1840
|
);
|
|
1530
1841
|
};
|
|
1531
1842
|
|
|
1843
|
+
const withFileRenameEvidence = (params: {
|
|
1844
|
+
readonly plan: VocabularyRegradePlan;
|
|
1845
|
+
readonly report: RegradeReport & {
|
|
1846
|
+
readonly run: NonNullable<RegradeReport['run']>;
|
|
1847
|
+
};
|
|
1848
|
+
readonly run: FileRenameRegradeRun;
|
|
1849
|
+
}): RegradeReport => {
|
|
1850
|
+
const vocabularyPaths = params.report.run.ledger.occurrences
|
|
1851
|
+
.filter((occurrence) => occurrence.scopeTier === 'in-scope')
|
|
1852
|
+
.map((occurrence) => occurrence.path);
|
|
1853
|
+
const remainingPolicyPaths = new Map<string, number>();
|
|
1854
|
+
for (const path of params.run.policyOccurrencePaths) {
|
|
1855
|
+
remainingPolicyPaths.set(path, (remainingPolicyPaths.get(path) ?? 0) + 1);
|
|
1856
|
+
}
|
|
1857
|
+
const fileInScopePaths = params.run.occurrencePaths.filter((path) => {
|
|
1858
|
+
const remaining = remainingPolicyPaths.get(path) ?? 0;
|
|
1859
|
+
if (remaining === 0) {
|
|
1860
|
+
return true;
|
|
1861
|
+
}
|
|
1862
|
+
remainingPolicyPaths.set(path, remaining - 1);
|
|
1863
|
+
return false;
|
|
1864
|
+
});
|
|
1865
|
+
const evidencePaths = [...vocabularyPaths, ...fileInScopePaths];
|
|
1866
|
+
const expected = uniqueSorted(params.plan.scope?.teachingSurfaces ?? []);
|
|
1867
|
+
const touched = expected.filter((pattern) =>
|
|
1868
|
+
evidencePaths.some((path) => matchesAnyPathGlob(path, [pattern]))
|
|
1869
|
+
);
|
|
1870
|
+
const missing = expected.filter((pattern) => !touched.includes(pattern));
|
|
1871
|
+
const vocabularyPolicyPaths = params.report.run.ledger.occurrences
|
|
1872
|
+
.filter((occurrence) => occurrence.scopeTier === 'policy-classified')
|
|
1873
|
+
.map((occurrence) => occurrence.path);
|
|
1874
|
+
const policyPaths = [
|
|
1875
|
+
...vocabularyPolicyPaths,
|
|
1876
|
+
...params.run.policyOccurrencePaths,
|
|
1877
|
+
];
|
|
1878
|
+
const policyEvidenceMissing =
|
|
1879
|
+
params.plan.scope?.policyClassified?.some(
|
|
1880
|
+
(policy) =>
|
|
1881
|
+
policy.expectMatches === true &&
|
|
1882
|
+
!policyPaths.some((path) => matchesAnyPathGlob(path, policy.paths))
|
|
1883
|
+
) ?? false;
|
|
1884
|
+
const evidenceReasons = [
|
|
1885
|
+
...(policyEvidenceMissing
|
|
1886
|
+
? ['expected-policy-classified-evidence-missing']
|
|
1887
|
+
: []),
|
|
1888
|
+
...(missing.length === 0 ? [] : ['expected-teaching-surfaces-missing']),
|
|
1889
|
+
];
|
|
1890
|
+
const reasons = uniqueSorted([
|
|
1891
|
+
...params.report.run.report.gate.reasons.filter(
|
|
1892
|
+
(reason) =>
|
|
1893
|
+
reason !== 'expected-policy-classified-evidence-missing' &&
|
|
1894
|
+
reason !== 'expected-teaching-surfaces-missing'
|
|
1895
|
+
),
|
|
1896
|
+
...evidenceReasons,
|
|
1897
|
+
]);
|
|
1898
|
+
const filePolicyCount = params.run.policyOccurrencePaths.length;
|
|
1899
|
+
const fileInScopeCount = params.run.occurrencePaths.length - filePolicyCount;
|
|
1900
|
+
const projectedFileInScopeCount =
|
|
1901
|
+
params.run.report.rewritten + params.run.report.review;
|
|
1902
|
+
return {
|
|
1903
|
+
...params.report,
|
|
1904
|
+
run: {
|
|
1905
|
+
...params.report.run,
|
|
1906
|
+
report: {
|
|
1907
|
+
...params.report.run.report,
|
|
1908
|
+
fileRenames: params.run.evidence,
|
|
1909
|
+
gate: {
|
|
1910
|
+
...params.report.run.report.gate,
|
|
1911
|
+
reasons,
|
|
1912
|
+
status: reasons.length === 0 ? 'green' : 'open',
|
|
1913
|
+
},
|
|
1914
|
+
scopeTiers: {
|
|
1915
|
+
'in-scope':
|
|
1916
|
+
params.report.run.report.scopeTiers['in-scope'] -
|
|
1917
|
+
projectedFileInScopeCount +
|
|
1918
|
+
fileInScopeCount,
|
|
1919
|
+
'policy-classified':
|
|
1920
|
+
params.report.run.report.scopeTiers['policy-classified'] +
|
|
1921
|
+
filePolicyCount,
|
|
1922
|
+
},
|
|
1923
|
+
teachingSurfaces: { expected, missing, touched },
|
|
1924
|
+
},
|
|
1925
|
+
},
|
|
1926
|
+
};
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
const combineVocabularyReports = (params: {
|
|
1930
|
+
readonly fileRenameRun: FileRenameRegradeRun | null;
|
|
1931
|
+
readonly plan: VocabularyRegradePlan;
|
|
1932
|
+
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
1933
|
+
readonly proseReport: RegradeReport | null;
|
|
1934
|
+
readonly symbolReport: RegradeReport | null;
|
|
1935
|
+
}): TrailsResult<RegradeReport, Error> => {
|
|
1936
|
+
const baseReport =
|
|
1937
|
+
params.proseReport ?? params.symbolReport ?? params.fileRenameRun?.report;
|
|
1938
|
+
if (baseReport === undefined) {
|
|
1939
|
+
return regradeNoEngineForScope();
|
|
1940
|
+
}
|
|
1941
|
+
let combined = reportWithVocabularyTransitionRun({
|
|
1942
|
+
plan: params.plan,
|
|
1943
|
+
preserveInventory: params.preserveInventory,
|
|
1944
|
+
report: baseReport,
|
|
1945
|
+
});
|
|
1946
|
+
if (params.proseReport !== null && params.symbolReport !== null) {
|
|
1947
|
+
combined = mergeRegradeReports(combined, params.symbolReport);
|
|
1948
|
+
}
|
|
1949
|
+
if (
|
|
1950
|
+
params.fileRenameRun !== null &&
|
|
1951
|
+
baseReport !== params.fileRenameRun.report
|
|
1952
|
+
) {
|
|
1953
|
+
combined = mergeRegradeReports(combined, params.fileRenameRun.report);
|
|
1954
|
+
}
|
|
1955
|
+
if (params.fileRenameRun !== null && combined.run !== undefined) {
|
|
1956
|
+
combined = withFileRenameEvidence({
|
|
1957
|
+
plan: params.plan,
|
|
1958
|
+
report: { ...combined, run: combined.run },
|
|
1959
|
+
run: params.fileRenameRun,
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
if (combined.apply !== undefined && params.fileRenameRun !== null) {
|
|
1963
|
+
const movedPaths = new Map(
|
|
1964
|
+
(params.plan.fileRenames ?? []).map((rename) => [
|
|
1965
|
+
posix.normalize(rename.from.replaceAll('\\', '/')),
|
|
1966
|
+
posix.normalize(rename.to.replaceAll('\\', '/')),
|
|
1967
|
+
])
|
|
1968
|
+
);
|
|
1969
|
+
const changedPaths = new Set(params.fileRenameRun.changedPaths);
|
|
1970
|
+
for (const entry of combined.entries) {
|
|
1971
|
+
if (entry.outcome !== 'rewrite') {
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
const normalizedEntryPath = posix.normalize(entry.path);
|
|
1975
|
+
const movedPath = movedPaths.get(normalizedEntryPath);
|
|
1976
|
+
changedPaths.add(
|
|
1977
|
+
movedPath !== undefined && changedPaths.has(movedPath)
|
|
1978
|
+
? movedPath
|
|
1979
|
+
: normalizedEntryPath
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
const filesChanged = changedPaths.size;
|
|
1983
|
+
combined = {
|
|
1984
|
+
...combined,
|
|
1985
|
+
apply: { ...combined.apply, filesChanged },
|
|
1986
|
+
...(combined.run === undefined
|
|
1987
|
+
? {}
|
|
1988
|
+
: {
|
|
1989
|
+
run: {
|
|
1990
|
+
...combined.run,
|
|
1991
|
+
report: { ...combined.run.report, filesChanged },
|
|
1992
|
+
},
|
|
1993
|
+
}),
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
return validateRegradeReport(combined);
|
|
1997
|
+
};
|
|
1998
|
+
|
|
1999
|
+
const runPlanFileRenames = (
|
|
2000
|
+
plan: VocabularyRegradePlan,
|
|
2001
|
+
params: {
|
|
2002
|
+
readonly apply: boolean;
|
|
2003
|
+
readonly includeEntries: RegradeInput['includeEntries'];
|
|
2004
|
+
readonly rootDir: string;
|
|
2005
|
+
}
|
|
2006
|
+
): TrailsResult<FileRenameRegradeRun | null, Error> =>
|
|
2007
|
+
plan.fileRenames === undefined || plan.fileRenames.length === 0
|
|
2008
|
+
? Result.ok(null)
|
|
2009
|
+
: runFileRenameRegrade({
|
|
2010
|
+
apply: params.apply,
|
|
2011
|
+
excludeGeneratedArtifacts: true,
|
|
2012
|
+
includeEntries: params.includeEntries,
|
|
2013
|
+
renames: plan.fileRenames,
|
|
2014
|
+
root: params.rootDir,
|
|
2015
|
+
...(plan.scope === undefined ? {} : { scope: plan.scope }),
|
|
2016
|
+
vocabularyPlan: plan,
|
|
2017
|
+
});
|
|
2018
|
+
|
|
1532
2019
|
const runResolvedVocabularyPlan = (params: {
|
|
1533
2020
|
readonly apply: boolean;
|
|
1534
2021
|
readonly includeEntries: RegradeInput['includeEntries'];
|
|
@@ -1536,70 +2023,117 @@ const runResolvedVocabularyPlan = (params: {
|
|
|
1536
2023
|
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
1537
2024
|
readonly rootDir: string;
|
|
1538
2025
|
}): TrailsResult<RegradeReport, Error> => {
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
2026
|
+
const fileRenamePreflight = runPlanFileRenames(params.plan, {
|
|
2027
|
+
apply: false,
|
|
2028
|
+
includeEntries: params.includeEntries,
|
|
2029
|
+
rootDir: params.rootDir,
|
|
2030
|
+
});
|
|
2031
|
+
if (fileRenamePreflight.isErr()) {
|
|
2032
|
+
return fileRenamePreflight;
|
|
2033
|
+
}
|
|
2034
|
+
const evidencePlan = vocabularyEvidencePlan(params.plan);
|
|
2035
|
+
const runProse = (
|
|
2036
|
+
apply: boolean
|
|
2037
|
+
): TrailsResult<RegradeReport | null, Error> =>
|
|
2038
|
+
evidencePlan === null
|
|
1542
2039
|
? Result.ok(null)
|
|
1543
2040
|
: runVocabularyRegrade({
|
|
1544
|
-
apply
|
|
2041
|
+
apply,
|
|
1545
2042
|
includeEntries: params.includeEntries,
|
|
1546
|
-
plan:
|
|
2043
|
+
plan: evidencePlan,
|
|
1547
2044
|
...(params.preserveInventory.length === 0
|
|
1548
2045
|
? {}
|
|
1549
2046
|
: { preserveInventory: params.preserveInventory }),
|
|
1550
2047
|
root: params.rootDir,
|
|
2048
|
+
sourceFilter: (path) =>
|
|
2049
|
+
vocabularyEvidenceSource(path, evidencePlan.scope),
|
|
1551
2050
|
});
|
|
1552
|
-
|
|
1553
|
-
|
|
2051
|
+
const runSymbols = (
|
|
2052
|
+
apply: boolean
|
|
2053
|
+
): TrailsResult<RegradeReport | null, Error> =>
|
|
2054
|
+
runGovernedSymbolRegrade({
|
|
2055
|
+
apply,
|
|
2056
|
+
includeEntries: params.includeEntries,
|
|
2057
|
+
plan: params.plan,
|
|
2058
|
+
preserveInventory: params.preserveInventory,
|
|
2059
|
+
rootDir: params.rootDir,
|
|
2060
|
+
});
|
|
2061
|
+
const prosePreview = runProse(false);
|
|
2062
|
+
if (prosePreview.isErr()) {
|
|
2063
|
+
return prosePreview;
|
|
2064
|
+
}
|
|
2065
|
+
const prosePreviewReport = withoutVocabularySourceFilterSkips(
|
|
2066
|
+
prosePreview.value
|
|
2067
|
+
);
|
|
2068
|
+
const symbolPreview = runSymbols(false);
|
|
2069
|
+
if (symbolPreview.isErr()) {
|
|
2070
|
+
return symbolPreview;
|
|
2071
|
+
}
|
|
2072
|
+
if (!params.apply) {
|
|
2073
|
+
if (
|
|
2074
|
+
prosePreviewReport?.scanned === 0 &&
|
|
2075
|
+
symbolPreview.value === null &&
|
|
2076
|
+
fileRenamePreflight.value === null &&
|
|
2077
|
+
!vocabularyProseEngineApplies(params.plan.scope)
|
|
2078
|
+
) {
|
|
2079
|
+
return regradeNoEngineForScope();
|
|
2080
|
+
}
|
|
2081
|
+
return combineVocabularyReports({
|
|
2082
|
+
fileRenameRun: fileRenamePreflight.value,
|
|
2083
|
+
plan: params.plan,
|
|
2084
|
+
preserveInventory: params.preserveInventory,
|
|
2085
|
+
proseReport: prosePreviewReport,
|
|
2086
|
+
symbolReport: symbolPreview.value,
|
|
2087
|
+
});
|
|
1554
2088
|
}
|
|
1555
2089
|
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
includeEntries: params.includeEntries,
|
|
1559
|
-
plan: params.plan,
|
|
1560
|
-
preserveInventory: params.preserveInventory,
|
|
2090
|
+
const snapshots = snapshotRegradeSources({
|
|
2091
|
+
reports: [prosePreviewReport, symbolPreview.value],
|
|
1561
2092
|
rootDir: params.rootDir,
|
|
1562
2093
|
});
|
|
2094
|
+
if (snapshots.isErr()) {
|
|
2095
|
+
return snapshots;
|
|
2096
|
+
}
|
|
2097
|
+
const reportResult = runProse(true);
|
|
2098
|
+
if (reportResult.isErr()) {
|
|
2099
|
+
return regradeApplyErrorAfterRollback(reportResult.error, snapshots.value);
|
|
2100
|
+
}
|
|
2101
|
+
const symbolReportResult = runSymbols(true);
|
|
1563
2102
|
if (symbolReportResult.isErr()) {
|
|
1564
|
-
return
|
|
2103
|
+
return regradeApplyErrorAfterRollback(
|
|
2104
|
+
symbolReportResult.error,
|
|
2105
|
+
snapshots.value
|
|
2106
|
+
);
|
|
1565
2107
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
plan: params.plan,
|
|
1576
|
-
preserveInventory: params.preserveInventory,
|
|
1577
|
-
report: symbolReport,
|
|
1578
|
-
})
|
|
2108
|
+
const fileRenameResult = runPlanFileRenames(params.plan, {
|
|
2109
|
+
apply: true,
|
|
2110
|
+
includeEntries: params.includeEntries,
|
|
2111
|
+
rootDir: params.rootDir,
|
|
2112
|
+
});
|
|
2113
|
+
if (fileRenameResult.isErr()) {
|
|
2114
|
+
return regradeApplyErrorAfterRollback(
|
|
2115
|
+
fileRenameResult.error,
|
|
2116
|
+
snapshots.value
|
|
1579
2117
|
);
|
|
1580
2118
|
}
|
|
1581
2119
|
|
|
1582
|
-
const
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
return
|
|
1591
|
-
}
|
|
1592
|
-
if (symbolReport === null) {
|
|
1593
|
-
return Result.ok(validated.value);
|
|
2120
|
+
const report = withoutVocabularySourceFilterSkips(reportResult.value);
|
|
2121
|
+
const symbolReport = symbolReportResult.value;
|
|
2122
|
+
if (
|
|
2123
|
+
report?.scanned === 0 &&
|
|
2124
|
+
symbolReport === null &&
|
|
2125
|
+
fileRenameResult.value === null &&
|
|
2126
|
+
!vocabularyProseEngineApplies(params.plan.scope)
|
|
2127
|
+
) {
|
|
2128
|
+
return regradeNoEngineForScope();
|
|
1594
2129
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
);
|
|
2130
|
+
return combineVocabularyReports({
|
|
2131
|
+
fileRenameRun: fileRenameResult.value,
|
|
2132
|
+
plan: params.plan,
|
|
2133
|
+
preserveInventory: params.preserveInventory,
|
|
2134
|
+
proseReport: report,
|
|
2135
|
+
symbolReport,
|
|
2136
|
+
});
|
|
1603
2137
|
};
|
|
1604
2138
|
|
|
1605
2139
|
interface ClassRegradeCoreParams {
|
|
@@ -1697,12 +2231,18 @@ const runPlanArtifactDryRun = async (params: {
|
|
|
1697
2231
|
rootDir: params.rootDir,
|
|
1698
2232
|
});
|
|
1699
2233
|
}
|
|
1700
|
-
const
|
|
2234
|
+
const preserveResult = await deriveLiveApiPreserveInventory(
|
|
2235
|
+
planBody,
|
|
2236
|
+
params.rootDir
|
|
2237
|
+
);
|
|
2238
|
+
if (preserveResult.isErr()) {
|
|
2239
|
+
return preserveResult;
|
|
2240
|
+
}
|
|
1701
2241
|
return runResolvedVocabularyPlan({
|
|
1702
2242
|
apply: false,
|
|
1703
2243
|
includeEntries: params.includeEntries,
|
|
1704
2244
|
plan: planBody,
|
|
1705
|
-
preserveInventory,
|
|
2245
|
+
preserveInventory: preserveResult.value,
|
|
1706
2246
|
rootDir: params.rootDir,
|
|
1707
2247
|
});
|
|
1708
2248
|
};
|
|
@@ -1816,7 +2356,8 @@ const runVocabularyCommandRegrade = async (
|
|
|
1816
2356
|
|
|
1817
2357
|
const planResult = buildVocabularyPlan(
|
|
1818
2358
|
input,
|
|
1819
|
-
vocabularyScopeFromConfig(configScope)
|
|
2359
|
+
vocabularyScopeFromConfig(configScope),
|
|
2360
|
+
rootDir
|
|
1820
2361
|
);
|
|
1821
2362
|
if (planResult.isErr()) {
|
|
1822
2363
|
return planResult;
|
|
@@ -1825,14 +2366,18 @@ const runVocabularyCommandRegrade = async (
|
|
|
1825
2366
|
return regradeRootNotFound(rootDir);
|
|
1826
2367
|
}
|
|
1827
2368
|
|
|
1828
|
-
const
|
|
1829
|
-
planResult.value
|
|
2369
|
+
const preserveResult = await deriveLiveApiPreserveInventory(
|
|
2370
|
+
planResult.value,
|
|
2371
|
+
rootDir
|
|
1830
2372
|
);
|
|
2373
|
+
if (preserveResult.isErr()) {
|
|
2374
|
+
return preserveResult;
|
|
2375
|
+
}
|
|
1831
2376
|
const report = runResolvedVocabularyPlan({
|
|
1832
2377
|
apply: input.apply,
|
|
1833
2378
|
includeEntries: input.includeEntries,
|
|
1834
2379
|
plan: planResult.value,
|
|
1835
|
-
preserveInventory,
|
|
2380
|
+
preserveInventory: preserveResult.value,
|
|
1836
2381
|
rootDir,
|
|
1837
2382
|
});
|
|
1838
2383
|
if (report.isErr() || !input.writeRecord) {
|
|
@@ -1935,6 +2480,7 @@ const expansionForReport = (
|
|
|
1935
2480
|
},
|
|
1936
2481
|
],
|
|
1937
2482
|
kind: 'form',
|
|
2483
|
+
provenance: 'derived',
|
|
1938
2484
|
status: 'pending',
|
|
1939
2485
|
suggestedClassification: occurrence.disposition,
|
|
1940
2486
|
value: occurrence.form,
|
|
@@ -1966,6 +2512,7 @@ const expansionForReport = (
|
|
|
1966
2512
|
},
|
|
1967
2513
|
],
|
|
1968
2514
|
kind: 'form',
|
|
2515
|
+
provenance: 'derived',
|
|
1969
2516
|
status: 'pending',
|
|
1970
2517
|
suggestedClassification: entry.reason ?? detail.reason,
|
|
1971
2518
|
value: detail.symbol,
|
|
@@ -2119,6 +2666,11 @@ const validateClassPlanInput = (
|
|
|
2119
2666
|
'`type: vocabulary` cannot be combined with `classIds`.'
|
|
2120
2667
|
);
|
|
2121
2668
|
}
|
|
2669
|
+
if (input.fileRenames !== undefined && input.fileRenames.length > 0) {
|
|
2670
|
+
return new ValidationError(
|
|
2671
|
+
'`fileRenames` is not supported for class-mode plans; governed file moves require a vocabulary plan.'
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2122
2674
|
if (input.expand) {
|
|
2123
2675
|
return new ValidationError(
|
|
2124
2676
|
'`expand` stages vocabulary review candidates and is not supported for class-mode plans.'
|
|
@@ -2339,6 +2891,84 @@ const readCurrentVocabularyPlanArtifact = (
|
|
|
2339
2891
|
return Result.ok({ ...candidate, plan: candidate.plan });
|
|
2340
2892
|
};
|
|
2341
2893
|
|
|
2894
|
+
const finishVocabularyPlanArtifact = (params: {
|
|
2895
|
+
readonly current?: VocabularyRegradePlanArtifact | undefined;
|
|
2896
|
+
readonly currentPath: string;
|
|
2897
|
+
readonly input: RegradePlanInput;
|
|
2898
|
+
readonly plan: VocabularyRegradePlan;
|
|
2899
|
+
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
2900
|
+
readonly rootDir: string;
|
|
2901
|
+
readonly shouldDryRun: boolean;
|
|
2902
|
+
}): TrailsResult<RegradePlanArtifact, Error> => {
|
|
2903
|
+
const initialReport = runResolvedVocabularyPlan({
|
|
2904
|
+
apply: false,
|
|
2905
|
+
includeEntries: params.input.includeEntries,
|
|
2906
|
+
plan: params.plan,
|
|
2907
|
+
preserveInventory: params.preserveInventory,
|
|
2908
|
+
rootDir: params.rootDir,
|
|
2909
|
+
});
|
|
2910
|
+
if (initialReport.isErr()) {
|
|
2911
|
+
return initialReport;
|
|
2912
|
+
}
|
|
2913
|
+
const initialProvenance = regradePlanProvenanceForInput(
|
|
2914
|
+
params.input,
|
|
2915
|
+
params.plan
|
|
2916
|
+
);
|
|
2917
|
+
const scopeIsAuthored =
|
|
2918
|
+
initialProvenance.fields['scope'] === 'authored' ||
|
|
2919
|
+
params.current?.provenance.fields['scope'] === 'authored';
|
|
2920
|
+
const plan = scopeIsAuthored
|
|
2921
|
+
? params.plan
|
|
2922
|
+
: withDerivedTeachingSurfaceInventory({
|
|
2923
|
+
plan: params.plan,
|
|
2924
|
+
report: initialReport.value,
|
|
2925
|
+
});
|
|
2926
|
+
const report =
|
|
2927
|
+
plan === params.plan
|
|
2928
|
+
? initialReport
|
|
2929
|
+
: runResolvedVocabularyPlan({
|
|
2930
|
+
apply: false,
|
|
2931
|
+
includeEntries: params.input.includeEntries,
|
|
2932
|
+
plan,
|
|
2933
|
+
preserveInventory: params.preserveInventory,
|
|
2934
|
+
rootDir: params.rootDir,
|
|
2935
|
+
});
|
|
2936
|
+
if (report.isErr()) {
|
|
2937
|
+
return report;
|
|
2938
|
+
}
|
|
2939
|
+
const expansion = mergeRegradePlanExpansion(
|
|
2940
|
+
params.current?.expansion,
|
|
2941
|
+
params.input.expand ? expansionForReport(report.value) : undefined,
|
|
2942
|
+
plan
|
|
2943
|
+
);
|
|
2944
|
+
const transitionId = priorTransitionId(params.currentPath, 'vocabulary');
|
|
2945
|
+
const derivedProvenance = regradePlanProvenanceForInput(params.input, plan);
|
|
2946
|
+
const provenance =
|
|
2947
|
+
params.current === undefined
|
|
2948
|
+
? derivedProvenance
|
|
2949
|
+
: preserveAuthoredPlanProvenance(params.current, derivedProvenance);
|
|
2950
|
+
const artifact = buildRegradePlanArtifact({
|
|
2951
|
+
derivation: deriveRegradePlanDerivation({
|
|
2952
|
+
plan,
|
|
2953
|
+
preserveInventory: params.preserveInventory,
|
|
2954
|
+
provenance,
|
|
2955
|
+
report: report.value,
|
|
2956
|
+
rootDir: params.rootDir,
|
|
2957
|
+
}),
|
|
2958
|
+
...(expansion === undefined ? {} : { expansion }),
|
|
2959
|
+
input: params.input,
|
|
2960
|
+
plan,
|
|
2961
|
+
report: report.value,
|
|
2962
|
+
rootDir: params.rootDir,
|
|
2963
|
+
...(transitionId === undefined ? {} : { transitionId }),
|
|
2964
|
+
});
|
|
2965
|
+
const mergedArtifact =
|
|
2966
|
+
params.current === undefined ? artifact : { ...artifact, provenance };
|
|
2967
|
+
return params.shouldDryRun
|
|
2968
|
+
? validateRegradePlanArtifact(mergedArtifact)
|
|
2969
|
+
: writeRegradePlanArtifact(params.rootDir, mergedArtifact);
|
|
2970
|
+
};
|
|
2971
|
+
|
|
2342
2972
|
const runPlanRegrade = async (
|
|
2343
2973
|
input: RegradePlanInput,
|
|
2344
2974
|
rootDir: string,
|
|
@@ -2368,7 +2998,8 @@ const runPlanRegrade = async (
|
|
|
2368
2998
|
};
|
|
2369
2999
|
const planResult = buildVocabularyPlan(
|
|
2370
3000
|
planInput,
|
|
2371
|
-
vocabularyScopeFromConfig(configScope)
|
|
3001
|
+
vocabularyScopeFromConfig(configScope),
|
|
3002
|
+
rootDir
|
|
2372
3003
|
);
|
|
2373
3004
|
if (planResult.isErr()) {
|
|
2374
3005
|
return planResult;
|
|
@@ -2386,45 +3017,19 @@ const runPlanRegrade = async (
|
|
|
2386
3017
|
current === undefined
|
|
2387
3018
|
? planResult.value
|
|
2388
3019
|
: mergeAuthoredPlanFields(current, planResult.value);
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
includeEntries: input.includeEntries,
|
|
2393
|
-
plan,
|
|
2394
|
-
preserveInventory,
|
|
2395
|
-
rootDir,
|
|
2396
|
-
});
|
|
2397
|
-
if (report.isErr()) {
|
|
2398
|
-
return report;
|
|
3020
|
+
const preserveResult = await deriveLiveApiPreserveInventory(plan, rootDir);
|
|
3021
|
+
if (preserveResult.isErr()) {
|
|
3022
|
+
return preserveResult;
|
|
2399
3023
|
}
|
|
2400
|
-
|
|
2401
|
-
current
|
|
2402
|
-
|
|
2403
|
-
plan
|
|
2404
|
-
);
|
|
2405
|
-
const transitionId = priorTransitionId(currentPath, 'vocabulary');
|
|
2406
|
-
const artifact = buildRegradePlanArtifact({
|
|
2407
|
-
...(expansion === undefined ? {} : { expansion }),
|
|
3024
|
+
return finishVocabularyPlanArtifact({
|
|
3025
|
+
current,
|
|
3026
|
+
currentPath,
|
|
2408
3027
|
input,
|
|
2409
3028
|
plan,
|
|
2410
|
-
|
|
3029
|
+
preserveInventory: preserveResult.value,
|
|
2411
3030
|
rootDir,
|
|
2412
|
-
|
|
3031
|
+
shouldDryRun,
|
|
2413
3032
|
});
|
|
2414
|
-
const mergedArtifact =
|
|
2415
|
-
current === undefined
|
|
2416
|
-
? artifact
|
|
2417
|
-
: {
|
|
2418
|
-
...artifact,
|
|
2419
|
-
provenance: preserveAuthoredPlanProvenance(
|
|
2420
|
-
current,
|
|
2421
|
-
artifact.provenance
|
|
2422
|
-
),
|
|
2423
|
-
};
|
|
2424
|
-
if (shouldDryRun) {
|
|
2425
|
-
return validateRegradePlanArtifact(mergedArtifact);
|
|
2426
|
-
}
|
|
2427
|
-
return writeRegradePlanArtifact(rootDir, mergedArtifact);
|
|
2428
3033
|
};
|
|
2429
3034
|
|
|
2430
3035
|
const loadPlanForInput = async (
|
|
@@ -2517,7 +3122,11 @@ const runCheckRegradePlan = async (
|
|
|
2517
3122
|
if (report.isErr()) {
|
|
2518
3123
|
return report;
|
|
2519
3124
|
}
|
|
2520
|
-
const status = planStatusForReport(
|
|
3125
|
+
const status = planStatusForReport(
|
|
3126
|
+
loaded.value.artifact,
|
|
3127
|
+
report.value,
|
|
3128
|
+
rootDir
|
|
3129
|
+
);
|
|
2521
3130
|
const checked = reportWithPlanSummary(
|
|
2522
3131
|
report.value,
|
|
2523
3132
|
loaded.value.artifact,
|
|
@@ -2567,7 +3176,7 @@ const runPreviewRegradePlan = async (
|
|
|
2567
3176
|
reportWithPlanSummary(
|
|
2568
3177
|
report.value,
|
|
2569
3178
|
loaded.value.artifact,
|
|
2570
|
-
planStatusForReport(loaded.value.artifact, report.value)
|
|
3179
|
+
planStatusForReport(loaded.value.artifact, report.value, rootDir)
|
|
2571
3180
|
)
|
|
2572
3181
|
);
|
|
2573
3182
|
};
|
|
@@ -2660,6 +3269,12 @@ const runApplyRegradePlan = async (
|
|
|
2660
3269
|
if (loaded.isErr()) {
|
|
2661
3270
|
return loaded;
|
|
2662
3271
|
}
|
|
3272
|
+
const governedPlanValidation = validateGovernedRegradePlan(
|
|
3273
|
+
loaded.value.artifact
|
|
3274
|
+
);
|
|
3275
|
+
if (governedPlanValidation.isErr()) {
|
|
3276
|
+
return governedPlanValidation;
|
|
3277
|
+
}
|
|
2663
3278
|
const dryRunReport = await runPlanArtifactDryRun({
|
|
2664
3279
|
artifact: loaded.value.artifact,
|
|
2665
3280
|
includeEntries: input.includeEntries,
|
|
@@ -2668,7 +3283,11 @@ const runApplyRegradePlan = async (
|
|
|
2668
3283
|
if (dryRunReport.isErr()) {
|
|
2669
3284
|
return dryRunReport;
|
|
2670
3285
|
}
|
|
2671
|
-
const status = planStatusForReport(
|
|
3286
|
+
const status = planStatusForReport(
|
|
3287
|
+
loaded.value.artifact,
|
|
3288
|
+
dryRunReport.value,
|
|
3289
|
+
rootDir
|
|
3290
|
+
);
|
|
2672
3291
|
if (status === 'stale') {
|
|
2673
3292
|
return Result.err(
|
|
2674
3293
|
new ValidationError(
|
|
@@ -2695,12 +3314,18 @@ const runApplyRegradePlan = async (
|
|
|
2695
3314
|
rootDir,
|
|
2696
3315
|
});
|
|
2697
3316
|
} else {
|
|
2698
|
-
const
|
|
3317
|
+
const preserveResult = await deriveLiveApiPreserveInventory(
|
|
3318
|
+
planBody,
|
|
3319
|
+
rootDir
|
|
3320
|
+
);
|
|
3321
|
+
if (preserveResult.isErr()) {
|
|
3322
|
+
return preserveResult;
|
|
3323
|
+
}
|
|
2699
3324
|
applied = runResolvedVocabularyPlan({
|
|
2700
3325
|
apply: true,
|
|
2701
3326
|
includeEntries: input.includeEntries,
|
|
2702
3327
|
plan: planBody,
|
|
2703
|
-
preserveInventory,
|
|
3328
|
+
preserveInventory: preserveResult.value,
|
|
2704
3329
|
rootDir,
|
|
2705
3330
|
});
|
|
2706
3331
|
}
|
|
@@ -2800,6 +3425,17 @@ const runAdjustRegrade = async (
|
|
|
2800
3425
|
}
|
|
2801
3426
|
const artifact: RegradePlanArtifact = {
|
|
2802
3427
|
...draft,
|
|
3428
|
+
...(draft.plan.kind === 'class'
|
|
3429
|
+
? {}
|
|
3430
|
+
: {
|
|
3431
|
+
derivation: deriveRegradePlanDerivation({
|
|
3432
|
+
plan: draft.plan,
|
|
3433
|
+
preserveInventory: report.value.run?.preserveInventory ?? [],
|
|
3434
|
+
provenance: draft.provenance,
|
|
3435
|
+
report: report.value,
|
|
3436
|
+
rootDir,
|
|
3437
|
+
}),
|
|
3438
|
+
}),
|
|
2803
3439
|
sourceHash: regradeSourceHash(report.value),
|
|
2804
3440
|
};
|
|
2805
3441
|
if (shouldDryRun) {
|
|
@@ -2834,7 +3470,7 @@ const listRegradePlans = async (
|
|
|
2834
3470
|
kind: body.kind,
|
|
2835
3471
|
path: artifact.value.path,
|
|
2836
3472
|
schemaVersion: artifact.value.schemaVersion,
|
|
2837
|
-
status: planStatusForReport(artifact.value, report.value),
|
|
3473
|
+
status: planStatusForReport(artifact.value, report.value, rootDir),
|
|
2838
3474
|
});
|
|
2839
3475
|
}
|
|
2840
3476
|
return Result.ok({ plans });
|
|
@@ -2969,6 +3605,47 @@ export const listRegradesTrail = trail('list.regrades', {
|
|
|
2969
3605
|
permit: 'public',
|
|
2970
3606
|
});
|
|
2971
3607
|
|
|
3608
|
+
export const auditRegradeTrail = trail('audit.regrade', {
|
|
3609
|
+
cli: { path: ['regrade', 'audit'] },
|
|
3610
|
+
description:
|
|
3611
|
+
'Audit applied Regrade vocabulary transitions against current source',
|
|
3612
|
+
implementation: async (input, ctx) => {
|
|
3613
|
+
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
3614
|
+
if (rootDirResult.isErr()) {
|
|
3615
|
+
return rootDirResult;
|
|
3616
|
+
}
|
|
3617
|
+
const result = await auditRegradeHistory(input, rootDirResult.value);
|
|
3618
|
+
if (result.isErr()) {
|
|
3619
|
+
return result;
|
|
3620
|
+
}
|
|
3621
|
+
const output = validateOutput(regradeAuditOutputSchema, result.value);
|
|
3622
|
+
if (output.isErr()) {
|
|
3623
|
+
return Result.err(output.error);
|
|
3624
|
+
}
|
|
3625
|
+
if (input.failOnOpen && output.value.gate.status === 'open') {
|
|
3626
|
+
return Result.err(
|
|
3627
|
+
new ValidationError('Regrade audit found current-tree residue.', {
|
|
3628
|
+
context: {
|
|
3629
|
+
gate: output.value.gate,
|
|
3630
|
+
transitions: output.value.transitions
|
|
3631
|
+
.filter((transition) => transition.report.status === 'open')
|
|
3632
|
+
.map((transition) => ({
|
|
3633
|
+
open: transition.report.open,
|
|
3634
|
+
source: transition.source,
|
|
3635
|
+
transitionId: transition.transitionId,
|
|
3636
|
+
})),
|
|
3637
|
+
},
|
|
3638
|
+
})
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
return Result.ok(output.value);
|
|
3642
|
+
},
|
|
3643
|
+
input: regradeAuditInputSchema,
|
|
3644
|
+
intent: 'read',
|
|
3645
|
+
output: regradeAuditOutputSchema,
|
|
3646
|
+
permit: 'public',
|
|
3647
|
+
});
|
|
3648
|
+
|
|
2972
3649
|
export const checkRegradeTrail = trail('check.regrade', {
|
|
2973
3650
|
cli: { path: ['regrade', 'check'] },
|
|
2974
3651
|
description: 'Check a saved Regrade plan gate without writing source',
|