@ontrails/trails 1.0.0-beta.45 → 1.0.0-beta.46
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 +21 -0
- package/README.md +1 -1
- package/package.json +16 -16
- package/src/cli.ts +13 -0
- package/src/regrade/audit.ts +118 -33
- package/src/regrade/history.ts +423 -425
- package/src/regrade/lifecycle.ts +76 -0
- package/src/regrade/plan-artifact.ts +0 -3
- package/src/regrade/prepared-run.ts +259 -0
- package/src/regrade/receipt-history.ts +445 -0
- package/src/regrade/source-transaction.ts +121 -11
- package/src/run-regrade-progress.ts +47 -0
- package/src/trails/create-scaffold.ts +1 -0
- package/src/trails/regrade.ts +965 -236
package/src/trails/regrade.ts
CHANGED
|
@@ -14,14 +14,19 @@ import {
|
|
|
14
14
|
} from '@ontrails/core';
|
|
15
15
|
import type { PathScope, Result as TrailsResult } from '@ontrails/core';
|
|
16
16
|
import {
|
|
17
|
+
applyPreparedRegradeRun,
|
|
18
|
+
applyPreparedVocabularyRegradeRun,
|
|
17
19
|
createGovernedAstIdentifierRenameClasses,
|
|
18
20
|
loadWardenRegradeClasses,
|
|
21
|
+
prepareRegradeRun,
|
|
22
|
+
prepareVocabularyRegradeRun,
|
|
19
23
|
readVocabularyTransitionRecord,
|
|
20
24
|
regradeReportOutput,
|
|
21
25
|
runFileRenameRegrade,
|
|
22
26
|
runRegrade,
|
|
23
27
|
runVocabularyRegrade,
|
|
24
28
|
transitionRecordReportWithSummary,
|
|
29
|
+
validatePreparedRegradeRun,
|
|
25
30
|
vocabularyRegradeTransitionForInput,
|
|
26
31
|
vocabularyDispositionValues,
|
|
27
32
|
vocabularyRegradePlanSchema,
|
|
@@ -30,6 +35,9 @@ import {
|
|
|
30
35
|
} from '@ontrails/regrade';
|
|
31
36
|
import type {
|
|
32
37
|
FileRenameRegradeRun,
|
|
38
|
+
PreparedRegradeRun,
|
|
39
|
+
PreparedRegradeRunIdentity,
|
|
40
|
+
PreparedVocabularyRegradeRun,
|
|
33
41
|
RegradeApplySummary,
|
|
34
42
|
RegradeReport,
|
|
35
43
|
RegradeReportEntry,
|
|
@@ -46,7 +54,6 @@ import {
|
|
|
46
54
|
mkdirSync,
|
|
47
55
|
readdirSync,
|
|
48
56
|
readFileSync,
|
|
49
|
-
rmSync,
|
|
50
57
|
writeFileSync,
|
|
51
58
|
} from 'node:fs';
|
|
52
59
|
import type { Dirent } from 'node:fs';
|
|
@@ -60,8 +67,12 @@ import {
|
|
|
60
67
|
} from '../regrade/audit.js';
|
|
61
68
|
import { loadRegradeConfig } from '../regrade/config.js';
|
|
62
69
|
import {
|
|
63
|
-
|
|
70
|
+
RegradeLifecycleTracker,
|
|
71
|
+
regradeLifecycleSchema,
|
|
72
|
+
} from '../regrade/lifecycle.js';
|
|
73
|
+
import {
|
|
64
74
|
appendRegradeHistoryRun,
|
|
75
|
+
consumeActiveRegradePlanAfterHistoryWrite,
|
|
65
76
|
readRegradeHistoryArtifact,
|
|
66
77
|
regradeHistoryPathForPlan,
|
|
67
78
|
resolveRegradeHistoryPath,
|
|
@@ -70,7 +81,18 @@ import {
|
|
|
70
81
|
} from '../regrade/history.js';
|
|
71
82
|
import type { RegradeHistorySummary } from '../regrade/history.js';
|
|
72
83
|
import { deriveLiveApiPreserveInventory } from '../regrade/live-api-preserve.js';
|
|
84
|
+
import {
|
|
85
|
+
captureRegradeChangedFilesBefore,
|
|
86
|
+
completeRegradeChangedFiles,
|
|
87
|
+
resolveRegradeSourceRevision,
|
|
88
|
+
validateRegradeReceiptPlan,
|
|
89
|
+
} from '../regrade/receipt-history.js';
|
|
90
|
+
import type { RegradeChangedFileEvidence } from '../regrade/receipt-history.js';
|
|
73
91
|
import { deriveRegradePlanDerivation } from '../regrade/plan-derivation.js';
|
|
92
|
+
import {
|
|
93
|
+
preparedRegradeRunIdentity,
|
|
94
|
+
validatePreparedRegradePlanArtifact,
|
|
95
|
+
} from '../regrade/prepared-run.js';
|
|
74
96
|
import {
|
|
75
97
|
regradeApplyErrorAfterRollback,
|
|
76
98
|
snapshotRegradeSources,
|
|
@@ -239,7 +261,19 @@ const regradePlansOutputSchema = z.object({
|
|
|
239
261
|
plans: z.array(regradePlanSummarySchema),
|
|
240
262
|
});
|
|
241
263
|
|
|
242
|
-
const
|
|
264
|
+
const regradeLifecycleReportOutputSchema = regradeReportOutput.extend({
|
|
265
|
+
lifecycle: regradeLifecycleSchema.describe(
|
|
266
|
+
'Observed phases and wall-clock timings for this lifecycle command'
|
|
267
|
+
),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const regradePlanCommandOutputSchema = regradePlanArtifactSchema.extend({
|
|
271
|
+
lifecycle: regradeLifecycleSchema.describe(
|
|
272
|
+
'Observed phases and wall-clock timings for this lifecycle command'
|
|
273
|
+
),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const regradeCheckOutputSchema = regradeLifecycleReportOutputSchema.extend({
|
|
243
277
|
check: z
|
|
244
278
|
.object({
|
|
245
279
|
plan: z
|
|
@@ -875,6 +909,19 @@ const mergeTransitionRunReportWithSymbol = (
|
|
|
875
909
|
};
|
|
876
910
|
};
|
|
877
911
|
|
|
912
|
+
const withScannedPaths = (
|
|
913
|
+
report: RegradeReport,
|
|
914
|
+
paths: readonly string[]
|
|
915
|
+
): RegradeReport => {
|
|
916
|
+
Object.defineProperty(report, 'scannedPaths', {
|
|
917
|
+
configurable: false,
|
|
918
|
+
enumerable: false,
|
|
919
|
+
value: Object.freeze([...paths]),
|
|
920
|
+
writable: false,
|
|
921
|
+
});
|
|
922
|
+
return report;
|
|
923
|
+
};
|
|
924
|
+
|
|
878
925
|
const mergeRegradeReports = (
|
|
879
926
|
vocabularyReport: RegradeReport,
|
|
880
927
|
symbolReport: RegradeReport
|
|
@@ -908,7 +955,15 @@ const mergeRegradeReports = (
|
|
|
908
955
|
symbolReport.skipsByReason
|
|
909
956
|
);
|
|
910
957
|
const apply = mergeApplySummary(vocabularyReport.apply, symbolReport.apply);
|
|
911
|
-
const
|
|
958
|
+
const scannedPaths = uniqueSorted([
|
|
959
|
+
...(vocabularyReport.scannedPaths ?? []),
|
|
960
|
+
...(symbolReport.scannedPaths ?? []),
|
|
961
|
+
]);
|
|
962
|
+
const scanned =
|
|
963
|
+
vocabularyReport.scannedPaths === undefined ||
|
|
964
|
+
symbolReport.scannedPaths === undefined
|
|
965
|
+
? vocabularyReport.scanned + symbolReport.scanned
|
|
966
|
+
: scannedPaths.length;
|
|
912
967
|
const run =
|
|
913
968
|
vocabularyReport.run === undefined
|
|
914
969
|
? undefined
|
|
@@ -920,36 +975,39 @@ const mergeRegradeReports = (
|
|
|
920
975
|
),
|
|
921
976
|
};
|
|
922
977
|
|
|
923
|
-
return
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
978
|
+
return withScannedPaths(
|
|
979
|
+
{
|
|
980
|
+
...vocabularyReport,
|
|
981
|
+
...(apply === undefined ? {} : { apply }),
|
|
982
|
+
entries,
|
|
983
|
+
matched,
|
|
984
|
+
review,
|
|
985
|
+
rewritten,
|
|
986
|
+
scan: {
|
|
987
|
+
byDirectory: mergedDirectoryBuckets(matchedPaths, occurrencePaths),
|
|
988
|
+
byExtension: mergedExtensionBuckets(matchedPaths, occurrencePaths),
|
|
989
|
+
files: {
|
|
990
|
+
matched: new Set(matchedPaths).size,
|
|
991
|
+
scanned,
|
|
992
|
+
skipped: Math.max(vocabularyReport.skipped, symbolReport.skipped),
|
|
993
|
+
},
|
|
994
|
+
skippedByReason,
|
|
937
995
|
},
|
|
938
|
-
|
|
996
|
+
...(run === undefined ? {} : { run }),
|
|
997
|
+
scanned,
|
|
998
|
+
selectedClassIds: uniqueSorted([
|
|
999
|
+
...vocabularyReport.selectedClassIds,
|
|
1000
|
+
...symbolReport.selectedClassIds,
|
|
1001
|
+
]),
|
|
1002
|
+
skipped: Math.max(vocabularyReport.skipped, symbolReport.skipped),
|
|
1003
|
+
skipsByReason: skippedByReason,
|
|
1004
|
+
unknownClassIds: uniqueSorted([
|
|
1005
|
+
...vocabularyReport.unknownClassIds,
|
|
1006
|
+
...symbolReport.unknownClassIds,
|
|
1007
|
+
]),
|
|
939
1008
|
},
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
selectedClassIds: uniqueSorted([
|
|
943
|
-
...vocabularyReport.selectedClassIds,
|
|
944
|
-
...symbolReport.selectedClassIds,
|
|
945
|
-
]),
|
|
946
|
-
skipped: Math.max(vocabularyReport.skipped, symbolReport.skipped),
|
|
947
|
-
skipsByReason: skippedByReason,
|
|
948
|
-
unknownClassIds: uniqueSorted([
|
|
949
|
-
...vocabularyReport.unknownClassIds,
|
|
950
|
-
...symbolReport.unknownClassIds,
|
|
951
|
-
]),
|
|
952
|
-
};
|
|
1009
|
+
scannedPaths
|
|
1010
|
+
);
|
|
953
1011
|
};
|
|
954
1012
|
|
|
955
1013
|
const vocabularySymbolCollection = (
|
|
@@ -1046,41 +1104,62 @@ const withoutVocabularySourceFilterSkips = (
|
|
|
1046
1104
|
if (report === null || rejected === 0) {
|
|
1047
1105
|
return report;
|
|
1048
1106
|
}
|
|
1049
|
-
return
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
|
1107
|
+
return withScannedPaths(
|
|
1108
|
+
{
|
|
1109
|
+
...report,
|
|
1110
|
+
...(report.apply === undefined
|
|
1111
|
+
? {}
|
|
1112
|
+
: {
|
|
1113
|
+
apply: {
|
|
1114
|
+
...report.apply,
|
|
1115
|
+
skipped: Math.max(0, report.apply.skipped - rejected),
|
|
1116
|
+
},
|
|
1117
|
+
}),
|
|
1118
|
+
entries: report.entries.filter(
|
|
1119
|
+
(entry) => entry.reason !== 'not-selected-source'
|
|
1070
1120
|
),
|
|
1121
|
+
scan: {
|
|
1122
|
+
...report.scan,
|
|
1123
|
+
files: {
|
|
1124
|
+
...report.scan.files,
|
|
1125
|
+
skipped: Math.max(0, report.scan.files.skipped - rejected),
|
|
1126
|
+
},
|
|
1127
|
+
skippedByReason: withoutNotSelectedSourceCount(
|
|
1128
|
+
report.scan.skippedByReason
|
|
1129
|
+
),
|
|
1130
|
+
},
|
|
1131
|
+
skipped: Math.max(0, report.skipped - rejected),
|
|
1132
|
+
skipsByReason: withoutNotSelectedSourceCount(report.skipsByReason),
|
|
1071
1133
|
},
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
};
|
|
1134
|
+
report.scannedPaths ?? []
|
|
1135
|
+
);
|
|
1075
1136
|
};
|
|
1076
1137
|
|
|
1077
1138
|
const vocabularyEvidenceSource = (
|
|
1078
1139
|
path: string,
|
|
1079
|
-
scope: VocabularyRegradePlan['scope'] | undefined
|
|
1140
|
+
scope: VocabularyRegradePlan['scope'] | undefined,
|
|
1141
|
+
includeCodeComments = false
|
|
1080
1142
|
): boolean =>
|
|
1081
1143
|
vocabularyProseExtensions.includes(extname(path)) ||
|
|
1144
|
+
(includeCodeComments && symbolSourceExtensions.includes(extname(path))) ||
|
|
1082
1145
|
symbolOccurrenceIsPolicyClassified(scope, path);
|
|
1083
1146
|
|
|
1147
|
+
const classifiedCommentInventoryApplies = (
|
|
1148
|
+
plan: VocabularyRegradePlan
|
|
1149
|
+
): boolean =>
|
|
1150
|
+
vocabularyRegradeTransitionForInput(plan.from, plan.to)?.target.kind ===
|
|
1151
|
+
'classified';
|
|
1152
|
+
|
|
1153
|
+
const vocabularyEvidenceSourceKind = (
|
|
1154
|
+
path: string,
|
|
1155
|
+
plan: VocabularyRegradePlan
|
|
1156
|
+
): 'all' | 'comments' =>
|
|
1157
|
+
classifiedCommentInventoryApplies(plan) &&
|
|
1158
|
+
symbolSourceExtensions.includes(extname(path)) &&
|
|
1159
|
+
!symbolOccurrenceIsPolicyClassified(plan.scope, path)
|
|
1160
|
+
? 'comments'
|
|
1161
|
+
: 'all';
|
|
1162
|
+
|
|
1084
1163
|
const vocabularyProseEngineApplies = (
|
|
1085
1164
|
scope: VocabularyRegradePlan['scope'] | undefined
|
|
1086
1165
|
): boolean =>
|
|
@@ -1215,8 +1294,12 @@ const buildVocabularyPlan = (
|
|
|
1215
1294
|
const overrides = mergeVocabularyOverrides(registryPlan, input);
|
|
1216
1295
|
const preserveRules = mergeVocabularyPreserveRules(registryPlan, preserve);
|
|
1217
1296
|
const scope = mergeVocabularyScope(registryPlan?.scope, configScope, input);
|
|
1218
|
-
const fileRenames =
|
|
1219
|
-
input.fileRenames ?? registryFileRenamesForRoot(registryPlan, rootDir)
|
|
1297
|
+
const fileRenames = (
|
|
1298
|
+
input.fileRenames ?? registryFileRenamesForRoot(registryPlan, rootDir)
|
|
1299
|
+
)?.map((rename) => ({
|
|
1300
|
+
from: posix.normalize(rename.from.replaceAll('\\', '/')),
|
|
1301
|
+
to: posix.normalize(rename.to.replaceAll('\\', '/')),
|
|
1302
|
+
}));
|
|
1220
1303
|
|
|
1221
1304
|
return Result.ok({
|
|
1222
1305
|
...(registryPlan?.caseSensitive === undefined
|
|
@@ -1312,41 +1395,40 @@ const reportWithVocabularyTransitionRun = (params: {
|
|
|
1312
1395
|
report: transitionRunReportForRegradeReport(params.report),
|
|
1313
1396
|
};
|
|
1314
1397
|
|
|
1315
|
-
return
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1398
|
+
return withScannedPaths(
|
|
1399
|
+
{
|
|
1400
|
+
...params.report,
|
|
1401
|
+
run: {
|
|
1402
|
+
...run,
|
|
1403
|
+
plan: params.plan,
|
|
1404
|
+
...(preserveInventory === undefined ? {} : { preserveInventory }),
|
|
1405
|
+
report:
|
|
1406
|
+
params.report.run === undefined
|
|
1407
|
+
? transitionRunReportForRegradeReport(params.report)
|
|
1408
|
+
: params.report.run.report,
|
|
1409
|
+
},
|
|
1325
1410
|
},
|
|
1326
|
-
|
|
1411
|
+
params.report.scannedPaths ?? []
|
|
1412
|
+
);
|
|
1327
1413
|
};
|
|
1328
1414
|
|
|
1329
|
-
const
|
|
1330
|
-
readonly apply: boolean;
|
|
1331
|
-
readonly includeEntries: RegradeInput['includeEntries'];
|
|
1415
|
+
const governedSymbolRegradeConfiguration = (params: {
|
|
1332
1416
|
readonly plan: VocabularyRegradePlan;
|
|
1333
1417
|
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
1334
|
-
|
|
1335
|
-
}): TrailsResult<RegradeReport | null, Error> => {
|
|
1418
|
+
}) => {
|
|
1336
1419
|
const transition = vocabularyRegradeTransitionForInput(
|
|
1337
1420
|
params.plan.from,
|
|
1338
1421
|
params.plan.to
|
|
1339
1422
|
);
|
|
1340
1423
|
if (transition === undefined) {
|
|
1341
|
-
return
|
|
1424
|
+
return null;
|
|
1342
1425
|
}
|
|
1343
1426
|
|
|
1344
1427
|
const symbolCollection = vocabularySymbolCollection(params.plan.scope);
|
|
1345
1428
|
if (symbolCollection === null) {
|
|
1346
|
-
return
|
|
1429
|
+
return null;
|
|
1347
1430
|
}
|
|
1348
|
-
return
|
|
1349
|
-
apply: params.apply,
|
|
1431
|
+
return {
|
|
1350
1432
|
classes: createGovernedAstIdentifierRenameClasses(
|
|
1351
1433
|
{
|
|
1352
1434
|
...transition,
|
|
@@ -1375,6 +1457,23 @@ const runGovernedSymbolRegrade = (params: {
|
|
|
1375
1457
|
}
|
|
1376
1458
|
),
|
|
1377
1459
|
...(symbolCollection === undefined ? {} : { collection: symbolCollection }),
|
|
1460
|
+
};
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
const runGovernedSymbolRegrade = (params: {
|
|
1464
|
+
readonly apply: boolean;
|
|
1465
|
+
readonly includeEntries: RegradeInput['includeEntries'];
|
|
1466
|
+
readonly plan: VocabularyRegradePlan;
|
|
1467
|
+
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
1468
|
+
readonly rootDir: string;
|
|
1469
|
+
}): TrailsResult<RegradeReport | null, Error> => {
|
|
1470
|
+
const configuration = governedSymbolRegradeConfiguration(params);
|
|
1471
|
+
if (configuration === null) {
|
|
1472
|
+
return Result.ok(null);
|
|
1473
|
+
}
|
|
1474
|
+
return runRegrade({
|
|
1475
|
+
...configuration,
|
|
1476
|
+
apply: params.apply,
|
|
1378
1477
|
includeEntries: params.includeEntries,
|
|
1379
1478
|
root: params.rootDir,
|
|
1380
1479
|
});
|
|
@@ -1571,11 +1670,104 @@ const buildRegradePlanArtifact = (params: {
|
|
|
1571
1670
|
};
|
|
1572
1671
|
};
|
|
1573
1672
|
|
|
1673
|
+
const normalizeAuthoredPlanPath = (path: string): string =>
|
|
1674
|
+
posix.normalize(path.replaceAll('\\', '/'));
|
|
1675
|
+
|
|
1676
|
+
const normalizeRegradePlanBodyPaths = (
|
|
1677
|
+
plan: RegradePlanBody
|
|
1678
|
+
): RegradePlanBody => {
|
|
1679
|
+
const scope =
|
|
1680
|
+
plan.scope === undefined
|
|
1681
|
+
? undefined
|
|
1682
|
+
: {
|
|
1683
|
+
...plan.scope,
|
|
1684
|
+
...(plan.scope.exclude === undefined
|
|
1685
|
+
? {}
|
|
1686
|
+
: {
|
|
1687
|
+
exclude: plan.scope.exclude.map(normalizeAuthoredPlanPath),
|
|
1688
|
+
}),
|
|
1689
|
+
...(plan.scope.include === undefined
|
|
1690
|
+
? {}
|
|
1691
|
+
: {
|
|
1692
|
+
include: plan.scope.include.map(normalizeAuthoredPlanPath),
|
|
1693
|
+
}),
|
|
1694
|
+
};
|
|
1695
|
+
if (plan.kind === 'class') {
|
|
1696
|
+
return {
|
|
1697
|
+
...plan,
|
|
1698
|
+
...(scope === undefined ? {} : { scope }),
|
|
1699
|
+
} as RegradePlanBody;
|
|
1700
|
+
}
|
|
1701
|
+
return {
|
|
1702
|
+
...plan,
|
|
1703
|
+
...(plan.fileRenames === undefined
|
|
1704
|
+
? {}
|
|
1705
|
+
: {
|
|
1706
|
+
fileRenames: plan.fileRenames.map((rename) => ({
|
|
1707
|
+
...rename,
|
|
1708
|
+
from: normalizeAuthoredPlanPath(rename.from),
|
|
1709
|
+
to: normalizeAuthoredPlanPath(rename.to),
|
|
1710
|
+
})),
|
|
1711
|
+
}),
|
|
1712
|
+
...(plan.preserve === undefined
|
|
1713
|
+
? {}
|
|
1714
|
+
: {
|
|
1715
|
+
preserve: plan.preserve.map((preserve) => ({
|
|
1716
|
+
...preserve,
|
|
1717
|
+
...(preserve.paths === undefined
|
|
1718
|
+
? {}
|
|
1719
|
+
: {
|
|
1720
|
+
paths: preserve.paths.map(normalizeAuthoredPlanPath),
|
|
1721
|
+
}),
|
|
1722
|
+
})),
|
|
1723
|
+
}),
|
|
1724
|
+
...(scope === undefined
|
|
1725
|
+
? {}
|
|
1726
|
+
: {
|
|
1727
|
+
scope: {
|
|
1728
|
+
...scope,
|
|
1729
|
+
...(plan.scope?.ignoredDirectories === undefined
|
|
1730
|
+
? {}
|
|
1731
|
+
: {
|
|
1732
|
+
ignoredDirectories: plan.scope.ignoredDirectories.map(
|
|
1733
|
+
normalizeAuthoredPlanPath
|
|
1734
|
+
),
|
|
1735
|
+
}),
|
|
1736
|
+
...(plan.scope?.policyClassified === undefined
|
|
1737
|
+
? {}
|
|
1738
|
+
: {
|
|
1739
|
+
policyClassified: plan.scope.policyClassified.map(
|
|
1740
|
+
(policy) => ({
|
|
1741
|
+
...policy,
|
|
1742
|
+
paths: policy.paths.map(normalizeAuthoredPlanPath),
|
|
1743
|
+
})
|
|
1744
|
+
),
|
|
1745
|
+
}),
|
|
1746
|
+
...(plan.scope?.teachingSurfaces === undefined
|
|
1747
|
+
? {}
|
|
1748
|
+
: {
|
|
1749
|
+
teachingSurfaces: plan.scope.teachingSurfaces.map(
|
|
1750
|
+
normalizeAuthoredPlanPath
|
|
1751
|
+
),
|
|
1752
|
+
}),
|
|
1753
|
+
},
|
|
1754
|
+
}),
|
|
1755
|
+
} as RegradePlanBody;
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
const normalizeRegradePlanArtifactPaths = (
|
|
1759
|
+
artifact: RegradePlanArtifact
|
|
1760
|
+
): RegradePlanArtifact => ({
|
|
1761
|
+
...artifact,
|
|
1762
|
+
plan: normalizeRegradePlanBodyPaths(artifact.plan),
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1574
1765
|
const writeRegradePlanArtifact = (
|
|
1575
1766
|
rootDir: string,
|
|
1576
1767
|
artifact: RegradePlanArtifact
|
|
1577
1768
|
): TrailsResult<RegradePlanArtifact, InternalError | ValidationError> => {
|
|
1578
|
-
const
|
|
1769
|
+
const normalizedArtifact = normalizeRegradePlanArtifactPaths(artifact);
|
|
1770
|
+
const parsed = regradePlanArtifactSchema.safeParse(normalizedArtifact);
|
|
1579
1771
|
if (!parsed.success) {
|
|
1580
1772
|
return Result.err(
|
|
1581
1773
|
new ValidationError('Invalid Regrade plan artifact.', {
|
|
@@ -1601,7 +1793,9 @@ const writeRegradePlanArtifact = (
|
|
|
1601
1793
|
const validateRegradePlanArtifact = (
|
|
1602
1794
|
artifact: RegradePlanArtifact
|
|
1603
1795
|
): TrailsResult<RegradePlanArtifact, ValidationError> => {
|
|
1604
|
-
const parsed = regradePlanArtifactSchema.safeParse(
|
|
1796
|
+
const parsed = regradePlanArtifactSchema.safeParse(
|
|
1797
|
+
normalizeRegradePlanArtifactPaths(artifact)
|
|
1798
|
+
);
|
|
1605
1799
|
if (!parsed.success) {
|
|
1606
1800
|
return Result.err(
|
|
1607
1801
|
new ValidationError('Invalid Regrade plan artifact.', {
|
|
@@ -1637,7 +1831,11 @@ const readRegradePlanArtifact = (
|
|
|
1637
1831
|
})
|
|
1638
1832
|
);
|
|
1639
1833
|
}
|
|
1640
|
-
return Result.ok(
|
|
1834
|
+
return Result.ok(
|
|
1835
|
+
normalizeRegradePlanArtifactPaths(
|
|
1836
|
+
parsed.data as unknown as RegradePlanArtifact
|
|
1837
|
+
)
|
|
1838
|
+
);
|
|
1641
1839
|
};
|
|
1642
1840
|
|
|
1643
1841
|
/**
|
|
@@ -1897,7 +2095,7 @@ const withFileRenameEvidence = (params: {
|
|
|
1897
2095
|
]);
|
|
1898
2096
|
const filePolicyCount = params.run.policyOccurrencePaths.length;
|
|
1899
2097
|
const fileInScopeCount = params.run.occurrencePaths.length - filePolicyCount;
|
|
1900
|
-
const
|
|
2098
|
+
const derivedFileInScopeCount =
|
|
1901
2099
|
params.run.report.rewritten + params.run.report.review;
|
|
1902
2100
|
return {
|
|
1903
2101
|
...params.report,
|
|
@@ -1914,7 +2112,7 @@ const withFileRenameEvidence = (params: {
|
|
|
1914
2112
|
scopeTiers: {
|
|
1915
2113
|
'in-scope':
|
|
1916
2114
|
params.report.run.report.scopeTiers['in-scope'] -
|
|
1917
|
-
|
|
2115
|
+
derivedFileInScopeCount +
|
|
1918
2116
|
fileInScopeCount,
|
|
1919
2117
|
'policy-classified':
|
|
1920
2118
|
params.report.run.report.scopeTiers['policy-classified'] +
|
|
@@ -2016,22 +2214,88 @@ const runPlanFileRenames = (
|
|
|
2016
2214
|
vocabularyPlan: plan,
|
|
2017
2215
|
});
|
|
2018
2216
|
|
|
2019
|
-
|
|
2217
|
+
interface PreparedVocabularyPlanRun {
|
|
2218
|
+
readonly fileRenamePreflight: FileRenameRegradeRun | null;
|
|
2219
|
+
readonly identity: PreparedRegradeRunIdentity;
|
|
2220
|
+
readonly prose: PreparedVocabularyRegradeRun | null;
|
|
2221
|
+
readonly proseReport: RegradeReport | null;
|
|
2222
|
+
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
2223
|
+
readonly symbol: PreparedRegradeRun | null;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
const validatePreparedPlanIdentity = (
|
|
2227
|
+
expected: PreparedRegradeRunIdentity,
|
|
2228
|
+
actual: PreparedRegradeRunIdentity
|
|
2229
|
+
): TrailsResult<void, ValidationError> => {
|
|
2230
|
+
for (const field of [
|
|
2231
|
+
'planContentHash',
|
|
2232
|
+
'policyHash',
|
|
2233
|
+
'scopeHash',
|
|
2234
|
+
'lockStateHash',
|
|
2235
|
+
'toolVersion',
|
|
2236
|
+
] as const) {
|
|
2237
|
+
if (expected[field] !== actual[field]) {
|
|
2238
|
+
return Result.err(
|
|
2239
|
+
new ValidationError(
|
|
2240
|
+
`Prepared Regrade identity field \`${field}\` is stale.`,
|
|
2241
|
+
{
|
|
2242
|
+
context: {
|
|
2243
|
+
actual: actual[field],
|
|
2244
|
+
expected: expected[field],
|
|
2245
|
+
field,
|
|
2246
|
+
},
|
|
2247
|
+
}
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return Result.ok();
|
|
2253
|
+
};
|
|
2254
|
+
|
|
2255
|
+
interface ResolvedVocabularyPlanParams {
|
|
2020
2256
|
readonly apply: boolean;
|
|
2257
|
+
readonly currentIdentity?: PreparedRegradeRunIdentity | undefined;
|
|
2021
2258
|
readonly includeEntries: RegradeInput['includeEntries'];
|
|
2022
2259
|
readonly plan: VocabularyRegradePlan;
|
|
2260
|
+
readonly prepareIdentity?: PreparedRegradeRunIdentity | undefined;
|
|
2261
|
+
readonly prepared?: PreparedVocabularyPlanRun | undefined;
|
|
2262
|
+
readonly preparedResult?:
|
|
2263
|
+
| { value?: PreparedVocabularyPlanRun | undefined }
|
|
2264
|
+
| undefined;
|
|
2023
2265
|
readonly preserveInventory: readonly VocabularyPreserveInventoryEntry[];
|
|
2024
2266
|
readonly rootDir: string;
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
interface VocabularyPlanExecution {
|
|
2270
|
+
readonly fileRenamePreflight: FileRenameRegradeRun | null;
|
|
2271
|
+
readonly prosePrepared: PreparedVocabularyRegradeRun | null;
|
|
2272
|
+
readonly prosePreviewReport: RegradeReport | null;
|
|
2273
|
+
readonly runProse: (
|
|
2274
|
+
apply: boolean
|
|
2275
|
+
) => TrailsResult<RegradeReport | null, Error>;
|
|
2276
|
+
readonly runSymbols: (
|
|
2277
|
+
apply: boolean
|
|
2278
|
+
) => TrailsResult<RegradeReport | null, Error>;
|
|
2279
|
+
readonly symbolPrepared: PreparedRegradeRun | null;
|
|
2280
|
+
readonly symbolPreviewReport: RegradeReport | null;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
const vocabularyPlanExecution = (
|
|
2284
|
+
params: ResolvedVocabularyPlanParams
|
|
2285
|
+
): TrailsResult<VocabularyPlanExecution, Error> => {
|
|
2286
|
+
const fileRenamePreflight =
|
|
2287
|
+
params.prepared === undefined
|
|
2288
|
+
? runPlanFileRenames(params.plan, {
|
|
2289
|
+
apply: false,
|
|
2290
|
+
includeEntries: params.includeEntries,
|
|
2291
|
+
rootDir: params.rootDir,
|
|
2292
|
+
})
|
|
2293
|
+
: Result.ok(params.prepared.fileRenamePreflight);
|
|
2031
2294
|
if (fileRenamePreflight.isErr()) {
|
|
2032
2295
|
return fileRenamePreflight;
|
|
2033
2296
|
}
|
|
2034
2297
|
const evidencePlan = vocabularyEvidencePlan(params.plan);
|
|
2298
|
+
const includeCodeComments = classifiedCommentInventoryApplies(params.plan);
|
|
2035
2299
|
const runProse = (
|
|
2036
2300
|
apply: boolean
|
|
2037
2301
|
): TrailsResult<RegradeReport | null, Error> =>
|
|
@@ -2046,7 +2310,13 @@ const runResolvedVocabularyPlan = (params: {
|
|
|
2046
2310
|
: { preserveInventory: params.preserveInventory }),
|
|
2047
2311
|
root: params.rootDir,
|
|
2048
2312
|
sourceFilter: (path) =>
|
|
2049
|
-
vocabularyEvidenceSource(
|
|
2313
|
+
vocabularyEvidenceSource(
|
|
2314
|
+
path,
|
|
2315
|
+
evidencePlan.scope,
|
|
2316
|
+
includeCodeComments
|
|
2317
|
+
),
|
|
2318
|
+
sourceKindForPath: (path) =>
|
|
2319
|
+
vocabularyEvidenceSourceKind(path, evidencePlan),
|
|
2050
2320
|
});
|
|
2051
2321
|
const runSymbols = (
|
|
2052
2322
|
apply: boolean
|
|
@@ -2058,47 +2328,244 @@ const runResolvedVocabularyPlan = (params: {
|
|
|
2058
2328
|
preserveInventory: params.preserveInventory,
|
|
2059
2329
|
rootDir: params.rootDir,
|
|
2060
2330
|
});
|
|
2061
|
-
const
|
|
2331
|
+
const prosePrepared =
|
|
2332
|
+
params.prepareIdentity === undefined || evidencePlan === null
|
|
2333
|
+
? Result.ok(null)
|
|
2334
|
+
: prepareVocabularyRegradeRun({
|
|
2335
|
+
identity: params.prepareIdentity,
|
|
2336
|
+
includeEntries: params.includeEntries,
|
|
2337
|
+
plan: evidencePlan,
|
|
2338
|
+
...(params.preserveInventory.length === 0
|
|
2339
|
+
? {}
|
|
2340
|
+
: { preserveInventory: params.preserveInventory }),
|
|
2341
|
+
root: params.rootDir,
|
|
2342
|
+
sourceFilter: (path) =>
|
|
2343
|
+
vocabularyEvidenceSource(
|
|
2344
|
+
path,
|
|
2345
|
+
evidencePlan.scope,
|
|
2346
|
+
includeCodeComments
|
|
2347
|
+
),
|
|
2348
|
+
sourceKindForPath: (path) =>
|
|
2349
|
+
vocabularyEvidenceSourceKind(path, evidencePlan),
|
|
2350
|
+
});
|
|
2351
|
+
if (prosePrepared.isErr()) {
|
|
2352
|
+
return prosePrepared;
|
|
2353
|
+
}
|
|
2354
|
+
let prosePreview: TrailsResult<RegradeReport | null, Error>;
|
|
2355
|
+
if (params.prepared !== undefined) {
|
|
2356
|
+
prosePreview = Result.ok(params.prepared.proseReport);
|
|
2357
|
+
} else if (prosePrepared.value === null) {
|
|
2358
|
+
prosePreview = runProse(false);
|
|
2359
|
+
} else {
|
|
2360
|
+
prosePreview = Result.ok(prosePrepared.value.report);
|
|
2361
|
+
}
|
|
2062
2362
|
if (prosePreview.isErr()) {
|
|
2063
2363
|
return prosePreview;
|
|
2064
2364
|
}
|
|
2065
2365
|
const prosePreviewReport = withoutVocabularySourceFilterSkips(
|
|
2066
2366
|
prosePreview.value
|
|
2067
2367
|
);
|
|
2068
|
-
const
|
|
2368
|
+
const symbolConfiguration = governedSymbolRegradeConfiguration({
|
|
2369
|
+
plan: params.plan,
|
|
2370
|
+
preserveInventory: params.preserveInventory,
|
|
2371
|
+
});
|
|
2372
|
+
const symbolPrepared =
|
|
2373
|
+
params.prepareIdentity === undefined || symbolConfiguration === null
|
|
2374
|
+
? Result.ok(null)
|
|
2375
|
+
: prepareRegradeRun({
|
|
2376
|
+
...symbolConfiguration,
|
|
2377
|
+
identity: params.prepareIdentity,
|
|
2378
|
+
includeEntries: params.includeEntries,
|
|
2379
|
+
root: params.rootDir,
|
|
2380
|
+
});
|
|
2381
|
+
if (symbolPrepared.isErr()) {
|
|
2382
|
+
return symbolPrepared;
|
|
2383
|
+
}
|
|
2384
|
+
// Apply reevaluates symbol work after prose because shared code comments can
|
|
2385
|
+
// change source bytes and offsets. Initial preparation retains the original
|
|
2386
|
+
// symbol source state solely for pre-mutation freshness validation.
|
|
2387
|
+
const symbolPreview =
|
|
2388
|
+
params.prepared !== undefined || symbolPrepared.value === null
|
|
2389
|
+
? runSymbols(false)
|
|
2390
|
+
: Result.ok(symbolPrepared.value.report);
|
|
2069
2391
|
if (symbolPreview.isErr()) {
|
|
2070
2392
|
return symbolPreview;
|
|
2071
2393
|
}
|
|
2394
|
+
return Result.ok({
|
|
2395
|
+
fileRenamePreflight: fileRenamePreflight.value,
|
|
2396
|
+
prosePrepared: prosePrepared.value,
|
|
2397
|
+
prosePreviewReport,
|
|
2398
|
+
runProse,
|
|
2399
|
+
runSymbols,
|
|
2400
|
+
symbolPrepared: symbolPrepared.value,
|
|
2401
|
+
symbolPreviewReport: symbolPreview.value,
|
|
2402
|
+
});
|
|
2403
|
+
};
|
|
2404
|
+
|
|
2405
|
+
const previewResolvedVocabularyPlan = (
|
|
2406
|
+
params: ResolvedVocabularyPlanParams,
|
|
2407
|
+
execution: VocabularyPlanExecution
|
|
2408
|
+
): TrailsResult<RegradeReport, Error> => {
|
|
2409
|
+
if (
|
|
2410
|
+
execution.prosePreviewReport?.scanned === 0 &&
|
|
2411
|
+
execution.symbolPreviewReport === null &&
|
|
2412
|
+
execution.fileRenamePreflight === null &&
|
|
2413
|
+
!vocabularyProseEngineApplies(params.plan.scope)
|
|
2414
|
+
) {
|
|
2415
|
+
return regradeNoEngineForScope();
|
|
2416
|
+
}
|
|
2417
|
+
const report = combineVocabularyReports({
|
|
2418
|
+
fileRenameRun: execution.fileRenamePreflight,
|
|
2419
|
+
plan: params.plan,
|
|
2420
|
+
preserveInventory: params.preserveInventory,
|
|
2421
|
+
proseReport: execution.prosePreviewReport,
|
|
2422
|
+
symbolReport: execution.symbolPreviewReport,
|
|
2423
|
+
});
|
|
2424
|
+
if (
|
|
2425
|
+
report.isOk() &&
|
|
2426
|
+
params.prepareIdentity !== undefined &&
|
|
2427
|
+
params.preparedResult !== undefined
|
|
2428
|
+
) {
|
|
2429
|
+
params.preparedResult.value = {
|
|
2430
|
+
fileRenamePreflight: execution.fileRenamePreflight,
|
|
2431
|
+
identity: params.prepareIdentity,
|
|
2432
|
+
preserveInventory: params.preserveInventory,
|
|
2433
|
+
prose: execution.prosePrepared,
|
|
2434
|
+
proseReport: execution.prosePreviewReport,
|
|
2435
|
+
symbol: execution.symbolPrepared,
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
return report;
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
export const validatePreparedFileRenameSourceState = (params: {
|
|
2442
|
+
readonly includeEntries: RegradeInput['includeEntries'];
|
|
2443
|
+
readonly plan: VocabularyRegradePlan;
|
|
2444
|
+
readonly prepared: FileRenameRegradeRun | null;
|
|
2445
|
+
readonly rootDir: string;
|
|
2446
|
+
}): TrailsResult<void, Error> => {
|
|
2447
|
+
if (params.prepared === null) {
|
|
2448
|
+
return Result.ok();
|
|
2449
|
+
}
|
|
2450
|
+
const current = runPlanFileRenames(params.plan, {
|
|
2451
|
+
apply: false,
|
|
2452
|
+
includeEntries: params.includeEntries,
|
|
2453
|
+
rootDir: params.rootDir,
|
|
2454
|
+
});
|
|
2455
|
+
if (current.isErr()) {
|
|
2456
|
+
return current;
|
|
2457
|
+
}
|
|
2458
|
+
if (current.value === null) {
|
|
2459
|
+
return Result.err(
|
|
2460
|
+
new InternalError('Prepared Regrade file rename set changed.')
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
if (current.value.sourceStateHash !== params.prepared.sourceStateHash) {
|
|
2464
|
+
return Result.err(
|
|
2465
|
+
new ValidationError(
|
|
2466
|
+
'Prepared Regrade file rename source state is stale.',
|
|
2467
|
+
{
|
|
2468
|
+
context: {
|
|
2469
|
+
actual: current.value.sourceStateHash,
|
|
2470
|
+
expected: params.prepared.sourceStateHash,
|
|
2471
|
+
},
|
|
2472
|
+
}
|
|
2473
|
+
)
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
return Result.ok();
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
const validatePreparedVocabularyPlanState = (params: {
|
|
2480
|
+
readonly currentIdentity: PreparedRegradeRunIdentity;
|
|
2481
|
+
readonly execution: VocabularyPlanExecution;
|
|
2482
|
+
readonly includeEntries: RegradeInput['includeEntries'];
|
|
2483
|
+
readonly plan: VocabularyRegradePlan;
|
|
2484
|
+
readonly prepared: PreparedVocabularyPlanRun;
|
|
2485
|
+
readonly rootDir: string;
|
|
2486
|
+
}): TrailsResult<void, Error> => {
|
|
2487
|
+
const identity = validatePreparedPlanIdentity(
|
|
2488
|
+
params.prepared.identity,
|
|
2489
|
+
params.currentIdentity
|
|
2490
|
+
);
|
|
2491
|
+
if (identity.isErr()) {
|
|
2492
|
+
return identity;
|
|
2493
|
+
}
|
|
2494
|
+
const fileRenameState = validatePreparedFileRenameSourceState({
|
|
2495
|
+
includeEntries: params.includeEntries,
|
|
2496
|
+
plan: params.plan,
|
|
2497
|
+
prepared: params.execution.fileRenamePreflight,
|
|
2498
|
+
rootDir: params.rootDir,
|
|
2499
|
+
});
|
|
2500
|
+
if (fileRenameState.isErr() || params.prepared.symbol === null) {
|
|
2501
|
+
return fileRenameState;
|
|
2502
|
+
}
|
|
2503
|
+
return validatePreparedRegradeRun(
|
|
2504
|
+
params.prepared.symbol,
|
|
2505
|
+
params.currentIdentity
|
|
2506
|
+
);
|
|
2507
|
+
};
|
|
2508
|
+
|
|
2509
|
+
/**
|
|
2510
|
+
* Prepared vocabulary lifecycle seam for focused conformance tests.
|
|
2511
|
+
*
|
|
2512
|
+
* @internal
|
|
2513
|
+
*/
|
|
2514
|
+
export const runResolvedVocabularyPlan = (
|
|
2515
|
+
params: ResolvedVocabularyPlanParams
|
|
2516
|
+
): TrailsResult<RegradeReport, Error> => {
|
|
2517
|
+
const execution = vocabularyPlanExecution(params);
|
|
2518
|
+
if (execution.isErr()) {
|
|
2519
|
+
return execution;
|
|
2520
|
+
}
|
|
2072
2521
|
if (!params.apply) {
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
) {
|
|
2079
|
-
return
|
|
2522
|
+
return previewResolvedVocabularyPlan(params, execution.value);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
let { currentIdentity } = params;
|
|
2526
|
+
if (params.prepared !== undefined) {
|
|
2527
|
+
if (currentIdentity === undefined) {
|
|
2528
|
+
return Result.err(
|
|
2529
|
+
new InternalError('Prepared Regrade apply is missing current identity.')
|
|
2530
|
+
);
|
|
2080
2531
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2532
|
+
const preparedState = validatePreparedVocabularyPlanState({
|
|
2533
|
+
currentIdentity,
|
|
2534
|
+
execution: execution.value,
|
|
2535
|
+
includeEntries: params.includeEntries,
|
|
2083
2536
|
plan: params.plan,
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
symbolReport: symbolPreview.value,
|
|
2537
|
+
prepared: params.prepared,
|
|
2538
|
+
rootDir: params.rootDir,
|
|
2087
2539
|
});
|
|
2540
|
+
if (preparedState.isErr()) {
|
|
2541
|
+
return preparedState;
|
|
2542
|
+
}
|
|
2088
2543
|
}
|
|
2089
2544
|
|
|
2090
2545
|
const snapshots = snapshotRegradeSources({
|
|
2091
|
-
reports: [
|
|
2546
|
+
reports: [
|
|
2547
|
+
execution.value.prosePreviewReport,
|
|
2548
|
+
execution.value.symbolPreviewReport,
|
|
2549
|
+
],
|
|
2092
2550
|
rootDir: params.rootDir,
|
|
2093
2551
|
});
|
|
2094
2552
|
if (snapshots.isErr()) {
|
|
2095
2553
|
return snapshots;
|
|
2096
2554
|
}
|
|
2097
|
-
|
|
2555
|
+
let reportResult: TrailsResult<RegradeReport | null, Error>;
|
|
2556
|
+
if (params.prepared?.prose === undefined || params.prepared.prose === null) {
|
|
2557
|
+
reportResult = execution.value.runProse(true);
|
|
2558
|
+
} else {
|
|
2559
|
+
currentIdentity ??= params.prepared.identity;
|
|
2560
|
+
reportResult = applyPreparedVocabularyRegradeRun(
|
|
2561
|
+
params.prepared.prose,
|
|
2562
|
+
currentIdentity
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2098
2565
|
if (reportResult.isErr()) {
|
|
2099
2566
|
return regradeApplyErrorAfterRollback(reportResult.error, snapshots.value);
|
|
2100
2567
|
}
|
|
2101
|
-
const symbolReportResult = runSymbols(true);
|
|
2568
|
+
const symbolReportResult = execution.value.runSymbols(true);
|
|
2102
2569
|
if (symbolReportResult.isErr()) {
|
|
2103
2570
|
return regradeApplyErrorAfterRollback(
|
|
2104
2571
|
symbolReportResult.error,
|
|
@@ -2247,6 +2714,131 @@ const runPlanArtifactDryRun = async (params: {
|
|
|
2247
2714
|
});
|
|
2248
2715
|
};
|
|
2249
2716
|
|
|
2717
|
+
interface PreparedClassPlanRun {
|
|
2718
|
+
readonly identity: PreparedRegradeRunIdentity;
|
|
2719
|
+
readonly run: PreparedRegradeRun;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
type PreparedPlanRun =
|
|
2723
|
+
| { readonly kind: 'class'; readonly prepared: PreparedClassPlanRun }
|
|
2724
|
+
| {
|
|
2725
|
+
readonly kind: 'vocabulary';
|
|
2726
|
+
readonly prepared: PreparedVocabularyPlanRun;
|
|
2727
|
+
};
|
|
2728
|
+
|
|
2729
|
+
const preparePlanArtifactRun = async (params: {
|
|
2730
|
+
readonly artifact: RegradePlanArtifact;
|
|
2731
|
+
readonly includeEntries: RegradePlanReferenceInput['includeEntries'];
|
|
2732
|
+
readonly rootDir: string;
|
|
2733
|
+
}): Promise<
|
|
2734
|
+
TrailsResult<
|
|
2735
|
+
{ readonly prepared: PreparedPlanRun; readonly report: RegradeReport },
|
|
2736
|
+
Error
|
|
2737
|
+
>
|
|
2738
|
+
> => {
|
|
2739
|
+
const planBody = params.artifact.plan;
|
|
2740
|
+
if (planBody.kind === 'class') {
|
|
2741
|
+
const classSet = await loadWardenRegradeClasses(params.rootDir);
|
|
2742
|
+
if (classSet.diagnostics.length > 0) {
|
|
2743
|
+
return Result.err(
|
|
2744
|
+
new InternalError('Failed to load Regrade project Warden rules.', {
|
|
2745
|
+
context: {
|
|
2746
|
+
diagnostics: classSet.diagnostics,
|
|
2747
|
+
rootDir: params.rootDir,
|
|
2748
|
+
},
|
|
2749
|
+
})
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
const identity = preparedRegradeRunIdentity({
|
|
2753
|
+
artifact: params.artifact,
|
|
2754
|
+
classIds: classSet.classes.map((regradeClass) => regradeClass.id),
|
|
2755
|
+
classes: classSet.classes,
|
|
2756
|
+
includeEntries: params.includeEntries,
|
|
2757
|
+
rootDir: params.rootDir,
|
|
2758
|
+
});
|
|
2759
|
+
if (identity.isErr()) {
|
|
2760
|
+
return identity;
|
|
2761
|
+
}
|
|
2762
|
+
const prepared = prepareRegradeRun({
|
|
2763
|
+
classes: classSet.classes,
|
|
2764
|
+
...(planBody.scope === undefined
|
|
2765
|
+
? {}
|
|
2766
|
+
: {
|
|
2767
|
+
collection: {
|
|
2768
|
+
...(planBody.scope.exclude === undefined
|
|
2769
|
+
? {}
|
|
2770
|
+
: { exclude: planBody.scope.exclude }),
|
|
2771
|
+
...(planBody.scope.extensions === undefined
|
|
2772
|
+
? {}
|
|
2773
|
+
: { extensions: planBody.scope.extensions }),
|
|
2774
|
+
...(planBody.scope.include === undefined
|
|
2775
|
+
? {}
|
|
2776
|
+
: { include: planBody.scope.include }),
|
|
2777
|
+
},
|
|
2778
|
+
}),
|
|
2779
|
+
identity: identity.value,
|
|
2780
|
+
includeEntries: params.includeEntries,
|
|
2781
|
+
root: params.rootDir,
|
|
2782
|
+
selection: { classIds: planBody.classIds },
|
|
2783
|
+
});
|
|
2784
|
+
if (prepared.isErr()) {
|
|
2785
|
+
return prepared;
|
|
2786
|
+
}
|
|
2787
|
+
if (prepared.value === null) {
|
|
2788
|
+
return regradeRootNotFound(params.rootDir);
|
|
2789
|
+
}
|
|
2790
|
+
const report = validateRegradeReport(prepared.value.report);
|
|
2791
|
+
if (report.isErr()) {
|
|
2792
|
+
return report;
|
|
2793
|
+
}
|
|
2794
|
+
return Result.ok({
|
|
2795
|
+
prepared: {
|
|
2796
|
+
kind: 'class',
|
|
2797
|
+
prepared: { identity: identity.value, run: prepared.value },
|
|
2798
|
+
},
|
|
2799
|
+
report: report.value,
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
const preserveResult = await deriveLiveApiPreserveInventory(
|
|
2804
|
+
planBody,
|
|
2805
|
+
params.rootDir
|
|
2806
|
+
);
|
|
2807
|
+
if (preserveResult.isErr()) {
|
|
2808
|
+
return preserveResult;
|
|
2809
|
+
}
|
|
2810
|
+
const identity = preparedRegradeRunIdentity({
|
|
2811
|
+
artifact: params.artifact,
|
|
2812
|
+
includeEntries: params.includeEntries,
|
|
2813
|
+
rootDir: params.rootDir,
|
|
2814
|
+
});
|
|
2815
|
+
if (identity.isErr()) {
|
|
2816
|
+
return identity;
|
|
2817
|
+
}
|
|
2818
|
+
const preparedResult: { value?: PreparedVocabularyPlanRun } = {};
|
|
2819
|
+
const report = runResolvedVocabularyPlan({
|
|
2820
|
+
apply: false,
|
|
2821
|
+
includeEntries: params.includeEntries,
|
|
2822
|
+
plan: planBody,
|
|
2823
|
+
prepareIdentity: identity.value,
|
|
2824
|
+
preparedResult,
|
|
2825
|
+
preserveInventory: preserveResult.value,
|
|
2826
|
+
rootDir: params.rootDir,
|
|
2827
|
+
});
|
|
2828
|
+
if (report.isErr()) {
|
|
2829
|
+
return report;
|
|
2830
|
+
}
|
|
2831
|
+
if (preparedResult.value === undefined) {
|
|
2832
|
+
return Result.err(
|
|
2833
|
+
new InternalError('Regrade vocabulary run was not prepared.')
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
return Result.ok({
|
|
2837
|
+
prepared: { kind: 'vocabulary', prepared: preparedResult.value },
|
|
2838
|
+
report: report.value,
|
|
2839
|
+
});
|
|
2840
|
+
};
|
|
2841
|
+
|
|
2250
2842
|
const runLegacyVocabularyRecordRegrade = (
|
|
2251
2843
|
input: RegradeInput,
|
|
2252
2844
|
rootDir: string,
|
|
@@ -3054,12 +3646,13 @@ const loadPlanForInput = async (
|
|
|
3054
3646
|
|
|
3055
3647
|
const reportWithCheckedHistorySummary = (
|
|
3056
3648
|
report: RegradeReport,
|
|
3057
|
-
historyPath: string
|
|
3649
|
+
historyPath: string,
|
|
3650
|
+
schemaVersion: number
|
|
3058
3651
|
): RegradeReport => ({
|
|
3059
3652
|
...report,
|
|
3060
3653
|
history: {
|
|
3061
3654
|
path: historyPath,
|
|
3062
|
-
schemaVersion
|
|
3655
|
+
schemaVersion,
|
|
3063
3656
|
status: 'checked',
|
|
3064
3657
|
},
|
|
3065
3658
|
});
|
|
@@ -3089,7 +3682,11 @@ const checkGraduatedRegradeHistory = (
|
|
|
3089
3682
|
);
|
|
3090
3683
|
}
|
|
3091
3684
|
return validateRegradeReport(
|
|
3092
|
-
reportWithCheckedHistorySummary(
|
|
3685
|
+
reportWithCheckedHistorySummary(
|
|
3686
|
+
lastRun.report,
|
|
3687
|
+
artifact.value.path,
|
|
3688
|
+
artifact.value.schemaVersion
|
|
3689
|
+
)
|
|
3093
3690
|
);
|
|
3094
3691
|
};
|
|
3095
3692
|
|
|
@@ -3104,6 +3701,7 @@ const runCheckRegradePlan = async (
|
|
|
3104
3701
|
if (historyPath.isOk()) {
|
|
3105
3702
|
return checkGraduatedRegradeHistory(historyPath.value);
|
|
3106
3703
|
}
|
|
3704
|
+
return historyPath;
|
|
3107
3705
|
}
|
|
3108
3706
|
return planPath;
|
|
3109
3707
|
}
|
|
@@ -3114,24 +3712,17 @@ const runCheckRegradePlan = async (
|
|
|
3114
3712
|
const loaded = {
|
|
3115
3713
|
value: { artifact: artifact.value, path: planPath.value },
|
|
3116
3714
|
};
|
|
3117
|
-
const
|
|
3715
|
+
const prepared = await preparePlanArtifactRun({
|
|
3118
3716
|
artifact: loaded.value.artifact,
|
|
3119
3717
|
includeEntries: input.includeEntries,
|
|
3120
3718
|
rootDir,
|
|
3121
3719
|
});
|
|
3122
|
-
if (
|
|
3123
|
-
return
|
|
3720
|
+
if (prepared.isErr()) {
|
|
3721
|
+
return prepared;
|
|
3124
3722
|
}
|
|
3125
|
-
const
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
rootDir
|
|
3129
|
-
);
|
|
3130
|
-
const checked = reportWithPlanSummary(
|
|
3131
|
-
report.value,
|
|
3132
|
-
loaded.value.artifact,
|
|
3133
|
-
status
|
|
3134
|
-
);
|
|
3723
|
+
const { report } = prepared.value;
|
|
3724
|
+
const status = planStatusForReport(loaded.value.artifact, report, rootDir);
|
|
3725
|
+
const checked = reportWithPlanSummary(report, loaded.value.artifact, status);
|
|
3135
3726
|
if (status === 'stale') {
|
|
3136
3727
|
return Result.err(
|
|
3137
3728
|
new ValidationError(
|
|
@@ -3164,29 +3755,32 @@ const runPreviewRegradePlan = async (
|
|
|
3164
3755
|
if (loaded.isErr()) {
|
|
3165
3756
|
return loaded;
|
|
3166
3757
|
}
|
|
3167
|
-
const
|
|
3758
|
+
const prepared = await preparePlanArtifactRun({
|
|
3168
3759
|
artifact: loaded.value.artifact,
|
|
3169
3760
|
includeEntries: input.includeEntries,
|
|
3170
3761
|
rootDir,
|
|
3171
3762
|
});
|
|
3172
|
-
if (
|
|
3173
|
-
return
|
|
3763
|
+
if (prepared.isErr()) {
|
|
3764
|
+
return prepared;
|
|
3174
3765
|
}
|
|
3766
|
+
const { report } = prepared.value;
|
|
3175
3767
|
return validateRegradeReport(
|
|
3176
3768
|
reportWithPlanSummary(
|
|
3177
|
-
report
|
|
3769
|
+
report,
|
|
3178
3770
|
loaded.value.artifact,
|
|
3179
|
-
planStatusForReport(loaded.value.artifact, report
|
|
3771
|
+
planStatusForReport(loaded.value.artifact, report, rootDir)
|
|
3180
3772
|
)
|
|
3181
3773
|
);
|
|
3182
3774
|
};
|
|
3183
3775
|
|
|
3184
3776
|
const writeRegradeHistory = (params: {
|
|
3185
3777
|
readonly artifact: RegradePlanArtifact;
|
|
3778
|
+
readonly changedFiles: readonly RegradeChangedFileEvidence[];
|
|
3186
3779
|
readonly completedReport: RegradeReport;
|
|
3187
3780
|
readonly planPath: string;
|
|
3188
3781
|
readonly report: RegradeReport;
|
|
3189
3782
|
readonly rootDir: string;
|
|
3783
|
+
readonly sourceRevision: string;
|
|
3190
3784
|
}): TrailsResult<RegradeHistorySummary, Error> => {
|
|
3191
3785
|
const absolutePath = regradeHistoryPathForPlan(
|
|
3192
3786
|
params.rootDir,
|
|
@@ -3207,37 +3801,27 @@ const writeRegradeHistory = (params: {
|
|
|
3207
3801
|
}
|
|
3208
3802
|
const appended = appendRegradeHistoryRun({
|
|
3209
3803
|
artifact: params.artifact,
|
|
3804
|
+
changedFiles: params.changedFiles,
|
|
3210
3805
|
completedReport: params.completedReport,
|
|
3211
3806
|
report: params.report,
|
|
3212
3807
|
rootDir: params.rootDir,
|
|
3808
|
+
sourceRevision: params.sourceRevision,
|
|
3213
3809
|
});
|
|
3214
3810
|
if (appended.isErr()) {
|
|
3215
3811
|
return appended;
|
|
3216
3812
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
} catch {
|
|
3230
|
-
// Best-effort rollback; the surfaced error below preserves the primary failure.
|
|
3231
|
-
}
|
|
3232
|
-
return Result.err(
|
|
3233
|
-
new InternalError('Failed to remove active Regrade plan.', {
|
|
3234
|
-
...(error instanceof Error ? { cause: error } : {}),
|
|
3235
|
-
context: {
|
|
3236
|
-
history: appended.value.path,
|
|
3237
|
-
plan: rootRelativePath(params.rootDir, params.planPath),
|
|
3238
|
-
},
|
|
3239
|
-
})
|
|
3240
|
-
);
|
|
3813
|
+
// Apply always consumes the active plan, replay included: the plan is a
|
|
3814
|
+
// single-use apply intent, and the consolidated history already records
|
|
3815
|
+
// the run the replay repeats.
|
|
3816
|
+
const consumed = consumeActiveRegradePlanAfterHistoryWrite({
|
|
3817
|
+
absoluteHistoryPath: absolutePath,
|
|
3818
|
+
absolutePlanPath: params.planPath,
|
|
3819
|
+
historyPath: appended.value.path,
|
|
3820
|
+
planPath: rootRelativePath(params.rootDir, params.planPath),
|
|
3821
|
+
priorHistoryBytes,
|
|
3822
|
+
});
|
|
3823
|
+
if (consumed.isErr()) {
|
|
3824
|
+
return consumed;
|
|
3241
3825
|
}
|
|
3242
3826
|
return appended;
|
|
3243
3827
|
};
|
|
@@ -3260,6 +3844,87 @@ const historyReportForAppliedPlan = (
|
|
|
3260
3844
|
}),
|
|
3261
3845
|
});
|
|
3262
3846
|
|
|
3847
|
+
/**
|
|
3848
|
+
* Re-read the active plan after preparation and before mutation.
|
|
3849
|
+
*
|
|
3850
|
+
* @internal
|
|
3851
|
+
*/
|
|
3852
|
+
export const reloadPreparedRegradePlan = async (params: {
|
|
3853
|
+
readonly input: RegradeApplyPlanInput;
|
|
3854
|
+
readonly loaded: {
|
|
3855
|
+
readonly artifact: RegradePlanArtifact;
|
|
3856
|
+
readonly path: string;
|
|
3857
|
+
};
|
|
3858
|
+
readonly rootDir: string;
|
|
3859
|
+
}): Promise<TrailsResult<RegradePlanArtifact, Error>> => {
|
|
3860
|
+
const current = await loadPlanForInput(params.input, params.rootDir);
|
|
3861
|
+
if (current.isErr()) {
|
|
3862
|
+
return current;
|
|
3863
|
+
}
|
|
3864
|
+
const unchanged = validatePreparedRegradePlanArtifact({
|
|
3865
|
+
current: current.value.artifact,
|
|
3866
|
+
currentPath: current.value.path,
|
|
3867
|
+
expected: params.loaded.artifact,
|
|
3868
|
+
expectedPath: params.loaded.path,
|
|
3869
|
+
});
|
|
3870
|
+
return unchanged.isErr() ? unchanged : Result.ok(current.value.artifact);
|
|
3871
|
+
};
|
|
3872
|
+
|
|
3873
|
+
const applyPreparedPlanRun = async (params: {
|
|
3874
|
+
readonly artifact: RegradePlanArtifact;
|
|
3875
|
+
readonly includeEntries: RegradeInput['includeEntries'];
|
|
3876
|
+
readonly prepared: PreparedPlanRun;
|
|
3877
|
+
readonly rootDir: string;
|
|
3878
|
+
}): Promise<TrailsResult<RegradeReport, Error>> => {
|
|
3879
|
+
if (params.artifact.plan.kind === 'class') {
|
|
3880
|
+
if (params.prepared.kind !== 'class') {
|
|
3881
|
+
return Result.err(new InternalError('Prepared Regrade kind changed.'));
|
|
3882
|
+
}
|
|
3883
|
+
const classSet = await loadWardenRegradeClasses(params.rootDir);
|
|
3884
|
+
if (classSet.diagnostics.length > 0) {
|
|
3885
|
+
return Result.err(
|
|
3886
|
+
new InternalError('Failed to load Regrade project Warden rules.', {
|
|
3887
|
+
context: {
|
|
3888
|
+
diagnostics: classSet.diagnostics,
|
|
3889
|
+
rootDir: params.rootDir,
|
|
3890
|
+
},
|
|
3891
|
+
})
|
|
3892
|
+
);
|
|
3893
|
+
}
|
|
3894
|
+
const identity = preparedRegradeRunIdentity({
|
|
3895
|
+
artifact: params.artifact,
|
|
3896
|
+
classIds: classSet.classes.map((regradeClass) => regradeClass.id),
|
|
3897
|
+
classes: classSet.classes,
|
|
3898
|
+
includeEntries: params.includeEntries,
|
|
3899
|
+
rootDir: params.rootDir,
|
|
3900
|
+
});
|
|
3901
|
+
return identity.isErr()
|
|
3902
|
+
? identity
|
|
3903
|
+
: applyPreparedRegradeRun(params.prepared.prepared.run, identity.value);
|
|
3904
|
+
}
|
|
3905
|
+
if (params.prepared.kind !== 'vocabulary') {
|
|
3906
|
+
return Result.err(new InternalError('Prepared Regrade kind changed.'));
|
|
3907
|
+
}
|
|
3908
|
+
const identity = preparedRegradeRunIdentity({
|
|
3909
|
+
artifact: params.artifact,
|
|
3910
|
+
includeEntries: params.includeEntries,
|
|
3911
|
+
rootDir: params.rootDir,
|
|
3912
|
+
});
|
|
3913
|
+
if (identity.isErr()) {
|
|
3914
|
+
return identity;
|
|
3915
|
+
}
|
|
3916
|
+
const { prepared } = params.prepared;
|
|
3917
|
+
return runResolvedVocabularyPlan({
|
|
3918
|
+
apply: true,
|
|
3919
|
+
currentIdentity: identity.value,
|
|
3920
|
+
includeEntries: params.includeEntries,
|
|
3921
|
+
plan: params.artifact.plan,
|
|
3922
|
+
prepared,
|
|
3923
|
+
preserveInventory: prepared.preserveInventory,
|
|
3924
|
+
rootDir: params.rootDir,
|
|
3925
|
+
});
|
|
3926
|
+
};
|
|
3927
|
+
|
|
3263
3928
|
const runApplyRegradePlan = async (
|
|
3264
3929
|
input: RegradeApplyPlanInput,
|
|
3265
3930
|
rootDir: string,
|
|
@@ -3275,17 +3940,18 @@ const runApplyRegradePlan = async (
|
|
|
3275
3940
|
if (governedPlanValidation.isErr()) {
|
|
3276
3941
|
return governedPlanValidation;
|
|
3277
3942
|
}
|
|
3278
|
-
const
|
|
3943
|
+
const preparedRun = await preparePlanArtifactRun({
|
|
3279
3944
|
artifact: loaded.value.artifact,
|
|
3280
3945
|
includeEntries: input.includeEntries,
|
|
3281
3946
|
rootDir,
|
|
3282
3947
|
});
|
|
3283
|
-
if (
|
|
3284
|
-
return
|
|
3948
|
+
if (preparedRun.isErr()) {
|
|
3949
|
+
return preparedRun;
|
|
3285
3950
|
}
|
|
3951
|
+
const dryRunReport = preparedRun.value.report;
|
|
3286
3952
|
const status = planStatusForReport(
|
|
3287
3953
|
loaded.value.artifact,
|
|
3288
|
-
dryRunReport
|
|
3954
|
+
dryRunReport,
|
|
3289
3955
|
rootDir
|
|
3290
3956
|
);
|
|
3291
3957
|
if (status === 'stale') {
|
|
@@ -3300,62 +3966,99 @@ const runApplyRegradePlan = async (
|
|
|
3300
3966
|
}
|
|
3301
3967
|
if (shouldDryRun) {
|
|
3302
3968
|
return validateRegradeReport(
|
|
3303
|
-
reportWithPlanSummary(dryRunReport
|
|
3969
|
+
reportWithPlanSummary(dryRunReport, loaded.value.artifact, status)
|
|
3304
3970
|
);
|
|
3305
3971
|
}
|
|
3306
3972
|
|
|
3307
|
-
const
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
includeEntries: input.includeEntries,
|
|
3327
|
-
plan: planBody,
|
|
3328
|
-
preserveInventory: preserveResult.value,
|
|
3329
|
-
rootDir,
|
|
3330
|
-
});
|
|
3973
|
+
const currentPlan = await reloadPreparedRegradePlan({
|
|
3974
|
+
input,
|
|
3975
|
+
loaded: loaded.value,
|
|
3976
|
+
rootDir,
|
|
3977
|
+
});
|
|
3978
|
+
if (currentPlan.isErr()) {
|
|
3979
|
+
return currentPlan;
|
|
3980
|
+
}
|
|
3981
|
+
const activeArtifact = currentPlan.value;
|
|
3982
|
+
|
|
3983
|
+
// Receipt persistence is mandatory for apply. Resolve its Git-owned source
|
|
3984
|
+
// identity before mutating any source so failure leaves the tree untouched.
|
|
3985
|
+
const receiptPlan = validateRegradeReceiptPlan(activeArtifact);
|
|
3986
|
+
if (receiptPlan.isErr()) {
|
|
3987
|
+
return receiptPlan;
|
|
3988
|
+
}
|
|
3989
|
+
const sourceRevision = resolveRegradeSourceRevision(rootDir);
|
|
3990
|
+
if (sourceRevision.isErr()) {
|
|
3991
|
+
return sourceRevision;
|
|
3331
3992
|
}
|
|
3993
|
+
|
|
3994
|
+
const beforeChangedFiles = captureRegradeChangedFilesBefore({
|
|
3995
|
+
artifact: activeArtifact,
|
|
3996
|
+
report: dryRunReport,
|
|
3997
|
+
rootDir,
|
|
3998
|
+
});
|
|
3999
|
+
if (beforeChangedFiles.isErr()) {
|
|
4000
|
+
return beforeChangedFiles;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
const planBody = activeArtifact.plan;
|
|
4004
|
+
const sourceSnapshots = snapshotRegradeSources({
|
|
4005
|
+
optionalPaths:
|
|
4006
|
+
planBody.kind === 'vocabulary'
|
|
4007
|
+
? (planBody.fileRenames ?? []).flatMap((rename) => [
|
|
4008
|
+
rename.from,
|
|
4009
|
+
rename.to,
|
|
4010
|
+
])
|
|
4011
|
+
: [],
|
|
4012
|
+
reports: [dryRunReport],
|
|
4013
|
+
rootDir,
|
|
4014
|
+
});
|
|
4015
|
+
if (sourceSnapshots.isErr()) {
|
|
4016
|
+
return sourceSnapshots;
|
|
4017
|
+
}
|
|
4018
|
+
const rollbackApplyError = (error: Error): TrailsResult<never, Error> =>
|
|
4019
|
+
regradeApplyErrorAfterRollback(error, sourceSnapshots.value);
|
|
4020
|
+
const applied = await applyPreparedPlanRun({
|
|
4021
|
+
artifact: activeArtifact,
|
|
4022
|
+
includeEntries: input.includeEntries,
|
|
4023
|
+
prepared: preparedRun.value.prepared,
|
|
4024
|
+
rootDir,
|
|
4025
|
+
});
|
|
3332
4026
|
if (applied.isErr()) {
|
|
3333
|
-
return applied;
|
|
4027
|
+
return rollbackApplyError(applied.error);
|
|
4028
|
+
}
|
|
4029
|
+
const changedFiles = completeRegradeChangedFiles({
|
|
4030
|
+
before: beforeChangedFiles.value,
|
|
4031
|
+
rootDir,
|
|
4032
|
+
});
|
|
4033
|
+
if (changedFiles.isErr()) {
|
|
4034
|
+
return rollbackApplyError(changedFiles.error);
|
|
3334
4035
|
}
|
|
3335
4036
|
const completionReport = await runPlanArtifactDryRun({
|
|
3336
|
-
artifact:
|
|
4037
|
+
artifact: activeArtifact,
|
|
3337
4038
|
includeEntries: input.includeEntries,
|
|
3338
4039
|
rootDir,
|
|
3339
4040
|
});
|
|
3340
4041
|
if (completionReport.isErr()) {
|
|
3341
|
-
return completionReport;
|
|
4042
|
+
return rollbackApplyError(completionReport.error);
|
|
3342
4043
|
}
|
|
3343
4044
|
// Keep the pre-apply occurrence evidence that explains what this run changed,
|
|
3344
4045
|
// while carrying the completed counters and a separate post-apply source
|
|
3345
4046
|
// stamp so a later no-op apply can still be recognized as a replay.
|
|
3346
4047
|
const history = writeRegradeHistory({
|
|
3347
|
-
artifact:
|
|
4048
|
+
artifact: activeArtifact,
|
|
4049
|
+
changedFiles: changedFiles.value,
|
|
3348
4050
|
completedReport: completionReport.value,
|
|
3349
4051
|
planPath: loaded.value.path,
|
|
3350
|
-
report: historyReportForAppliedPlan(dryRunReport
|
|
4052
|
+
report: historyReportForAppliedPlan(dryRunReport, applied.value),
|
|
3351
4053
|
rootDir,
|
|
4054
|
+
sourceRevision: sourceRevision.value,
|
|
3352
4055
|
});
|
|
3353
4056
|
if (history.isErr()) {
|
|
3354
|
-
return history;
|
|
4057
|
+
return rollbackApplyError(history.error);
|
|
3355
4058
|
}
|
|
3356
4059
|
return validateRegradeReport(
|
|
3357
4060
|
reportWithHistorySummary(
|
|
3358
|
-
reportWithPlanSummary(applied.value,
|
|
4061
|
+
reportWithPlanSummary(applied.value, activeArtifact, status),
|
|
3359
4062
|
history.value
|
|
3360
4063
|
)
|
|
3361
4064
|
);
|
|
@@ -3542,30 +4245,37 @@ export const planRegradeTrail = trail('plan.regrade', {
|
|
|
3542
4245
|
cli: { path: ['regrade', 'plan'] },
|
|
3543
4246
|
description: 'Write or update a reviewed Regrade plan',
|
|
3544
4247
|
implementation: async (input, ctx) => {
|
|
3545
|
-
const
|
|
4248
|
+
const lifecycle = new RegradeLifecycleTracker({ progress: ctx.progress });
|
|
4249
|
+
const rootDirResult = await lifecycle.run('resolve-root', () =>
|
|
4250
|
+
resolveTrailRootDir(input.rootDir, ctx.cwd)
|
|
4251
|
+
);
|
|
3546
4252
|
if (rootDirResult.isErr()) {
|
|
3547
|
-
return rootDirResult;
|
|
4253
|
+
return Result.err(rootDirResult.error);
|
|
3548
4254
|
}
|
|
3549
4255
|
|
|
3550
|
-
const configResult = await
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
4256
|
+
const configResult = await lifecycle.run('load-config', () =>
|
|
4257
|
+
loadRegradeConfig({
|
|
4258
|
+
...(input.configPath === undefined
|
|
4259
|
+
? {}
|
|
4260
|
+
: { configPath: input.configPath }),
|
|
4261
|
+
env: ctx.env,
|
|
4262
|
+
rootDir: rootDirResult.value,
|
|
4263
|
+
})
|
|
4264
|
+
);
|
|
3557
4265
|
if (configResult.isErr()) {
|
|
3558
|
-
return configResult;
|
|
4266
|
+
return Result.err(configResult.error);
|
|
3559
4267
|
}
|
|
3560
4268
|
|
|
3561
|
-
const result = await
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
4269
|
+
const result = await lifecycle.run('derive-plan', () =>
|
|
4270
|
+
runPlanRegrade(
|
|
4271
|
+
input,
|
|
4272
|
+
rootDirResult.value,
|
|
4273
|
+
configResult.value.config?.scope,
|
|
4274
|
+
ctx.dryRun === true
|
|
4275
|
+
)
|
|
3566
4276
|
);
|
|
3567
4277
|
if (result.isErr()) {
|
|
3568
|
-
return result;
|
|
4278
|
+
return Result.err(result.error);
|
|
3569
4279
|
}
|
|
3570
4280
|
const output = regradePlanArtifactSchema.safeParse(result.value);
|
|
3571
4281
|
if (!output.success) {
|
|
@@ -3575,11 +4285,11 @@ export const planRegradeTrail = trail('plan.regrade', {
|
|
|
3575
4285
|
})
|
|
3576
4286
|
);
|
|
3577
4287
|
}
|
|
3578
|
-
return Result.ok(output.data);
|
|
4288
|
+
return Result.ok({ ...output.data, lifecycle: lifecycle.summary() });
|
|
3579
4289
|
},
|
|
3580
4290
|
input: regradePlanInputSchema,
|
|
3581
4291
|
intent: 'write',
|
|
3582
|
-
output:
|
|
4292
|
+
output: regradePlanCommandOutputSchema,
|
|
3583
4293
|
permit: 'public',
|
|
3584
4294
|
});
|
|
3585
4295
|
|
|
@@ -3650,13 +4360,18 @@ export const checkRegradeTrail = trail('check.regrade', {
|
|
|
3650
4360
|
cli: { path: ['regrade', 'check'] },
|
|
3651
4361
|
description: 'Check a saved Regrade plan gate without writing source',
|
|
3652
4362
|
implementation: async (input, ctx) => {
|
|
3653
|
-
const
|
|
4363
|
+
const lifecycle = new RegradeLifecycleTracker({ progress: ctx.progress });
|
|
4364
|
+
const rootDirResult = await lifecycle.run('resolve-root', () =>
|
|
4365
|
+
resolveTrailRootDir(input.rootDir, ctx.cwd)
|
|
4366
|
+
);
|
|
3654
4367
|
if (rootDirResult.isErr()) {
|
|
3655
|
-
return rootDirResult;
|
|
4368
|
+
return Result.err(rootDirResult.error);
|
|
3656
4369
|
}
|
|
3657
|
-
const result = await
|
|
4370
|
+
const result = await lifecycle.run('check-plan', () =>
|
|
4371
|
+
runCheckRegradePlan(input, rootDirResult.value)
|
|
4372
|
+
);
|
|
3658
4373
|
if (result.isErr()) {
|
|
3659
|
-
return result;
|
|
4374
|
+
return Result.err(result.error);
|
|
3660
4375
|
}
|
|
3661
4376
|
const checked = {
|
|
3662
4377
|
...result.value,
|
|
@@ -3668,6 +4383,7 @@ export const checkRegradeTrail = trail('check.regrade', {
|
|
|
3668
4383
|
'',
|
|
3669
4384
|
status: 'passed' as const,
|
|
3670
4385
|
},
|
|
4386
|
+
lifecycle: lifecycle.summary(),
|
|
3671
4387
|
};
|
|
3672
4388
|
const output = validateOutput(regradeCheckOutputSchema, checked);
|
|
3673
4389
|
if (output.isErr()) {
|
|
@@ -3685,15 +4401,23 @@ export const previewRegradeTrail = trail('preview.regrade', {
|
|
|
3685
4401
|
cli: { path: ['regrade', 'preview'] },
|
|
3686
4402
|
description: 'Preview a saved Regrade plan without writing source',
|
|
3687
4403
|
implementation: async (input, ctx) => {
|
|
3688
|
-
const
|
|
4404
|
+
const lifecycle = new RegradeLifecycleTracker({ progress: ctx.progress });
|
|
4405
|
+
const rootDirResult = await lifecycle.run('resolve-root', () =>
|
|
4406
|
+
resolveTrailRootDir(input.rootDir, ctx.cwd)
|
|
4407
|
+
);
|
|
3689
4408
|
if (rootDirResult.isErr()) {
|
|
3690
|
-
return rootDirResult;
|
|
4409
|
+
return Result.err(rootDirResult.error);
|
|
3691
4410
|
}
|
|
3692
|
-
const result = await
|
|
4411
|
+
const result = await lifecycle.run('preview-plan', () =>
|
|
4412
|
+
runPreviewRegradePlan(input, rootDirResult.value)
|
|
4413
|
+
);
|
|
3693
4414
|
if (result.isErr()) {
|
|
3694
|
-
return result;
|
|
4415
|
+
return Result.err(result.error);
|
|
3695
4416
|
}
|
|
3696
|
-
const output = validateOutput(
|
|
4417
|
+
const output = validateOutput(regradeLifecycleReportOutputSchema, {
|
|
4418
|
+
...result.value,
|
|
4419
|
+
lifecycle: lifecycle.summary(),
|
|
4420
|
+
});
|
|
3697
4421
|
if (output.isErr()) {
|
|
3698
4422
|
return Result.err(output.error);
|
|
3699
4423
|
}
|
|
@@ -3701,7 +4425,7 @@ export const previewRegradeTrail = trail('preview.regrade', {
|
|
|
3701
4425
|
},
|
|
3702
4426
|
input: regradePlanReferenceInputSchema,
|
|
3703
4427
|
intent: 'read',
|
|
3704
|
-
output:
|
|
4428
|
+
output: regradeLifecycleReportOutputSchema,
|
|
3705
4429
|
permit: 'public',
|
|
3706
4430
|
});
|
|
3707
4431
|
|
|
@@ -3709,19 +4433,23 @@ export const applyRegradeTrail = trail('apply.regrade', {
|
|
|
3709
4433
|
cli: { path: ['regrade', 'apply'] },
|
|
3710
4434
|
description: 'Apply a saved Regrade plan and move it to history',
|
|
3711
4435
|
implementation: async (input, ctx) => {
|
|
3712
|
-
const
|
|
4436
|
+
const lifecycle = new RegradeLifecycleTracker({ progress: ctx.progress });
|
|
4437
|
+
const rootDirResult = await lifecycle.run('resolve-root', () =>
|
|
4438
|
+
resolveTrailRootDir(input.rootDir, ctx.cwd)
|
|
4439
|
+
);
|
|
3713
4440
|
if (rootDirResult.isErr()) {
|
|
3714
|
-
return rootDirResult;
|
|
4441
|
+
return Result.err(rootDirResult.error);
|
|
3715
4442
|
}
|
|
3716
|
-
const result = await
|
|
3717
|
-
input,
|
|
3718
|
-
rootDirResult.value,
|
|
3719
|
-
ctx.dryRun === true
|
|
4443
|
+
const result = await lifecycle.run('apply-plan', () =>
|
|
4444
|
+
runApplyRegradePlan(input, rootDirResult.value, ctx.dryRun === true)
|
|
3720
4445
|
);
|
|
3721
4446
|
if (result.isErr()) {
|
|
3722
|
-
return result;
|
|
4447
|
+
return Result.err(result.error);
|
|
3723
4448
|
}
|
|
3724
|
-
const output = validateOutput(
|
|
4449
|
+
const output = validateOutput(regradeLifecycleReportOutputSchema, {
|
|
4450
|
+
...result.value,
|
|
4451
|
+
lifecycle: lifecycle.summary(),
|
|
4452
|
+
});
|
|
3725
4453
|
if (output.isErr()) {
|
|
3726
4454
|
return Result.err(output.error);
|
|
3727
4455
|
}
|
|
@@ -3729,7 +4457,7 @@ export const applyRegradeTrail = trail('apply.regrade', {
|
|
|
3729
4457
|
},
|
|
3730
4458
|
input: regradeApplyPlanInputSchema,
|
|
3731
4459
|
intent: 'write',
|
|
3732
|
-
output:
|
|
4460
|
+
output: regradeLifecycleReportOutputSchema,
|
|
3733
4461
|
permit: 'public',
|
|
3734
4462
|
});
|
|
3735
4463
|
|
|
@@ -3739,17 +4467,18 @@ export const adjustRegradeTrail = trail('adjust.regrade', {
|
|
|
3739
4467
|
description:
|
|
3740
4468
|
'Pull a graduated Regrade transition back to an active plan for adjustment',
|
|
3741
4469
|
implementation: async (input, ctx) => {
|
|
3742
|
-
const
|
|
4470
|
+
const lifecycle = new RegradeLifecycleTracker({ progress: ctx.progress });
|
|
4471
|
+
const rootDirResult = await lifecycle.run('resolve-root', () =>
|
|
4472
|
+
resolveTrailRootDir(input.rootDir, ctx.cwd)
|
|
4473
|
+
);
|
|
3743
4474
|
if (rootDirResult.isErr()) {
|
|
3744
|
-
return rootDirResult;
|
|
4475
|
+
return Result.err(rootDirResult.error);
|
|
3745
4476
|
}
|
|
3746
|
-
const result = await
|
|
3747
|
-
input,
|
|
3748
|
-
rootDirResult.value,
|
|
3749
|
-
ctx.dryRun === true
|
|
4477
|
+
const result = await lifecycle.run('adjust-plan', () =>
|
|
4478
|
+
runAdjustRegrade(input, rootDirResult.value, ctx.dryRun === true)
|
|
3750
4479
|
);
|
|
3751
4480
|
if (result.isErr()) {
|
|
3752
|
-
return result;
|
|
4481
|
+
return Result.err(result.error);
|
|
3753
4482
|
}
|
|
3754
4483
|
const output = regradePlanArtifactSchema.safeParse(result.value);
|
|
3755
4484
|
if (!output.success) {
|
|
@@ -3759,10 +4488,10 @@ export const adjustRegradeTrail = trail('adjust.regrade', {
|
|
|
3759
4488
|
})
|
|
3760
4489
|
);
|
|
3761
4490
|
}
|
|
3762
|
-
return Result.ok(output.data);
|
|
4491
|
+
return Result.ok({ ...output.data, lifecycle: lifecycle.summary() });
|
|
3763
4492
|
},
|
|
3764
4493
|
input: regradeAdjustInputSchema,
|
|
3765
4494
|
intent: 'write',
|
|
3766
|
-
output:
|
|
4495
|
+
output: regradePlanCommandOutputSchema,
|
|
3767
4496
|
permit: 'public',
|
|
3768
4497
|
});
|