perttool 0.5.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/CHANGELOG.md +71 -1
  2. package/README.md +128 -30
  3. package/dist/application/advance-history.d.ts +48 -0
  4. package/dist/application/advance-history.d.ts.map +1 -0
  5. package/dist/application/advance-history.js +263 -0
  6. package/dist/application/advance-history.js.map +1 -0
  7. package/dist/application/contract6-mutation.d.ts +3 -2
  8. package/dist/application/contract6-mutation.d.ts.map +1 -1
  9. package/dist/application/contract6-mutation.js +2 -1
  10. package/dist/application/contract6-mutation.js.map +1 -1
  11. package/dist/application/contract6-projection.d.ts +2 -1
  12. package/dist/application/contract6-projection.d.ts.map +1 -1
  13. package/dist/application/contract6-projection.js +31 -1
  14. package/dist/application/contract6-projection.js.map +1 -1
  15. package/dist/application/contract7-assurance.d.ts +57 -0
  16. package/dist/application/contract7-assurance.d.ts.map +1 -0
  17. package/dist/application/contract7-assurance.js +248 -0
  18. package/dist/application/contract7-assurance.js.map +1 -0
  19. package/dist/application/contract7-mermaid.d.ts +9 -0
  20. package/dist/application/contract7-mermaid.d.ts.map +1 -0
  21. package/dist/application/contract7-mermaid.js +73 -0
  22. package/dist/application/contract7-mermaid.js.map +1 -0
  23. package/dist/application/contract7-mutation.d.ts +21 -0
  24. package/dist/application/contract7-mutation.d.ts.map +1 -0
  25. package/dist/application/contract7-mutation.js +181 -0
  26. package/dist/application/contract7-mutation.js.map +1 -0
  27. package/dist/application/contract7-project.d.ts +10 -0
  28. package/dist/application/contract7-project.d.ts.map +1 -0
  29. package/dist/application/contract7-project.js +29 -0
  30. package/dist/application/contract7-project.js.map +1 -0
  31. package/dist/application/contract7-projection.d.ts +9 -0
  32. package/dist/application/contract7-projection.d.ts.map +1 -0
  33. package/dist/application/contract7-projection.js +100 -0
  34. package/dist/application/contract7-projection.js.map +1 -0
  35. package/dist/application/contract7-source.d.ts +24 -0
  36. package/dist/application/contract7-source.d.ts.map +1 -0
  37. package/dist/application/contract7-source.js +54 -0
  38. package/dist/application/contract7-source.js.map +1 -0
  39. package/dist/application/contract7-unit-migration.d.ts +13 -0
  40. package/dist/application/contract7-unit-migration.d.ts.map +1 -0
  41. package/dist/application/contract7-unit-migration.js +13 -0
  42. package/dist/application/contract7-unit-migration.js.map +1 -0
  43. package/dist/application/init.d.ts +1 -1
  44. package/dist/application/init.js +3 -3
  45. package/dist/application/mutate.d.ts +5 -1
  46. package/dist/application/mutate.d.ts.map +1 -1
  47. package/dist/application/mutate.js +2 -2
  48. package/dist/application/mutate.js.map +1 -1
  49. package/dist/application/target-actuals-analysis.d.ts +5 -4
  50. package/dist/application/target-actuals-analysis.d.ts.map +1 -1
  51. package/dist/application/target-actuals-analysis.js +18 -11
  52. package/dist/application/target-actuals-analysis.js.map +1 -1
  53. package/dist/application/target-actuals-mutation.d.ts +5 -3
  54. package/dist/application/target-actuals-mutation.d.ts.map +1 -1
  55. package/dist/application/target-actuals-mutation.js +16 -8
  56. package/dist/application/target-actuals-mutation.js.map +1 -1
  57. package/dist/application/target-actuals-write.d.ts +2 -1
  58. package/dist/application/target-actuals-write.d.ts.map +1 -1
  59. package/dist/application/target-actuals-write.js.map +1 -1
  60. package/dist/application/target-assurance-advance-history.d.ts +19 -0
  61. package/dist/application/target-assurance-advance-history.d.ts.map +1 -0
  62. package/dist/application/target-assurance-advance-history.js +61 -0
  63. package/dist/application/target-assurance-advance-history.js.map +1 -0
  64. package/dist/application/target-assurance-analysis.d.ts +34 -0
  65. package/dist/application/target-assurance-analysis.d.ts.map +1 -0
  66. package/dist/application/target-assurance-analysis.js +103 -0
  67. package/dist/application/target-assurance-analysis.js.map +1 -0
  68. package/dist/application/target-assurance-inspection.d.ts +38 -0
  69. package/dist/application/target-assurance-inspection.d.ts.map +1 -0
  70. package/dist/application/target-assurance-inspection.js +356 -0
  71. package/dist/application/target-assurance-inspection.js.map +1 -0
  72. package/dist/application/target-assurance-write.d.ts +22 -0
  73. package/dist/application/target-assurance-write.d.ts.map +1 -0
  74. package/dist/application/target-assurance-write.js +73 -0
  75. package/dist/application/target-assurance-write.js.map +1 -0
  76. package/dist/application/target-governance-init.d.ts +3 -3
  77. package/dist/application/target-governance-init.d.ts.map +1 -1
  78. package/dist/application/target-governance-init.js +14 -10
  79. package/dist/application/target-governance-init.js.map +1 -1
  80. package/dist/application/target-governance-project.d.ts +6 -4
  81. package/dist/application/target-governance-project.d.ts.map +1 -1
  82. package/dist/application/target-governance-project.js +18 -4
  83. package/dist/application/target-governance-project.js.map +1 -1
  84. package/dist/application/target-mutate.d.ts +7 -1
  85. package/dist/application/target-mutate.d.ts.map +1 -1
  86. package/dist/application/target-mutate.js +50 -3
  87. package/dist/application/target-mutate.js.map +1 -1
  88. package/dist/application/target-project-history.d.ts +4 -4
  89. package/dist/application/target-project-history.d.ts.map +1 -1
  90. package/dist/application/target-project-history.js +1 -1
  91. package/dist/application/target-project-history.js.map +1 -1
  92. package/dist/application/target-unit-migration-candidate.d.ts +29 -6
  93. package/dist/application/target-unit-migration-candidate.d.ts.map +1 -1
  94. package/dist/application/target-unit-migration-candidate.js +204 -3
  95. package/dist/application/target-unit-migration-candidate.js.map +1 -1
  96. package/dist/application/target-unit-migration-result.d.ts +2 -2
  97. package/dist/application/target-unit-migration-result.d.ts.map +1 -1
  98. package/dist/application/target-unit-migration-result.js +4 -2
  99. package/dist/application/target-unit-migration-result.js.map +1 -1
  100. package/dist/application/target-velocity-observation.d.ts +1 -1
  101. package/dist/application/target-velocity-observation.js +1 -1
  102. package/dist/assurance/advance.d.ts +45 -0
  103. package/dist/assurance/advance.d.ts.map +1 -0
  104. package/dist/assurance/advance.js +507 -0
  105. package/dist/assurance/advance.js.map +1 -0
  106. package/dist/assurance/authority.d.ts +86 -0
  107. package/dist/assurance/authority.d.ts.map +1 -0
  108. package/dist/assurance/authority.js +269 -0
  109. package/dist/assurance/authority.js.map +1 -0
  110. package/dist/assurance/canonical.d.ts +13 -0
  111. package/dist/assurance/canonical.d.ts.map +1 -0
  112. package/dist/assurance/canonical.js +320 -0
  113. package/dist/assurance/canonical.js.map +1 -0
  114. package/dist/assurance/compatibility.d.ts +62 -0
  115. package/dist/assurance/compatibility.d.ts.map +1 -0
  116. package/dist/assurance/compatibility.js +163 -0
  117. package/dist/assurance/compatibility.js.map +1 -0
  118. package/dist/assurance/evaluate.d.ts +4 -0
  119. package/dist/assurance/evaluate.d.ts.map +1 -0
  120. package/dist/assurance/evaluate.js +631 -0
  121. package/dist/assurance/evaluate.js.map +1 -0
  122. package/dist/assurance/governance.d.ts +42 -0
  123. package/dist/assurance/governance.d.ts.map +1 -0
  124. package/dist/assurance/governance.js +95 -0
  125. package/dist/assurance/governance.js.map +1 -0
  126. package/dist/assurance/mermaid.d.ts +40 -0
  127. package/dist/assurance/mermaid.d.ts.map +1 -0
  128. package/dist/assurance/mermaid.js +401 -0
  129. package/dist/assurance/mermaid.js.map +1 -0
  130. package/dist/assurance/mutation.d.ts +96 -0
  131. package/dist/assurance/mutation.d.ts.map +1 -0
  132. package/dist/assurance/mutation.js +757 -0
  133. package/dist/assurance/mutation.js.map +1 -0
  134. package/dist/assurance/source.d.ts +20 -0
  135. package/dist/assurance/source.d.ts.map +1 -0
  136. package/dist/assurance/source.js +313 -0
  137. package/dist/assurance/source.js.map +1 -0
  138. package/dist/assurance/types.d.ts +166 -0
  139. package/dist/assurance/types.d.ts.map +1 -0
  140. package/dist/assurance/types.js +3 -0
  141. package/dist/assurance/types.js.map +1 -0
  142. package/dist/cli.js +431 -72
  143. package/dist/cli.js.map +1 -1
  144. package/dist/command/actuals-discovery.d.ts.map +1 -1
  145. package/dist/command/actuals-discovery.js +50 -4
  146. package/dist/command/actuals-discovery.js.map +1 -1
  147. package/dist/command/assurance-discovery.d.ts +17 -0
  148. package/dist/command/assurance-discovery.d.ts.map +1 -0
  149. package/dist/command/assurance-discovery.js +287 -0
  150. package/dist/command/assurance-discovery.js.map +1 -0
  151. package/dist/command/assurance-usage.d.ts +20 -0
  152. package/dist/command/assurance-usage.d.ts.map +1 -0
  153. package/dist/command/assurance-usage.js +66 -0
  154. package/dist/command/assurance-usage.js.map +1 -0
  155. package/dist/conversion/mermaid.d.ts +2 -1
  156. package/dist/conversion/mermaid.d.ts.map +1 -1
  157. package/dist/conversion/mermaid.js +2 -2
  158. package/dist/conversion/mermaid.js.map +1 -1
  159. package/dist/formatter/source-formatter.d.ts +1 -1
  160. package/dist/formatter/source-formatter.d.ts.map +1 -1
  161. package/dist/formatter/source-formatter.js +56 -4
  162. package/dist/formatter/source-formatter.js.map +1 -1
  163. package/dist/formatter/target-source-formatter.d.ts +2 -1
  164. package/dist/formatter/target-source-formatter.d.ts.map +1 -1
  165. package/dist/formatter/target-source-formatter.js +18 -2
  166. package/dist/formatter/target-source-formatter.js.map +1 -1
  167. package/dist/help/assurance-guide.d.ts +10 -0
  168. package/dist/help/assurance-guide.d.ts.map +1 -0
  169. package/dist/help/assurance-guide.js +115 -0
  170. package/dist/help/assurance-guide.js.map +1 -0
  171. package/dist/help/guide.d.ts +1 -1
  172. package/dist/help/guide.d.ts.map +1 -1
  173. package/dist/help/registry.d.ts.map +1 -1
  174. package/dist/help/registry.js +11 -1
  175. package/dist/help/registry.js.map +1 -1
  176. package/dist/history/advance-history.d.ts +46 -0
  177. package/dist/history/advance-history.d.ts.map +1 -0
  178. package/dist/history/advance-history.js +425 -0
  179. package/dist/history/advance-history.js.map +1 -0
  180. package/dist/history/git-probe.d.ts +40 -0
  181. package/dist/history/git-probe.d.ts.map +1 -1
  182. package/dist/history/git-probe.js +330 -0
  183. package/dist/history/git-probe.js.map +1 -1
  184. package/dist/history/project-history.d.ts +14 -5
  185. package/dist/history/project-history.d.ts.map +1 -1
  186. package/dist/history/project-history.js +25 -18
  187. package/dist/history/project-history.js.map +1 -1
  188. package/dist/index.d.ts +31 -25
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +12 -14
  191. package/dist/index.js.map +1 -1
  192. package/dist/io/target-safe-write.d.ts +4 -1
  193. package/dist/io/target-safe-write.d.ts.map +1 -1
  194. package/dist/io/target-safe-write.js +19 -1
  195. package/dist/io/target-safe-write.js.map +1 -1
  196. package/dist/migration/grammar-boundary.d.ts +1 -1
  197. package/dist/migration/grammar-boundary.d.ts.map +1 -1
  198. package/dist/migration/grammar-boundary.js +7 -3
  199. package/dist/migration/grammar-boundary.js.map +1 -1
  200. package/dist/model/declaration-fields.d.ts +12 -0
  201. package/dist/model/declaration-fields.d.ts.map +1 -1
  202. package/dist/model/declaration-fields.js +45 -0
  203. package/dist/model/declaration-fields.js.map +1 -1
  204. package/dist/model/syntax.d.ts +17 -1
  205. package/dist/model/syntax.d.ts.map +1 -1
  206. package/dist/model/syntax.js.map +1 -1
  207. package/dist/mutation/actuals.js +1 -1
  208. package/dist/mutation/actuals.js.map +1 -1
  209. package/dist/mutation/advance-deletion.d.ts +14 -0
  210. package/dist/mutation/advance-deletion.d.ts.map +1 -0
  211. package/dist/mutation/advance-deletion.js +53 -0
  212. package/dist/mutation/advance-deletion.js.map +1 -0
  213. package/dist/mutation/advance.d.ts +18 -1
  214. package/dist/mutation/advance.d.ts.map +1 -1
  215. package/dist/mutation/advance.js +28 -25
  216. package/dist/mutation/advance.js.map +1 -1
  217. package/dist/mutation/project.d.ts +2 -1
  218. package/dist/mutation/project.d.ts.map +1 -1
  219. package/dist/mutation/project.js +21 -0
  220. package/dist/mutation/project.js.map +1 -1
  221. package/dist/parser/document-parser.d.ts +7 -0
  222. package/dist/parser/document-parser.d.ts.map +1 -1
  223. package/dist/parser/document-parser.js +227 -12
  224. package/dist/parser/document-parser.js.map +1 -1
  225. package/dist/recommendation/override-types.d.ts +2 -2
  226. package/dist/recommendation/override.d.ts +2 -2
  227. package/dist/recommendation/override.d.ts.map +1 -1
  228. package/dist/recommendation/override.js +13 -5
  229. package/dist/recommendation/override.js.map +1 -1
  230. package/dist/schema/registry.d.ts +1 -1
  231. package/dist/schema/registry.d.ts.map +1 -1
  232. package/dist/schema/registry.js +8 -6
  233. package/dist/schema/registry.js.map +1 -1
  234. package/dist/semantic/target-validator.d.ts +12 -1
  235. package/dist/semantic/target-validator.d.ts.map +1 -1
  236. package/dist/semantic/target-validator.js +47 -2
  237. package/dist/semantic/target-validator.js.map +1 -1
  238. package/dist/semantic/validator.d.ts +1 -0
  239. package/dist/semantic/validator.d.ts.map +1 -1
  240. package/dist/semantic/validator.js +304 -0
  241. package/dist/semantic/validator.js.map +1 -1
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +1 -1
  244. package/package.json +1 -1
  245. package/schemas/Perttool.AdvanceResult.v2.schema.json +334 -0
  246. package/schemas/Perttool.AgentGuidanceResult.v1.schema.json +1 -1
  247. package/schemas/{Perttool.AnalysisResult.v4.schema.json → Perttool.AnalysisResult.v5.schema.json} +10 -6
  248. package/schemas/{Perttool.CheckResult.v3.schema.json → Perttool.CheckResult.v4.schema.json} +14 -6
  249. package/schemas/Perttool.CliError.v1.schema.json +1 -1
  250. package/schemas/Perttool.CommandHelpResult.v1.schema.json +2 -2
  251. package/schemas/Perttool.Common.v1.schema.json +300 -4
  252. package/schemas/Perttool.ExportResult.v1.schema.json +1 -1
  253. package/schemas/Perttool.FormatResult.v1.schema.json +1 -1
  254. package/schemas/Perttool.GuideResult.v1.schema.json +1 -1
  255. package/schemas/Perttool.ImportResult.v1.schema.json +1 -1
  256. package/schemas/Perttool.InitResult.v1.schema.json +1 -1
  257. package/schemas/{Perttool.MutationResult.v3.schema.json → Perttool.MutationResult.v4.schema.json} +16 -5
  258. package/schemas/{Perttool.NextResult.v5.schema.json → Perttool.NextResult.v6.schema.json} +53 -8
  259. package/schemas/Perttool.OverrideDecision.v1.schema.json +1 -1
  260. package/schemas/Perttool.PlanAssuranceResult.v1.schema.json +77 -0
  261. package/schemas/Perttool.ProjectHistoryResult.v1.schema.json +1 -1
  262. package/schemas/{Perttool.ProjectResult.v3.schema.json → Perttool.ProjectResult.v4.schema.json} +15 -7
  263. package/schemas/Perttool.SchemaResult.v1.schema.json +1 -1
  264. package/schemas/Perttool.UnitMigrationResult.v3.schema.json +5 -3
  265. package/schemas/Perttool.VelocityObservationResult.v1.schema.json +1 -1
@@ -0,0 +1,757 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createUnifiedDiff } from "../editing/unified-diff.js";
3
+ import { governanceMetadataFromDocument } from "../governance/source.js";
4
+ import { classifyGovernanceScopes } from "../governance/authority.js";
5
+ import { limitDiagnostics, normalizeMaxDiagnostics, sortDiagnostics, } from "../model/diagnostics.js";
6
+ import { TARGET_GRAMMAR_6_DECLARATION_FIELD_ORDER, } from "../model/declaration-fields.js";
7
+ import { fieldNamed } from "../model/syntax.js";
8
+ import { EntityEditor } from "../mutation/entity-editor.js";
9
+ import { appendDeclarationEdit, contentTextEndOffset, deleteDeclarationEdit, leadingCommentStart, majorLineEnding, serializeTextField, splitPhysicalLines, } from "../mutation/source.js";
10
+ import { applyTextEdits, normalizeTextEdits, } from "../mutation/text-edits.js";
11
+ import { validateTargetGrammar6Document, } from "../semantic/target-validator.js";
12
+ import { evaluatePlanAssurance, sealTaskResult, } from "./evaluate.js";
13
+ import { composePlanAssuranceMutationImpact, } from "./authority.js";
14
+ import { evaluatePlanAssuranceGovernance, normalizePlanAssuranceGovernanceRequest, planAssuranceGovernanceDiagnostics, } from "./governance.js";
15
+ import { mergeBatchInsertions, } from "../application/mutate.js";
16
+ import { planTargetGrammar6AtomicMutationEdits, planTargetGrammar6BatchMutation, } from "../application/target-mutate.js";
17
+ import { projectPlanAssuranceInput } from "./source.js";
18
+ function mutationAffectsAssuranceSource(mutation) {
19
+ if (mutation.kind !== "batch")
20
+ return true;
21
+ return mutation.mutations.some((item) => item.kind.startsWith("plan_assurance.") ||
22
+ item.kind.startsWith("plan_dependency.") ||
23
+ item.kind.startsWith("task_outcome."));
24
+ }
25
+ const requestFieldsByKind = Object.freeze({
26
+ "plan_dependency.add": new Set([
27
+ "kind", "id", "predecessorTaskId", "successorTaskId", "mode", "reason",
28
+ ]),
29
+ "plan_dependency.set": new Set([
30
+ "kind", "id", "predecessorTaskId", "successorTaskId", "mode", "reason",
31
+ "clearReason",
32
+ ]),
33
+ "plan_dependency.remove": new Set(["kind", "id"]),
34
+ "plan_assurance.seal": new Set(["kind", "reason"]),
35
+ "plan_assurance.reseal": new Set(["kind", "taskIds", "reason"]),
36
+ "task_outcome.add": new Set([
37
+ "kind", "id", "taskId", "status", "summary", "reason",
38
+ ]),
39
+ "task_outcome.set": new Set([
40
+ "kind", "id", "status", "summary", "clearSummary", "reason",
41
+ "rebindCurrentBasis",
42
+ ]),
43
+ "task_outcome.remove": new Set(["kind", "id"]),
44
+ batch: new Set(["kind", "mutations"]),
45
+ });
46
+ function mutationRequestShapeError(value, nested = false) {
47
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
48
+ return "assurance mutation request must be an object";
49
+ }
50
+ const request = value;
51
+ const kind = request["kind"];
52
+ const ordinaryKind = typeof kind === "string" && (kind === "project.set" ||
53
+ kind.startsWith("task.") ||
54
+ kind.startsWith("gate.") ||
55
+ kind.startsWith("milestone.") ||
56
+ kind.startsWith("resource."));
57
+ if (typeof kind !== "string" ||
58
+ (requestFieldsByKind[kind] === undefined && !(nested && ordinaryKind))) {
59
+ return "assurance mutation kind is unsupported";
60
+ }
61
+ if (nested && ordinaryKind && requestFieldsByKind[kind] === undefined) {
62
+ return null;
63
+ }
64
+ if (nested && kind === "batch")
65
+ return "assurance batch cannot be nested";
66
+ if (Object.keys(request).some((field) => !requestFieldsByKind[kind].has(field))) {
67
+ return `${kind} request contains unsupported fields`;
68
+ }
69
+ if (kind === "batch") {
70
+ if (!Array.isArray(request["mutations"]) || request["mutations"].length === 0) {
71
+ return "assurance batch requires at least one mutation";
72
+ }
73
+ for (const item of request["mutations"]) {
74
+ const error = mutationRequestShapeError(item, true);
75
+ if (error !== null)
76
+ return error;
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+ const relationFieldOrder = ["mode", "reason"];
82
+ const sealFieldOrder = [
83
+ "accepted_contract",
84
+ "accepted_basis",
85
+ "accepted_inputs",
86
+ "reason",
87
+ ];
88
+ const outcomeFieldOrder = [
89
+ "model",
90
+ "task",
91
+ "against_basis",
92
+ "status",
93
+ "summary",
94
+ "reason",
95
+ ];
96
+ function digest(text) {
97
+ return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
98
+ }
99
+ function assuranceDiagnostic(code, severity, message, entityId = null, data = {}) {
100
+ return Object.freeze({
101
+ code,
102
+ severity,
103
+ message,
104
+ ...(entityId === null ? {} : { entityId }),
105
+ helpTopic: "plan-assurance",
106
+ data: Object.freeze(data),
107
+ });
108
+ }
109
+ function failure(text, documentId, diagnostics, maximum, alreadyTruncated = false) {
110
+ const limited = limitDiagnostics(sortDiagnostics(diagnostics), maximum);
111
+ return Object.freeze({
112
+ schemaVersion: "Perttool.MutationResult.v4",
113
+ ok: false,
114
+ documentId,
115
+ changed: false,
116
+ originalDigest: digest(text),
117
+ updatedDigest: null,
118
+ updatedText: null,
119
+ diff: null,
120
+ edits: Object.freeze([]),
121
+ governance: null,
122
+ assuranceImpact: null,
123
+ diagnostics: limited.diagnostics,
124
+ diagnosticsTruncated: alreadyTruncated || limited.truncated,
125
+ });
126
+ }
127
+ function entity(document, id) {
128
+ return document.declarations.find((declaration) => declaration.id === id);
129
+ }
130
+ function declarationOfKind(document, kind, id) {
131
+ return document.declarations.find((declaration) => declaration.kind === kind && declaration.id === id);
132
+ }
133
+ function nonemptyString(value) {
134
+ return typeof value === "string" && value.length > 0;
135
+ }
136
+ function validMode(value) {
137
+ return value === "both" || value === "execution_only" || value === "planning_only";
138
+ }
139
+ function insertDeclarations(text, document, serialized, beforeKinds) {
140
+ const lineEnding = majorLineEnding(text);
141
+ const later = document.declarations.find((declaration) => beforeKinds.has(declaration.kind));
142
+ if (later === undefined) {
143
+ return appendDeclarationEdit(text, serialized.join(`${lineEnding}${lineEnding}`), lineEnding);
144
+ }
145
+ const lines = splitPhysicalLines(text);
146
+ const offset = leadingCommentStart(lines, later.headerSpan.start.offset, 0);
147
+ return {
148
+ startOffset: offset,
149
+ endOffset: offset,
150
+ replacement: `${serialized.join(`${lineEnding}${lineEnding}`)}${lineEnding}${lineEnding}`,
151
+ };
152
+ }
153
+ function serializeAcceptedInputs(inputs, lineEnding) {
154
+ return [
155
+ " accepted_inputs:",
156
+ ...inputs.map((input) => ` ${input.predecessorTaskId} ${input.relationMode} ${input.assuranceHash}`),
157
+ ].join(lineEnding);
158
+ }
159
+ function serializeSeal(taskId, seal, reason, lineEnding) {
160
+ return [
161
+ `plan_seal ${taskId}:`,
162
+ ` accepted_contract ${seal.acceptedContractHash}`,
163
+ ` accepted_basis ${seal.acceptedBasisHash}`,
164
+ ...(seal.acceptedInputs.length === 0
165
+ ? []
166
+ : [serializeAcceptedInputs(seal.acceptedInputs, lineEnding)]),
167
+ serializeTextField("reason", reason, lineEnding),
168
+ ].join(lineEnding);
169
+ }
170
+ function planSealRecordEdits(text, declaration, seal, reason) {
171
+ const acceptedInputs = fieldNamed(declaration, "accepted_inputs");
172
+ const editor = new EntityEditor(text, declaration, sealFieldOrder, seal.acceptedInputs.length === 0 ? ["accepted_inputs"] : []);
173
+ editor.setScalar("accepted_contract", seal.acceptedContractHash);
174
+ editor.setScalar("accepted_basis", seal.acceptedBasisHash);
175
+ editor.setText("reason", reason);
176
+ const edits = [...editor.finish()];
177
+ if (seal.acceptedInputs.length > 0) {
178
+ const serialized = serializeAcceptedInputs(seal.acceptedInputs, editor.lineEnding);
179
+ if (acceptedInputs === undefined) {
180
+ const inputEditor = new EntityEditor(text, declaration, sealFieldOrder);
181
+ inputEditor.queue("accepted_inputs", serialized);
182
+ edits.push(...inputEditor.finish());
183
+ }
184
+ else {
185
+ edits.push({
186
+ startOffset: acceptedInputs.span.start.offset,
187
+ endOffset: contentTextEndOffset(acceptedInputs, splitPhysicalLines(text)),
188
+ replacement: serialized,
189
+ });
190
+ }
191
+ }
192
+ return edits;
193
+ }
194
+ function serializeRelation(mutation, lineEnding) {
195
+ return [
196
+ `task_relation ${mutation.id} ${mutation.predecessorTaskId} -> ${mutation.successorTaskId}:`,
197
+ ` mode ${mutation.mode}`,
198
+ ...(mutation.reason === undefined
199
+ ? []
200
+ : [serializeTextField("reason", mutation.reason, lineEnding)]),
201
+ ].join(lineEnding);
202
+ }
203
+ function relationRequestError(mutation) {
204
+ if (!nonemptyString(mutation.id))
205
+ return "relation ID must be nonempty";
206
+ if (mutation.kind === "plan_dependency.add") {
207
+ if (!nonemptyString(mutation.predecessorTaskId) ||
208
+ !nonemptyString(mutation.successorTaskId) ||
209
+ !validMode(mutation.mode)) {
210
+ return "relation add requires task IDs and a known mode";
211
+ }
212
+ if (mutation.reason !== undefined &&
213
+ !nonemptyString(mutation.reason)) {
214
+ return "relation reason must be nonempty when present";
215
+ }
216
+ return null;
217
+ }
218
+ if (mutation.predecessorTaskId === undefined &&
219
+ mutation.successorTaskId === undefined &&
220
+ mutation.mode === undefined &&
221
+ mutation.reason === undefined &&
222
+ mutation.clearReason !== true) {
223
+ return "relation set requires at least one change";
224
+ }
225
+ if (mutation.predecessorTaskId !== undefined &&
226
+ !nonemptyString(mutation.predecessorTaskId))
227
+ return "predecessor task ID must be nonempty";
228
+ if (mutation.successorTaskId !== undefined &&
229
+ !nonemptyString(mutation.successorTaskId))
230
+ return "successor task ID must be nonempty";
231
+ if (mutation.mode !== undefined && !validMode(mutation.mode)) {
232
+ return "relation mode is unknown";
233
+ }
234
+ if (mutation.reason !== undefined && !nonemptyString(mutation.reason)) {
235
+ return "relation reason must be nonempty when present";
236
+ }
237
+ if (mutation.reason !== undefined && mutation.clearReason === true) {
238
+ return "relation reason cannot be set and cleared together";
239
+ }
240
+ return null;
241
+ }
242
+ function planRelation(text, validated, mutation) {
243
+ const document = validated.document;
244
+ if (!nonemptyString(mutation.id)) {
245
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", "plan-dependency mutation requires a nonempty relation ID") };
246
+ }
247
+ const current = entity(document, mutation.id);
248
+ if (mutation.kind === "plan_dependency.add") {
249
+ const error = relationRequestError(mutation);
250
+ if (error !== null) {
251
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", error, mutation.id) };
252
+ }
253
+ if (current !== undefined) {
254
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-302", "error", `entity ID ${mutation.id} is already in use`, mutation.id) };
255
+ }
256
+ return { edits: [insertDeclarations(text, document, [serializeRelation(mutation, majorLineEnding(text))], new Set(["plan_seal", "task_outcome", "assurance_receipt", "work_event"]))] };
257
+ }
258
+ if (current === undefined || current.kind !== "task_relation") {
259
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-302", "error", `plan dependency ${mutation.id} does not exist`, mutation.id) };
260
+ }
261
+ if (mutation.kind === "plan_dependency.remove") {
262
+ return { edits: [deleteDeclarationEdit(current, splitPhysicalLines(text))] };
263
+ }
264
+ const error = relationRequestError(mutation);
265
+ if (error !== null) {
266
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", error, mutation.id) };
267
+ }
268
+ if (current.fromSpan === undefined || current.toSpan === undefined) {
269
+ throw new Error("validated task relation lost endpoint spans");
270
+ }
271
+ const editor = new EntityEditor(text, current, relationFieldOrder, mutation.clearReason === true ? ["reason"] : []);
272
+ if (mutation.mode !== undefined)
273
+ editor.setScalar("mode", mutation.mode);
274
+ if (mutation.reason !== undefined)
275
+ editor.setText("reason", mutation.reason);
276
+ const edits = [...editor.finish()];
277
+ if (mutation.predecessorTaskId !== undefined) {
278
+ edits.push({
279
+ startOffset: current.fromSpan.start.offset,
280
+ endOffset: current.fromSpan.end.offset,
281
+ replacement: mutation.predecessorTaskId,
282
+ });
283
+ }
284
+ if (mutation.successorTaskId !== undefined) {
285
+ edits.push({
286
+ startOffset: current.toSpan.start.offset,
287
+ endOffset: current.toSpan.end.offset,
288
+ replacement: mutation.successorTaskId,
289
+ });
290
+ }
291
+ return { edits };
292
+ }
293
+ function evaluationOf(validated) {
294
+ return evaluatePlanAssurance(projectPlanAssuranceInput(validated));
295
+ }
296
+ function currentEqualBasis(evaluation, taskId) {
297
+ const result = evaluation.taskResults.find((task) => task.taskId === taskId);
298
+ return result !== undefined &&
299
+ result.contractHash !== null &&
300
+ result.computedBasisHash !== null &&
301
+ result.acceptedBasisHash === result.computedBasisHash
302
+ ? result
303
+ : null;
304
+ }
305
+ function planInitialSeal(text, validated, mutation, capability) {
306
+ if (!nonemptyString(mutation.reason)) {
307
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", "initial seal requires a nonempty reason") };
308
+ }
309
+ const originalInput = projectPlanAssuranceInput(validated);
310
+ const enabled = originalInput.modelVersion !== null ||
311
+ originalInput.hashModelVersion !== null;
312
+ if (enabled &&
313
+ (originalInput.modelVersion !== 1 || originalInput.hashModelVersion !== 1)) {
314
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-303", "error", "initial seal requires known assurance models") };
315
+ }
316
+ const originalEvaluation = evaluationOf(validated);
317
+ const existingSealed = new Set(originalInput.tasks.filter((task) => task.seal !== null)
318
+ .map((task) => task.contract.taskId));
319
+ if (enabled &&
320
+ originalInput.tasks.every((task) => task.seal !== null)) {
321
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-303", "error", "initial seal would replace an existing complete baseline") };
322
+ }
323
+ if (enabled &&
324
+ originalEvaluation.taskResults.some((result) => existingSealed.has(result.taskId) &&
325
+ result.status !== "verified" &&
326
+ result.status !== "conditional")) {
327
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-303", "error", "partial initial seal cannot replace or silently accept an inconsistent existing seal") };
328
+ }
329
+ const project = validated.document.declarations.find((declaration) => declaration.kind === "project");
330
+ if (project === undefined)
331
+ throw new Error("validated document has no project");
332
+ const projectEditor = new EntityEditor(text, project, TARGET_GRAMMAR_6_DECLARATION_FIELD_ORDER.project);
333
+ if (!enabled) {
334
+ projectEditor.setScalar("version", "6");
335
+ projectEditor.setScalar("plan_assurance_model", "1");
336
+ projectEditor.setScalar("plan_assurance_hash_model", "1");
337
+ }
338
+ const projectEdits = [...projectEditor.finish()];
339
+ const enabledText = applyTextEdits(text, normalizeTextEdits(text, projectEdits, "plan assurance enablement"));
340
+ const enabledValidation = validateTargetGrammar6Document(enabledText, capability);
341
+ if (!enabledValidation.ok || enabledValidation.validatedDocument === null) {
342
+ throw new Error("initial assurance enablement did not validate");
343
+ }
344
+ const enabledInput = projectPlanAssuranceInput(enabledValidation.validatedDocument);
345
+ const enabledEvaluation = !enabled
346
+ ? evaluatePlanAssurance({
347
+ ...enabledInput,
348
+ tasks: enabledInput.tasks.map((task) => ({
349
+ ...task,
350
+ lifecycle: "unfinished",
351
+ outcome: null,
352
+ })),
353
+ })
354
+ : evaluatePlanAssurance(enabledInput);
355
+ if (!enabledEvaluation.ok) {
356
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-303", "error", "initial seal basis is unavailable") };
357
+ }
358
+ const missing = enabledEvaluation.taskResults
359
+ .filter((result) => !existingSealed.has(result.taskId));
360
+ let seals;
361
+ try {
362
+ seals = missing.map((result) => serializeSeal(result.taskId, sealTaskResult(result), mutation.reason, majorLineEnding(text)));
363
+ }
364
+ catch {
365
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-303", "error", "initial seal requires every missing task basis to be available") };
366
+ }
367
+ const sealEdit = insertDeclarations(text, validated.document, seals, new Set(["task_outcome", "assurance_receipt", "work_event"]));
368
+ return { edits: [...projectEdits, sealEdit] };
369
+ }
370
+ function planReseal(text, validated, mutation) {
371
+ if (!Array.isArray(mutation.taskIds) ||
372
+ mutation.taskIds.length === 0 ||
373
+ mutation.taskIds.some((taskId) => !nonemptyString(taskId)) ||
374
+ new Set(mutation.taskIds).size !== mutation.taskIds.length ||
375
+ !nonemptyString(mutation.reason)) {
376
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", "reseal requires unique task IDs and one nonempty reason") };
377
+ }
378
+ const input = projectPlanAssuranceInput(validated);
379
+ if (input.modelVersion !== 1 || input.hashModelVersion !== 1) {
380
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-304", "error", "reseal requires known enabled assurance models") };
381
+ }
382
+ const evaluation = evaluatePlanAssurance(input);
383
+ if (!evaluation.ok) {
384
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-304", "error", "reseal cannot evaluate the planning DAG") };
385
+ }
386
+ const selected = new Set(mutation.taskIds);
387
+ const resultById = new Map(evaluation.taskResults.map((result) => [result.taskId, result]));
388
+ for (const taskId of selected) {
389
+ const result = resultById.get(taskId);
390
+ const seal = declarationOfKind(validated.document, "plan_seal", taskId);
391
+ if (result === undefined ||
392
+ seal === undefined ||
393
+ result.contractHash === null ||
394
+ result.computedBasisHash === null) {
395
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-304", "error", `task ${taskId} has no resealable current basis`, taskId) };
396
+ }
397
+ const unresolved = evaluation.effectiveDependencies
398
+ .filter((relation) => relation.successorTaskId === taskId &&
399
+ relation.mode !== "execution_only")
400
+ .map((relation) => relation.predecessorTaskId)
401
+ .find((predecessorId) => {
402
+ const predecessor = resultById.get(predecessorId);
403
+ return predecessor !== undefined &&
404
+ predecessor.status !== "verified" &&
405
+ predecessor.status !== "conditional" &&
406
+ !selected.has(predecessorId);
407
+ });
408
+ if (unresolved !== undefined) {
409
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-304", "error", `task ${taskId} has unresolved unselected predecessor ${unresolved}`, taskId, { predecessor_task_id: unresolved }) };
410
+ }
411
+ }
412
+ const edits = [];
413
+ for (const taskId of [...selected].sort()) {
414
+ edits.push(...planSealRecordEdits(text, declarationOfKind(validated.document, "plan_seal", taskId), sealTaskResult(resultById.get(taskId)), mutation.reason));
415
+ }
416
+ return { edits };
417
+ }
418
+ function outcomeRequestError(mutation) {
419
+ if (!nonemptyString(mutation.id))
420
+ return "outcome ID must be nonempty";
421
+ if (mutation.kind === "task_outcome.add") {
422
+ if (!nonemptyString(mutation.taskId) || !nonemptyString(mutation.reason)) {
423
+ return "outcome add requires a task ID and nonempty reason";
424
+ }
425
+ if (mutation.status !== "conformant" && mutation.status !== "changed") {
426
+ return "outcome status is unknown";
427
+ }
428
+ if ((mutation.status === "changed" && !nonemptyString(mutation.summary)) ||
429
+ (mutation.status === "conformant" && mutation.summary !== undefined)) {
430
+ return "changed outcome requires summary and conformant outcome forbids it";
431
+ }
432
+ return null;
433
+ }
434
+ if (mutation.status === undefined &&
435
+ mutation.summary === undefined &&
436
+ mutation.clearSummary !== true &&
437
+ mutation.reason === undefined &&
438
+ mutation.rebindCurrentBasis !== true)
439
+ return "outcome set requires at least one change";
440
+ if (mutation.status !== undefined &&
441
+ mutation.status !== "conformant" &&
442
+ mutation.status !== "changed")
443
+ return "outcome status is unknown";
444
+ if (mutation.summary !== undefined && !nonemptyString(mutation.summary)) {
445
+ return "outcome summary must be nonempty when present";
446
+ }
447
+ if (mutation.reason !== undefined && !nonemptyString(mutation.reason)) {
448
+ return "outcome reason must be nonempty when present";
449
+ }
450
+ if (mutation.summary !== undefined && mutation.clearSummary === true) {
451
+ return "outcome summary cannot be set and cleared together";
452
+ }
453
+ if (mutation.rebindCurrentBasis === true && !nonemptyString(mutation.reason)) {
454
+ return "outcome rebind requires a nonempty reason";
455
+ }
456
+ return null;
457
+ }
458
+ function serializeOutcome(mutation, basis, lineEnding) {
459
+ return [
460
+ `task_outcome ${mutation.id}:`,
461
+ " model 1",
462
+ ` task ${mutation.taskId}`,
463
+ ` against_basis ${basis}`,
464
+ ` status ${mutation.status}`,
465
+ ...(mutation.summary === undefined
466
+ ? []
467
+ : [serializeTextField("summary", mutation.summary, lineEnding)]),
468
+ serializeTextField("reason", mutation.reason, lineEnding),
469
+ ].join(lineEnding);
470
+ }
471
+ function planOutcome(text, validated, mutation) {
472
+ if (!nonemptyString(mutation.id)) {
473
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", "task-outcome mutation requires a nonempty ID") };
474
+ }
475
+ const current = entity(validated.document, mutation.id);
476
+ const evaluation = evaluationOf(validated);
477
+ if (mutation.kind === "task_outcome.add") {
478
+ const error = outcomeRequestError(mutation);
479
+ if (error !== null) {
480
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", error, mutation.id) };
481
+ }
482
+ if (current !== undefined) {
483
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-302", "error", `entity ID ${mutation.id} is already in use`, mutation.id) };
484
+ }
485
+ const task = declarationOfKind(validated.document, "task", mutation.taskId);
486
+ const result = currentEqualBasis(evaluation, mutation.taskId);
487
+ if (task === undefined ||
488
+ fieldNamed(task, "status")?.value !== "done" ||
489
+ result === null) {
490
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-305", "error", `task ${mutation.taskId} is not a completed task with an equal accepted current basis`, mutation.taskId) };
491
+ }
492
+ if (evaluation.taskResults.some((candidate) => candidate.taskId === mutation.taskId &&
493
+ validated.document.declarations.some((declaration) => declaration.kind === "task_outcome" &&
494
+ fieldNamed(declaration, "task")?.value === mutation.taskId))) {
495
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-302", "error", `task ${mutation.taskId} already has an outcome`, mutation.taskId) };
496
+ }
497
+ return { edits: [insertDeclarations(text, validated.document, [serializeOutcome(mutation, result.computedBasisHash, majorLineEnding(text))], new Set(["assurance_receipt", "work_event"]))] };
498
+ }
499
+ if (current === undefined || current.kind !== "task_outcome") {
500
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-302", "error", `task outcome ${mutation.id} does not exist`, mutation.id) };
501
+ }
502
+ if (mutation.kind === "task_outcome.remove") {
503
+ return { edits: [deleteDeclarationEdit(current, splitPhysicalLines(text))] };
504
+ }
505
+ const error = outcomeRequestError(mutation);
506
+ if (error !== null) {
507
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", error, mutation.id) };
508
+ }
509
+ const taskId = fieldNamed(current, "task")?.value;
510
+ if (typeof taskId !== "string")
511
+ throw new Error("validated outcome lost task ID");
512
+ const editor = new EntityEditor(text, current, outcomeFieldOrder, mutation.clearSummary === true ? ["summary"] : []);
513
+ if (mutation.status !== undefined)
514
+ editor.setScalar("status", mutation.status);
515
+ if (mutation.summary !== undefined)
516
+ editor.setText("summary", mutation.summary);
517
+ if (mutation.reason !== undefined)
518
+ editor.setText("reason", mutation.reason);
519
+ if (mutation.rebindCurrentBasis === true) {
520
+ const result = currentEqualBasis(evaluation, taskId);
521
+ if (result === null) {
522
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-305", "error", `task ${taskId} has no equal accepted current basis for outcome rebind`, taskId) };
523
+ }
524
+ editor.setScalar("against_basis", result.computedBasisHash);
525
+ }
526
+ return { edits: editor.finish() };
527
+ }
528
+ function planEdits(text, validated, mutation, capability) {
529
+ if (mutation.kind === "plan_dependency.add" ||
530
+ mutation.kind === "plan_dependency.set" ||
531
+ mutation.kind === "plan_dependency.remove")
532
+ return planRelation(text, validated, mutation);
533
+ if (mutation.kind === "plan_assurance.seal") {
534
+ return planInitialSeal(text, validated, mutation, capability);
535
+ }
536
+ if (mutation.kind === "plan_assurance.reseal") {
537
+ return planReseal(text, validated, mutation);
538
+ }
539
+ return planOutcome(text, validated, mutation);
540
+ }
541
+ function planBatchEdits(text, validated, mutation, capability) {
542
+ if (!Array.isArray(mutation.mutations) ||
543
+ mutation.mutations.length === 0 ||
544
+ mutation.mutations.some((item) => item === null ||
545
+ typeof item !== "object" ||
546
+ item.kind === "batch")) {
547
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", "assurance batch requires at least one non-batch mutation") };
548
+ }
549
+ const isAssuranceMutation = (item) => requestFieldsByKind[item.kind] !== undefined;
550
+ const hasOrdinaryMutation = mutation.mutations.some((item) => !isAssuranceMutation(item));
551
+ const hasAssuranceMutation = mutation.mutations.some(isAssuranceMutation);
552
+ if (hasOrdinaryMutation && !hasAssuranceMutation) {
553
+ const result = planTargetGrammar6BatchMutation(text, mutation, capability);
554
+ return result.ok
555
+ ? { edits: result.edits }
556
+ : {
557
+ edits: [],
558
+ diagnostic: result.diagnostics.find(({ severity }) => severity === "error") ?? assuranceDiagnostic("PTASSURE-301", "error", "ordinary batch mutation could not produce a valid candidate"),
559
+ };
560
+ }
561
+ if (hasOrdinaryMutation) {
562
+ if (mutation.mutations.some((item) => isAssuranceMutation(item) &&
563
+ item.kind !== "plan_dependency.add" &&
564
+ item.kind !== "plan_dependency.set" &&
565
+ item.kind !== "plan_dependency.remove")) {
566
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", "mixed current-graph batches support relation maintenance; seal and outcome staging remains assurance-only") };
567
+ }
568
+ const edits = [];
569
+ for (const item of mutation.mutations) {
570
+ const planned = isAssuranceMutation(item)
571
+ ? planEdits(text, validated, item, capability)
572
+ : planTargetGrammar6AtomicMutationEdits(text, validated.document, item, capability);
573
+ if (planned.diagnostic !== undefined)
574
+ return planned;
575
+ if (!isAssuranceMutation(item) &&
576
+ (item.kind === "task.add" || item.kind === "gate.add") &&
577
+ planned.edits.length === 1 &&
578
+ planned.edits[0].startOffset === text.length &&
579
+ planned.edits[0].endOffset === text.length) {
580
+ const serialized = planned.edits[0].replacement
581
+ .replace(/^(?:\r?\n)+/, "")
582
+ .replace(/(?:\r?\n)+$/, "");
583
+ edits.push(insertDeclarations(text, validated.document, [serialized], new Set([
584
+ "task_relation",
585
+ "plan_seal",
586
+ "task_outcome",
587
+ "assurance_receipt",
588
+ "work_event",
589
+ ])));
590
+ }
591
+ else {
592
+ edits.push(...planned.edits);
593
+ }
594
+ }
595
+ let candidateText;
596
+ try {
597
+ candidateText = applyTextEdits(text, normalizeTextEdits(text, mergeBatchInsertions(edits), "mixed plan-assurance batch"));
598
+ }
599
+ catch (error) {
600
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", error instanceof Error ? error.message : "mixed batch edits overlap") };
601
+ }
602
+ const checked = validateTargetGrammar6Document(candidateText, capability);
603
+ if (!checked.ok || checked.validatedDocument === null) {
604
+ return {
605
+ edits: [],
606
+ diagnostic: checked.diagnostics.find(({ severity }) => severity === "error") ??
607
+ assuranceDiagnostic("PTASSURE-301", "error", "mixed plan-assurance batch produced an invalid final candidate"),
608
+ };
609
+ }
610
+ return candidateText === text
611
+ ? { edits: [] }
612
+ : { edits: [{ startOffset: 0, endOffset: text.length, replacement: candidateText }] };
613
+ }
614
+ let currentText = text;
615
+ let currentValidated = validated;
616
+ for (const atomic of mutation.mutations) {
617
+ if (!isAssuranceMutation(atomic)) {
618
+ throw new Error("assurance-only batch retained an ordinary mutation");
619
+ }
620
+ const planned = planEdits(currentText, currentValidated, atomic, capability);
621
+ if (planned.diagnostic !== undefined)
622
+ return planned;
623
+ let normalized;
624
+ try {
625
+ normalized = normalizeTextEdits(currentText, planned.edits, `assurance batch ${atomic.kind}`);
626
+ currentText = applyTextEdits(currentText, normalized);
627
+ }
628
+ catch (error) {
629
+ return { edits: [], diagnostic: assuranceDiagnostic("PTASSURE-301", "error", error instanceof Error ? error.message : "assurance batch edits overlap") };
630
+ }
631
+ const checked = validateTargetGrammar6Document(currentText, capability);
632
+ if (!checked.ok || checked.validatedDocument === null) {
633
+ const first = checked.diagnostics.find(({ severity }) => severity === "error");
634
+ return { edits: [], diagnostic: first ?? assuranceDiagnostic("PTASSURE-301", "error", `assurance batch ${atomic.kind} produced an invalid candidate`) };
635
+ }
636
+ currentValidated = checked.validatedDocument;
637
+ }
638
+ return currentText === text
639
+ ? { edits: [] }
640
+ : { edits: [{
641
+ startOffset: 0,
642
+ endOffset: text.length,
643
+ replacement: currentText,
644
+ }] };
645
+ }
646
+ function impact(before, after, beforeDocument, afterDocument) {
647
+ const beforeById = new Map(before.taskResults.map((result) => [result.taskId, result]));
648
+ const affectedTaskIds = after.taskResults
649
+ .filter((result) => {
650
+ const previous = beforeById.get(result.taskId);
651
+ return previous === undefined ||
652
+ previous.status !== result.status ||
653
+ previous.contractHash !== result.contractHash ||
654
+ previous.computedBasisHash !== result.computedBasisHash ||
655
+ previous.acceptedBasisHash !== result.acceptedBasisHash ||
656
+ previous.exportedAssuranceHash !== result.exportedAssuranceHash;
657
+ })
658
+ .map((result) => result.taskId);
659
+ const activeTaskIds = (document) => document.declarations
660
+ .filter((declaration) => declaration.kind === "task" &&
661
+ fieldNamed(declaration, "status")?.value === "active")
662
+ .map(({ id }) => id);
663
+ return Object.freeze({
664
+ modelVersion: 1,
665
+ affectedTaskIds: Object.freeze(affectedTaskIds),
666
+ before,
667
+ after,
668
+ projection: composePlanAssuranceMutationImpact(affectedTaskIds, before, after, activeTaskIds(beforeDocument), activeTaskIds(afterDocument)),
669
+ });
670
+ }
671
+ export function planTargetPlanAssuranceMutation(text, mutation, capability, options = {}) {
672
+ const maximum = normalizeMaxDiagnostics(options.maxDiagnostics);
673
+ const original = validateTargetGrammar6Document(text, capability, { maxDiagnostics: maximum });
674
+ if (!original.ok || original.validatedDocument === null) {
675
+ return failure(text, original.documentId, original.diagnostics, maximum, original.diagnosticsTruncated);
676
+ }
677
+ const requestError = mutationRequestShapeError(mutation);
678
+ if (requestError !== null) {
679
+ return failure(text, original.documentId, [...original.diagnostics, assuranceDiagnostic("PTASSURE-301", "error", requestError)], maximum, original.diagnosticsTruncated);
680
+ }
681
+ const normalizedGovernance = normalizePlanAssuranceGovernanceRequest(options.governance);
682
+ if (!normalizedGovernance.ok) {
683
+ return failure(text, original.documentId, [...original.diagnostics, ...normalizedGovernance.diagnostics], maximum, original.diagnosticsTruncated);
684
+ }
685
+ const planned = mutation.kind === "batch"
686
+ ? planBatchEdits(text, original.validatedDocument, mutation, capability)
687
+ : planEdits(text, original.validatedDocument, mutation, capability);
688
+ if (planned.diagnostic !== undefined) {
689
+ return failure(text, original.documentId, [...original.diagnostics, planned.diagnostic], maximum, original.diagnosticsTruncated);
690
+ }
691
+ let edits;
692
+ let updatedText;
693
+ try {
694
+ edits = Object.freeze(normalizeTextEdits(text, planned.edits, "plan assurance mutation"));
695
+ updatedText = applyTextEdits(text, edits);
696
+ }
697
+ catch (error) {
698
+ return failure(text, original.documentId, [...original.diagnostics, assuranceDiagnostic("PTASSURE-301", "error", error instanceof Error ? error.message : "assurance edits overlap")], maximum, original.diagnosticsTruncated);
699
+ }
700
+ const changed = updatedText !== text;
701
+ const candidate = validateTargetGrammar6Document(updatedText, capability, { maxDiagnostics: maximum });
702
+ if (!candidate.ok || candidate.validatedDocument === null) {
703
+ return failure(text, candidate.documentId ?? original.documentId, candidate.diagnostics, maximum, candidate.diagnosticsTruncated);
704
+ }
705
+ const before = evaluationOf(original.validatedDocument);
706
+ const after = evaluationOf(candidate.validatedDocument);
707
+ if (!after.ok) {
708
+ return failure(text, candidate.documentId, after.diagnostics.map((item) => assuranceDiagnostic(item.code, "error", item.message, item.entityId, item.data)), maximum);
709
+ }
710
+ const originalDigest = digest(text);
711
+ const metadata = governanceMetadataFromDocument(original.validatedDocument.document);
712
+ const governance = evaluatePlanAssuranceGovernance({
713
+ sourceDigest: originalDigest,
714
+ goalOwner: metadata.effective.goalOwner,
715
+ goalDelegates: metadata.effective.goalDelegates,
716
+ dagOwner: metadata.effective.dagOwner,
717
+ dagDelegates: metadata.effective.dagDelegates,
718
+ }, changed
719
+ ? Object.freeze([
720
+ ...classifyGovernanceScopes(original.validatedDocument.document, candidate.validatedDocument.document),
721
+ ...(mutationAffectsAssuranceSource(mutation)
722
+ ? ["plan_assurance"]
723
+ : []),
724
+ ])
725
+ : Object.freeze([]), normalizedGovernance.request);
726
+ const assuranceImpact = impact(before, after, original.validatedDocument.document, candidate.validatedDocument.document);
727
+ const diagnostics = [
728
+ ...candidate.diagnostics,
729
+ ...assuranceImpact.projection.diagnostics,
730
+ ...planAssuranceGovernanceDiagnostics(governance),
731
+ ];
732
+ const hasError = diagnostics.some((diagnostic) => diagnostic.severity === "error");
733
+ const warningFailure = options.warningsAsErrors === true &&
734
+ diagnostics.some((diagnostic) => diagnostic.severity === "warning");
735
+ const limited = limitDiagnostics(sortDiagnostics(diagnostics), maximum);
736
+ return Object.freeze({
737
+ schemaVersion: "Perttool.MutationResult.v4",
738
+ ok: !hasError && !warningFailure,
739
+ documentId: candidate.documentId,
740
+ changed,
741
+ originalDigest,
742
+ updatedDigest: digest(updatedText),
743
+ updatedText,
744
+ diff: createUnifiedDiff(text, updatedText, {
745
+ originalLabel: options.originalLabel ?? "original",
746
+ updatedLabel: options.updatedLabel ?? "updated",
747
+ }),
748
+ edits,
749
+ governance,
750
+ assuranceImpact,
751
+ diagnostics: limited.diagnostics,
752
+ diagnosticsTruncated: original.diagnosticsTruncated ||
753
+ candidate.diagnosticsTruncated ||
754
+ limited.truncated,
755
+ });
756
+ }
757
+ //# sourceMappingURL=mutation.js.map