perttool 0.7.0 → 0.8.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 (231) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +113 -22
  3. package/dist/actuals/lifecycle.d.ts +6 -20
  4. package/dist/actuals/lifecycle.d.ts.map +1 -1
  5. package/dist/actuals/lifecycle.js +5 -176
  6. package/dist/actuals/lifecycle.js.map +1 -1
  7. package/dist/actuals/reduction.d.ts +20 -0
  8. package/dist/actuals/reduction.d.ts.map +1 -0
  9. package/dist/actuals/reduction.js +175 -0
  10. package/dist/actuals/reduction.js.map +1 -0
  11. package/dist/analysis/service.d.ts +33 -0
  12. package/dist/analysis/service.d.ts.map +1 -0
  13. package/dist/analysis/service.js +153 -0
  14. package/dist/analysis/service.js.map +1 -0
  15. package/dist/analysis/target-temporal-input.d.ts +88 -0
  16. package/dist/analysis/target-temporal-input.d.ts.map +1 -0
  17. package/dist/analysis/target-temporal-input.js +249 -0
  18. package/dist/analysis/target-temporal-input.js.map +1 -0
  19. package/dist/analysis/temporal-deadline.d.ts +1 -1
  20. package/dist/analysis/temporal-deadline.d.ts.map +1 -1
  21. package/dist/analysis/temporal-precedence.d.ts +1 -1
  22. package/dist/analysis/temporal-precedence.d.ts.map +1 -1
  23. package/dist/analysis/temporal-precedence.js +1 -1
  24. package/dist/analysis/temporal-precedence.js.map +1 -1
  25. package/dist/analysis/temporal-resource.d.ts +1 -1
  26. package/dist/analysis/temporal-resource.d.ts.map +1 -1
  27. package/dist/analysis/temporal-resource.js +1 -1
  28. package/dist/analysis/temporal-resource.js.map +1 -1
  29. package/dist/application/advance-history.d.ts +2 -1
  30. package/dist/application/advance-history.d.ts.map +1 -1
  31. package/dist/application/advance-history.js +1 -1
  32. package/dist/application/advance-history.js.map +1 -1
  33. package/dist/application/analyze.d.ts +1 -32
  34. package/dist/application/analyze.d.ts.map +1 -1
  35. package/dist/application/analyze.js +1 -152
  36. package/dist/application/analyze.js.map +1 -1
  37. package/dist/application/check.d.ts +1 -42
  38. package/dist/application/check.d.ts.map +1 -1
  39. package/dist/application/check.js +1 -109
  40. package/dist/application/check.js.map +1 -1
  41. package/dist/application/cli-facade.d.ts +85 -0
  42. package/dist/application/cli-facade.d.ts.map +1 -0
  43. package/dist/application/cli-facade.js +108 -0
  44. package/dist/application/cli-facade.js.map +1 -0
  45. package/dist/application/contract6-mutation.js +2 -2
  46. package/dist/application/contract6-mutation.js.map +1 -1
  47. package/dist/application/contract7-mermaid.d.ts.map +1 -1
  48. package/dist/application/contract7-mermaid.js +3 -3
  49. package/dist/application/contract7-mermaid.js.map +1 -1
  50. package/dist/application/contract7-source.d.ts +1 -7
  51. package/dist/application/contract7-source.d.ts.map +1 -1
  52. package/dist/application/contract7-source.js +4 -12
  53. package/dist/application/contract7-source.js.map +1 -1
  54. package/dist/application/editor-dag-focus.d.ts +52 -0
  55. package/dist/application/editor-dag-focus.d.ts.map +1 -0
  56. package/dist/application/editor-dag-focus.js +132 -0
  57. package/dist/application/editor-dag-focus.js.map +1 -0
  58. package/dist/application/format.js +2 -2
  59. package/dist/application/format.js.map +1 -1
  60. package/dist/application/mutate.d.ts +1 -26
  61. package/dist/application/mutate.d.ts.map +1 -1
  62. package/dist/application/mutate.js +1 -205
  63. package/dist/application/mutate.js.map +1 -1
  64. package/dist/application/next.js +2 -2
  65. package/dist/application/next.js.map +1 -1
  66. package/dist/application/target-actuals-advance.js +2 -2
  67. package/dist/application/target-actuals-advance.js.map +1 -1
  68. package/dist/application/target-actuals-analysis.js +2 -2
  69. package/dist/application/target-actuals-analysis.js.map +1 -1
  70. package/dist/application/target-actuals-mutation.d.ts.map +1 -1
  71. package/dist/application/target-actuals-mutation.js +6 -5
  72. package/dist/application/target-actuals-mutation.js.map +1 -1
  73. package/dist/application/target-assurance-analysis.js +2 -2
  74. package/dist/application/target-assurance-analysis.js.map +1 -1
  75. package/dist/application/target-assurance-write.d.ts +2 -1
  76. package/dist/application/target-assurance-write.d.ts.map +1 -1
  77. package/dist/application/target-assurance-write.js +36 -12
  78. package/dist/application/target-assurance-write.js.map +1 -1
  79. package/dist/application/target-governance-mutation.js +2 -2
  80. package/dist/application/target-governance-mutation.js.map +1 -1
  81. package/dist/application/target-historical-graph.d.ts +124 -0
  82. package/dist/application/target-historical-graph.d.ts.map +1 -0
  83. package/dist/application/target-historical-graph.js +547 -0
  84. package/dist/application/target-historical-graph.js.map +1 -0
  85. package/dist/application/target-mutate.d.ts +1 -17
  86. package/dist/application/target-mutate.d.ts.map +1 -1
  87. package/dist/application/target-mutate.js +1 -159
  88. package/dist/application/target-mutate.js.map +1 -1
  89. package/dist/application/target-project-history.d.ts +2 -1
  90. package/dist/application/target-project-history.d.ts.map +1 -1
  91. package/dist/application/target-project-history.js +4 -4
  92. package/dist/application/target-project-history.js.map +1 -1
  93. package/dist/application/target-temporal-analysis.js +2 -2
  94. package/dist/application/target-temporal-analysis.js.map +1 -1
  95. package/dist/application/target-temporal-input.d.ts +1 -87
  96. package/dist/application/target-temporal-input.d.ts.map +1 -1
  97. package/dist/application/target-temporal-input.js +1 -248
  98. package/dist/application/target-temporal-input.js.map +1 -1
  99. package/dist/application/target-unit-migration-candidate.js +4 -4
  100. package/dist/application/target-unit-migration-candidate.js.map +1 -1
  101. package/dist/assurance/advance.js +2 -2
  102. package/dist/assurance/advance.js.map +1 -1
  103. package/dist/assurance/canonical.js +2 -2
  104. package/dist/assurance/canonical.js.map +1 -1
  105. package/dist/assurance/compatibility.js +2 -2
  106. package/dist/assurance/compatibility.js.map +1 -1
  107. package/dist/assurance/mermaid.d.ts +4 -2
  108. package/dist/assurance/mermaid.d.ts.map +1 -1
  109. package/dist/assurance/mermaid.js +11 -7
  110. package/dist/assurance/mermaid.js.map +1 -1
  111. package/dist/assurance/mutation.d.ts.map +1 -1
  112. package/dist/assurance/mutation.js +9 -23
  113. package/dist/assurance/mutation.js.map +1 -1
  114. package/dist/cli.js +66 -26
  115. package/dist/cli.js.map +1 -1
  116. package/dist/command/assurance-discovery.d.ts.map +1 -1
  117. package/dist/command/assurance-discovery.js +73 -3
  118. package/dist/command/assurance-discovery.js.map +1 -1
  119. package/dist/command/assurance-usage.d.ts.map +1 -1
  120. package/dist/command/assurance-usage.js +11 -0
  121. package/dist/command/assurance-usage.js.map +1 -1
  122. package/dist/conversion/mermaid-import.js +3 -3
  123. package/dist/conversion/mermaid-import.js.map +1 -1
  124. package/dist/conversion/mermaid.d.ts +1 -1
  125. package/dist/conversion/mermaid.d.ts.map +1 -1
  126. package/dist/conversion/mermaid.js +4 -4
  127. package/dist/conversion/mermaid.js.map +1 -1
  128. package/dist/core/index.d.ts +33 -0
  129. package/dist/core/index.d.ts.map +1 -0
  130. package/dist/core/index.js +13 -0
  131. package/dist/core/index.js.map +1 -0
  132. package/dist/core/source.d.ts +8 -0
  133. package/dist/core/source.d.ts.map +1 -0
  134. package/dist/core/source.js +11 -0
  135. package/dist/core/source.js.map +1 -0
  136. package/dist/formatter/source-formatter.d.ts +1 -1
  137. package/dist/formatter/source-formatter.d.ts.map +1 -1
  138. package/dist/formatter/source-formatter.js +1 -1
  139. package/dist/formatter/source-formatter.js.map +1 -1
  140. package/dist/guidance/validator.js +2 -2
  141. package/dist/guidance/validator.js.map +1 -1
  142. package/dist/help/assurance-guide.d.ts.map +1 -1
  143. package/dist/help/assurance-guide.js +209 -19
  144. package/dist/help/assurance-guide.js.map +1 -1
  145. package/dist/history/git-probe.d.ts +71 -0
  146. package/dist/history/git-probe.d.ts.map +1 -1
  147. package/dist/history/git-probe.js +517 -0
  148. package/dist/history/git-probe.js.map +1 -1
  149. package/dist/history/historical-graph.d.ts +174 -0
  150. package/dist/history/historical-graph.d.ts.map +1 -0
  151. package/dist/history/historical-graph.js +870 -0
  152. package/dist/history/historical-graph.js.map +1 -0
  153. package/dist/history/historical-transition.d.ts +311 -0
  154. package/dist/history/historical-transition.d.ts.map +1 -0
  155. package/dist/history/historical-transition.js +998 -0
  156. package/dist/history/historical-transition.js.map +1 -0
  157. package/dist/history/project-history.d.ts.map +1 -1
  158. package/dist/history/project-history.js +4 -4
  159. package/dist/history/project-history.js.map +1 -1
  160. package/dist/history/velocity-observation.js +1 -1
  161. package/dist/history/velocity-observation.js.map +1 -1
  162. package/dist/index.d.ts +2 -0
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js +1 -0
  165. package/dist/index.js.map +1 -1
  166. package/dist/io/document-file.js +2 -2
  167. package/dist/io/document-file.js.map +1 -1
  168. package/dist/io/safe-write.js +1 -1
  169. package/dist/io/safe-write.js.map +1 -1
  170. package/dist/model/sha256.d.ts +5 -0
  171. package/dist/model/sha256.d.ts.map +1 -0
  172. package/dist/model/sha256.js +105 -0
  173. package/dist/model/sha256.js.map +1 -0
  174. package/dist/mutation/advance.js +3 -3
  175. package/dist/mutation/advance.js.map +1 -1
  176. package/dist/mutation/diagnostics.d.ts +2 -2
  177. package/dist/mutation/diagnostics.d.ts.map +1 -1
  178. package/dist/mutation/diagnostics.js.map +1 -1
  179. package/dist/mutation/planner.d.ts +27 -0
  180. package/dist/mutation/planner.d.ts.map +1 -0
  181. package/dist/mutation/planner.js +206 -0
  182. package/dist/mutation/planner.js.map +1 -0
  183. package/dist/mutation/source.d.ts +2 -1
  184. package/dist/mutation/source.d.ts.map +1 -1
  185. package/dist/mutation/source.js +14 -0
  186. package/dist/mutation/source.js.map +1 -1
  187. package/dist/mutation/target-planner.d.ts +18 -0
  188. package/dist/mutation/target-planner.d.ts.map +1 -0
  189. package/dist/mutation/target-planner.js +160 -0
  190. package/dist/mutation/target-planner.js.map +1 -0
  191. package/dist/mutation/task.d.ts.map +1 -1
  192. package/dist/mutation/task.js +19 -11
  193. package/dist/mutation/task.js.map +1 -1
  194. package/dist/mutation/types.d.ts +1 -1
  195. package/dist/mutation/types.d.ts.map +1 -1
  196. package/dist/node/historical-host.d.ts +6 -0
  197. package/dist/node/historical-host.d.ts.map +1 -0
  198. package/dist/node/historical-host.js +8 -0
  199. package/dist/node/historical-host.js.map +1 -0
  200. package/dist/node/host.d.ts +3 -0
  201. package/dist/node/host.d.ts.map +1 -0
  202. package/dist/node/host.js +48 -0
  203. package/dist/node/host.js.map +1 -0
  204. package/dist/node/index.d.ts +2 -0
  205. package/dist/node/index.d.ts.map +1 -0
  206. package/dist/node/index.js +2 -0
  207. package/dist/node/index.js.map +1 -0
  208. package/dist/perttool-vscode-private-0.0.0-d1578b9.vsix +0 -0
  209. package/dist/ports/node-host.d.ts +41 -0
  210. package/dist/ports/node-host.d.ts.map +1 -0
  211. package/dist/ports/node-host.js +2 -0
  212. package/dist/ports/node-host.js.map +1 -0
  213. package/dist/recommendation/override.d.ts +40 -2
  214. package/dist/recommendation/override.d.ts.map +1 -1
  215. package/dist/recommendation/override.js +5 -6
  216. package/dist/recommendation/override.js.map +1 -1
  217. package/dist/schema/registry.d.ts.map +1 -1
  218. package/dist/schema/registry.js +1 -0
  219. package/dist/schema/registry.js.map +1 -1
  220. package/dist/semantic/check.d.ts +43 -0
  221. package/dist/semantic/check.d.ts.map +1 -0
  222. package/dist/semantic/check.js +110 -0
  223. package/dist/semantic/check.js.map +1 -0
  224. package/dist/session/document-session.d.ts +109 -0
  225. package/dist/session/document-session.d.ts.map +1 -0
  226. package/dist/session/document-session.js +432 -0
  227. package/dist/session/document-session.js.map +1 -0
  228. package/dist/version.d.ts +1 -1
  229. package/dist/version.js +1 -1
  230. package/package.json +18 -4
  231. package/schemas/Perttool.HistoricalGraphResult.v1.schema.json +654 -0
@@ -0,0 +1,870 @@
1
+ import { validateStoredLifecycleState } from "../actuals/lifecycle.js";
2
+ import { planTargetPlanAssuranceAdvance } from "../assurance/advance.js";
3
+ import { canonicalJson } from "../assurance/canonical.js";
4
+ import { evaluatePlanAssurance } from "../assurance/evaluate.js";
5
+ import { projectPlanAssuranceInput } from "../assurance/source.js";
6
+ import { sha256Digest } from "../model/sha256.js";
7
+ import { TARGET_GRAMMAR_6_CAPABILITY } from "../parser/document-parser.js";
8
+ import { validateTargetGrammar6Document, } from "../semantic/target-validator.js";
9
+ import { HISTORICAL_TRANSITION_MODEL_VERSION, projectHistoricalTransitionModel, projectHistoricalTransitionSequence, } from "./historical-transition.js";
10
+ export const HISTORICAL_DAG_MODEL_VERSION = 1;
11
+ export const HISTORICAL_DAG_MODEL_ID = "Perttool.HistoricalDagModel.v1";
12
+ export const HISTORICAL_CANONICAL_ADVANCE_PLANNER_VERSION = "perttool.canonical-advance.v1";
13
+ export const HISTORICAL_LINEAR_CORE_LIMITS = Object.freeze({
14
+ entityValueEpochs: 100_000,
15
+ transitionRecords: 2_047,
16
+ renderedGraphOccurrences: 20_000,
17
+ historicalSourceBindings: 100_000,
18
+ });
19
+ function deepFreeze(value) {
20
+ if (typeof value !== "object" || value === null || Object.isFrozen(value)) {
21
+ return value;
22
+ }
23
+ for (const child of Object.values(value)) {
24
+ deepFreeze(child);
25
+ }
26
+ return Object.freeze(value);
27
+ }
28
+ function compareUnicodeScalars(left, right) {
29
+ const leftScalars = Array.from(left, (value) => value.codePointAt(0));
30
+ const rightScalars = Array.from(right, (value) => value.codePointAt(0));
31
+ const common = Math.min(leftScalars.length, rightScalars.length);
32
+ for (let index = 0; index < common; index += 1) {
33
+ const difference = leftScalars[index] - rightScalars[index];
34
+ if (difference !== 0)
35
+ return difference;
36
+ }
37
+ return leftScalars.length - rightScalars.length;
38
+ }
39
+ function sorted(values) {
40
+ return [...new Set(values)].sort(compareUnicodeScalars);
41
+ }
42
+ function sameStrings(left, right) {
43
+ const normalizedLeft = sorted(left);
44
+ const normalizedRight = sorted(right);
45
+ return normalizedLeft.length === normalizedRight.length &&
46
+ normalizedLeft.every((value, index) => value === normalizedRight[index]);
47
+ }
48
+ function removedIds(before, after) {
49
+ const retained = new Set(after.map(({ id }) => id));
50
+ return sorted(before.map(({ id }) => id).filter((id) => !retained.has(id)));
51
+ }
52
+ function normalizeLimits(overrides) {
53
+ const limits = {
54
+ ...HISTORICAL_LINEAR_CORE_LIMITS,
55
+ ...overrides,
56
+ };
57
+ for (const [name, value] of Object.entries(limits)) {
58
+ if (!Number.isSafeInteger(value) || value < 0) {
59
+ throw new Error(`historical linear limit ${name} is invalid`);
60
+ }
61
+ }
62
+ return Object.freeze(limits);
63
+ }
64
+ function mapEvidenceCause(cause) {
65
+ if (cause === "ambiguous_path")
66
+ return "source_invalid";
67
+ if (cause === "target_changed")
68
+ return "repository_race";
69
+ return cause;
70
+ }
71
+ function causeRecord(cause, subject, commitId = null, limit = null, actual = null) {
72
+ return { cause, commit_id: commitId, subject, limit, actual };
73
+ }
74
+ function causeKey(cause) {
75
+ return [
76
+ cause.cause,
77
+ cause.commit_id ?? "",
78
+ cause.subject,
79
+ cause.limit ?? "",
80
+ cause.actual?.toString() ?? "",
81
+ ].join("\u0000");
82
+ }
83
+ function uniqueCauses(causes) {
84
+ const byKey = new Map();
85
+ for (const cause of causes) {
86
+ const key = causeKey(cause);
87
+ if (!byKey.has(key))
88
+ byKey.set(key, cause);
89
+ }
90
+ return [...byKey.values()];
91
+ }
92
+ function classifyAssurance(projection, validated) {
93
+ const evaluation = evaluatePlanAssurance(projectPlanAssuranceInput(validated));
94
+ if (!evaluation.ok || evaluation.coverage === null)
95
+ return "unavailable";
96
+ if (evaluation.coverage === "not_enabled")
97
+ return "not_enabled";
98
+ if (evaluation.unavailableTaskIds.length > 0 ||
99
+ projection.semantic.plan_seals.some(({ accepted_basis }) => accepted_basis.length === 0))
100
+ return "unavailable";
101
+ if (evaluation.coverage !== "complete" ||
102
+ evaluation.replanRequiredTaskIds.length > 0 ||
103
+ evaluation.directMismatchTaskIds.length > 0 ||
104
+ evaluation.inheritedMismatchTaskIds.length > 0)
105
+ return "withheld";
106
+ return "verified";
107
+ }
108
+ function processSnapshot(snapshot) {
109
+ if (snapshot.blobId === null || snapshot.source === null ||
110
+ snapshot.sourceDigest === null) {
111
+ return {
112
+ evidence: snapshot,
113
+ validity: "source_missing",
114
+ assurance: "unavailable",
115
+ text: null,
116
+ projection: null,
117
+ diagnosticCodes: [],
118
+ };
119
+ }
120
+ if (sha256Digest(snapshot.source) !== snapshot.sourceDigest) {
121
+ return {
122
+ evidence: snapshot,
123
+ validity: "source_invalid",
124
+ assurance: "unavailable",
125
+ text: null,
126
+ projection: null,
127
+ diagnosticCodes: [],
128
+ };
129
+ }
130
+ let text;
131
+ try {
132
+ text = new TextDecoder("utf-8", {
133
+ fatal: true,
134
+ ignoreBOM: true,
135
+ }).decode(snapshot.source);
136
+ }
137
+ catch {
138
+ return {
139
+ evidence: snapshot,
140
+ validity: "source_invalid",
141
+ assurance: "unavailable",
142
+ text: null,
143
+ projection: null,
144
+ diagnosticCodes: [],
145
+ };
146
+ }
147
+ const checked = validateTargetGrammar6Document(text, TARGET_GRAMMAR_6_CAPABILITY);
148
+ const diagnosticCodes = checked.diagnostics.map(({ code }) => code);
149
+ if (checked.grammarVersion !== null &&
150
+ ![1, 2, 3, 4, 5, 6].includes(checked.grammarVersion)) {
151
+ return {
152
+ evidence: snapshot,
153
+ validity: "grammar_unsupported",
154
+ assurance: "unavailable",
155
+ text,
156
+ projection: null,
157
+ diagnosticCodes,
158
+ };
159
+ }
160
+ if (checked.parseFailed) {
161
+ return {
162
+ evidence: snapshot,
163
+ validity: "syntax_invalid",
164
+ assurance: "unavailable",
165
+ text,
166
+ projection: null,
167
+ diagnosticCodes,
168
+ };
169
+ }
170
+ if (!checked.ok || checked.validatedDocument === null) {
171
+ return {
172
+ evidence: snapshot,
173
+ validity: "semantic_invalid",
174
+ assurance: "unavailable",
175
+ text,
176
+ projection: null,
177
+ diagnosticCodes,
178
+ };
179
+ }
180
+ const lifecycleDiagnostics = validateStoredLifecycleState(checked.validatedDocument);
181
+ if (lifecycleDiagnostics.length > 0) {
182
+ return {
183
+ evidence: snapshot,
184
+ validity: "semantic_invalid",
185
+ assurance: "unavailable",
186
+ text,
187
+ projection: null,
188
+ diagnosticCodes: [
189
+ ...diagnosticCodes,
190
+ ...lifecycleDiagnostics.map(({ code }) => code),
191
+ ],
192
+ };
193
+ }
194
+ const projection = projectHistoricalTransitionModel(checked.validatedDocument);
195
+ return {
196
+ evidence: snapshot,
197
+ validity: "semantic_valid",
198
+ assurance: classifyAssurance(projection, checked.validatedDocument),
199
+ text,
200
+ projection,
201
+ diagnosticCodes,
202
+ };
203
+ }
204
+ function semanticRecords(semantic) {
205
+ return [
206
+ ...semantic.milestones.map((value) => ({
207
+ kind: "milestone",
208
+ id: value.id,
209
+ semantic: value,
210
+ fromId: null,
211
+ toId: null,
212
+ })),
213
+ ...semantic.tasks.map((value) => ({
214
+ kind: "task",
215
+ id: value.id,
216
+ semantic: value,
217
+ fromId: value.from_milestone_id,
218
+ toId: value.to_milestone_id,
219
+ })),
220
+ ...semantic.gates.map((value) => ({
221
+ kind: "gate",
222
+ id: value.id,
223
+ semantic: value,
224
+ fromId: value.from_milestone_id,
225
+ toId: value.to_milestone_id,
226
+ })),
227
+ ...semantic.resources.map((value) => ({
228
+ kind: "resource",
229
+ id: value.id,
230
+ semantic: value,
231
+ fromId: null,
232
+ toId: null,
233
+ })),
234
+ ];
235
+ }
236
+ function epochKey(kind, id) {
237
+ return `${kind}\u0000${id}`;
238
+ }
239
+ function graphFor(commitId, projection, epochs, topologyEpochId, previousByOccurrence) {
240
+ const epochByKey = new Map(epochs.map((epoch) => [
241
+ epochKey(epoch.entity_kind, epoch.source_id),
242
+ epoch,
243
+ ]));
244
+ const occurrenceByEntity = new Map();
245
+ for (const epoch of epochs) {
246
+ occurrenceByEntity.set(epochKey(epoch.entity_kind, epoch.source_id), epoch.occurrence_id);
247
+ }
248
+ const occurrences = semanticRecords(projection.semantic).map((record) => {
249
+ const epoch = epochByKey.get(epochKey(record.kind, record.id));
250
+ if (epoch === undefined) {
251
+ throw new Error("historical sequence omitted an entity epoch");
252
+ }
253
+ const previous = epoch.occurrence_id === null
254
+ ? undefined
255
+ : previousByOccurrence.get(epoch.occurrence_id);
256
+ return {
257
+ entity_kind: record.kind,
258
+ source_id: record.id,
259
+ occurrence_id: epoch.occurrence_id,
260
+ value_epoch_ordinal: epoch.value_epoch_ordinal,
261
+ semantic: record.semantic,
262
+ from_occurrence_id: record.fromId === null
263
+ ? null
264
+ : occurrenceByEntity.get(epochKey("milestone", record.fromId)) ?? null,
265
+ to_occurrence_id: record.toId === null
266
+ ? null
267
+ : occurrenceByEntity.get(epochKey("milestone", record.toId)) ?? null,
268
+ first_observed_commit_id: previous?.first_observed_commit_id ?? commitId,
269
+ last_observed_commit_id: commitId,
270
+ retired_at_commit_id: null,
271
+ };
272
+ });
273
+ occurrences.sort((left, right) => ["milestone", "task", "gate", "resource"].indexOf(left.entity_kind) -
274
+ ["milestone", "task", "gate", "resource"].indexOf(right.entity_kind) ||
275
+ compareUnicodeScalars(left.source_id, right.source_id));
276
+ return deepFreeze({
277
+ project_id: projection.semantic.project.id,
278
+ semantic_digest: projection.semantic_digest,
279
+ topology_epoch_id: topologyEpochId,
280
+ occurrences,
281
+ });
282
+ }
283
+ function cloneRange(range) {
284
+ return {
285
+ start: { ...range.start },
286
+ end: { ...range.end },
287
+ };
288
+ }
289
+ function sourceBindings(snapshot, projection) {
290
+ if (snapshot.blobId === null || snapshot.sourceDigest === null)
291
+ return [];
292
+ const common = {
293
+ repository_id: snapshot.repositoryId,
294
+ repository_relative_path: snapshot.repositoryRelativePath,
295
+ commit_id: snapshot.commitId,
296
+ blob_id: snapshot.blobId,
297
+ source_digest: snapshot.sourceDigest,
298
+ };
299
+ const bindings = [];
300
+ for (const declaration of projection.source_fidelity.declarations) {
301
+ bindings.push({
302
+ ...common,
303
+ range: cloneRange(declaration.range),
304
+ declaration_kind: declaration.kind,
305
+ source_id: declaration.id,
306
+ owner_path: declaration.id,
307
+ });
308
+ for (const field of declaration.fields) {
309
+ bindings.push({
310
+ ...common,
311
+ range: cloneRange(field.range),
312
+ declaration_kind: declaration.kind,
313
+ source_id: declaration.id,
314
+ owner_path: `${declaration.id}.${field.name}[${field.ordinal}]`,
315
+ });
316
+ for (const child of field.child_ranges) {
317
+ bindings.push({
318
+ ...common,
319
+ range: cloneRange(child.range),
320
+ declaration_kind: declaration.kind,
321
+ source_id: declaration.id,
322
+ owner_path: `${declaration.id}.${field.name}[${field.ordinal}].${child.name}[${child.ordinal}]`,
323
+ });
324
+ }
325
+ }
326
+ }
327
+ return deepFreeze(bindings);
328
+ }
329
+ function countSourceBindings(projection) {
330
+ return projection.source_fidelity.declarations.reduce((declarationTotal, declaration) => declarationTotal + 1 + declaration.fields.reduce((fieldTotal, field) => fieldTotal + 1 + field.child_ranges.length, 0), 0);
331
+ }
332
+ function removedAssuranceIds(before, after) {
333
+ return sorted([
334
+ ...before.plan_seals
335
+ .map(({ task_id }) => task_id)
336
+ .filter((id) => !after.plan_seals.some(({ task_id }) => task_id === id)),
337
+ ...removedIds(before.task_outcomes, after.task_outcomes),
338
+ ...removedIds(before.assurance_receipts, after.assurance_receipts),
339
+ ]);
340
+ }
341
+ function stateChangedMilestones(before, after) {
342
+ const beforeById = new Map(before.milestones.map((value) => [value.id, value]));
343
+ return sorted(after.milestones
344
+ .filter((value) => beforeById.get(value.id)?.state !== value.state)
345
+ .map(({ id }) => id));
346
+ }
347
+ function canonicalAdvanceAttempt(previousText, previous, current) {
348
+ const result = planTargetPlanAssuranceAdvance(previousText, TARGET_GRAMMAR_6_CAPABILITY, { governance: { intent: "preview" } });
349
+ if (!result.ok || !result.changed || result.updatedText === null ||
350
+ result.advance === null || result.assuranceGuard?.status === "blocked")
351
+ return null;
352
+ const checked = validateTargetGrammar6Document(result.updatedText, TARGET_GRAMMAR_6_CAPABILITY);
353
+ if (!checked.ok || checked.validatedDocument === null)
354
+ return null;
355
+ const candidateProjection = projectHistoricalTransitionModel(checked.validatedDocument);
356
+ const beforeSemantic = previous.semantic;
357
+ const afterSemantic = current.semantic;
358
+ const summaryVerified = candidateProjection.semantic_digest === current.semantic_digest &&
359
+ sameStrings(result.advance.removedTaskIds, removedIds(beforeSemantic.tasks, afterSemantic.tasks)) &&
360
+ sameStrings(result.advance.removedGateIds, removedIds(beforeSemantic.gates, afterSemantic.gates)) &&
361
+ sameStrings(result.advance.removedMilestoneIds, removedIds(beforeSemantic.milestones, afterSemantic.milestones)) &&
362
+ sameStrings(result.advance.removedWorkEventIds, removedIds(beforeSemantic.work_events, afterSemantic.work_events)) &&
363
+ sameStrings(result.advance.removedAssuranceRecordIds, removedAssuranceIds(beforeSemantic, afterSemantic)) &&
364
+ sameStrings(result.advance.stateChangedMilestoneIds, stateChangedMilestones(beforeSemantic, afterSemantic));
365
+ return {
366
+ candidate: {
367
+ planner_version: HISTORICAL_CANONICAL_ADVANCE_PLANNER_VERSION,
368
+ base_semantic_digest: previous.semantic_digest,
369
+ candidate: candidateProjection,
370
+ complete: summaryVerified,
371
+ force_requested: false,
372
+ owner_assertion_used: false,
373
+ repository_proof_assumed: false,
374
+ persistence_assumed: false,
375
+ },
376
+ summary: result.advance,
377
+ summaryVerified,
378
+ };
379
+ }
380
+ function evidenceBindingValid(evidence) {
381
+ if (evidence.status === "unavailable")
382
+ return true;
383
+ if (evidence.objectFormat === null || evidence.repositoryId === null ||
384
+ evidence.repositoryReadSnapshotId === null ||
385
+ evidence.repositoryRelativePath === null ||
386
+ evidence.resolvedEndpoint === null || evidence.snapshots.length === 0 ||
387
+ evidence.inspectedCommitIds.length !== evidence.snapshots.length)
388
+ return false;
389
+ const seen = new Set();
390
+ for (const [index, snapshot] of evidence.snapshots.entries()) {
391
+ if (seen.has(snapshot.commitId) ||
392
+ evidence.inspectedCommitIds[index] !== snapshot.commitId ||
393
+ snapshot.modelVersion !== 1 ||
394
+ snapshot.objectFormat !== evidence.objectFormat ||
395
+ snapshot.repositoryId !== evidence.repositoryId ||
396
+ snapshot.repositoryReadSnapshotId !== evidence.repositoryReadSnapshotId ||
397
+ snapshot.repositoryRelativePath !== evidence.repositoryRelativePath ||
398
+ (snapshot.source === null) !== (snapshot.sourceDigest === null) ||
399
+ (snapshot.source === null) !== (snapshot.blobId === null) ||
400
+ (snapshot.source !== null &&
401
+ sha256Digest(snapshot.source) !== snapshot.sourceDigest))
402
+ return false;
403
+ seen.add(snapshot.commitId);
404
+ }
405
+ const endpoint = evidence.snapshots.at(-1);
406
+ return endpoint.commitId === evidence.resolvedEndpoint && endpoint.isEndpoint;
407
+ }
408
+ function emptyResult(evidence, limits, selectedSnapshotCommitId, causes) {
409
+ return deepFreeze({
410
+ model: HISTORICAL_DAG_MODEL_ID,
411
+ model_version: HISTORICAL_DAG_MODEL_VERSION,
412
+ transition_model_version: HISTORICAL_TRANSITION_MODEL_VERSION,
413
+ status: "unavailable",
414
+ ancestry_profile: "first_parent",
415
+ evidence_status: evidence.status,
416
+ repository_id: evidence.repositoryId,
417
+ repository_relative_path: evidence.repositoryRelativePath,
418
+ repository_read_snapshot_id: evidence.repositoryReadSnapshotId,
419
+ requested_endpoint: evidence.requestedEndpoint,
420
+ resolved_endpoint: evidence.resolvedEndpoint,
421
+ requested_lower_boundary: evidence.requestedLowerBoundary,
422
+ resolved_lower_boundary: evidence.resolvedLowerBoundary,
423
+ effective_checkpoint_id: null,
424
+ selected_snapshot_commit_id: selectedSnapshotCommitId,
425
+ selected_snapshot: null,
426
+ checkpoints: [],
427
+ lineage: null,
428
+ timeline: null,
429
+ causes: uniqueCauses(causes),
430
+ limits,
431
+ });
432
+ }
433
+ function outputLimitResult(evidence, limits, selectedSnapshotCommitId, effectiveCheckpointId, causes) {
434
+ return deepFreeze({
435
+ model: HISTORICAL_DAG_MODEL_ID,
436
+ model_version: HISTORICAL_DAG_MODEL_VERSION,
437
+ transition_model_version: HISTORICAL_TRANSITION_MODEL_VERSION,
438
+ status: "incomplete",
439
+ ancestry_profile: "first_parent",
440
+ evidence_status: evidence.status,
441
+ repository_id: evidence.repositoryId,
442
+ repository_relative_path: evidence.repositoryRelativePath,
443
+ repository_read_snapshot_id: evidence.repositoryReadSnapshotId,
444
+ requested_endpoint: evidence.requestedEndpoint,
445
+ resolved_endpoint: evidence.resolvedEndpoint,
446
+ requested_lower_boundary: evidence.requestedLowerBoundary,
447
+ resolved_lower_boundary: evidence.resolvedLowerBoundary,
448
+ effective_checkpoint_id: effectiveCheckpointId,
449
+ selected_snapshot_commit_id: selectedSnapshotCommitId,
450
+ selected_snapshot: null,
451
+ checkpoints: [],
452
+ lineage: null,
453
+ timeline: null,
454
+ causes: uniqueCauses(causes),
455
+ limits,
456
+ });
457
+ }
458
+ function topologyClosedAndAcyclic(occurrences) {
459
+ const milestones = new Set(occurrences
460
+ .filter(({ entity_kind }) => entity_kind === "milestone")
461
+ .map(({ occurrence_id }) => occurrence_id)
462
+ .filter((id) => id !== null));
463
+ const edges = occurrences.filter(({ entity_kind }) => entity_kind === "task" || entity_kind === "gate");
464
+ if (edges.some((edge) => edge.occurrence_id === null || edge.from_occurrence_id === null ||
465
+ edge.to_occurrence_id === null ||
466
+ !milestones.has(edge.from_occurrence_id) ||
467
+ !milestones.has(edge.to_occurrence_id)))
468
+ return "topology_conflict";
469
+ const indegree = new Map([...milestones].map((id) => [id, 0]));
470
+ const outgoing = new Map([...milestones].map((id) => [id, []]));
471
+ for (const edge of edges) {
472
+ const from = edge.from_occurrence_id;
473
+ const to = edge.to_occurrence_id;
474
+ outgoing.get(from).push(to);
475
+ indegree.set(to, indegree.get(to) + 1);
476
+ }
477
+ const ready = [...milestones]
478
+ .filter((id) => indegree.get(id) === 0)
479
+ .sort(compareUnicodeScalars);
480
+ let visited = 0;
481
+ while (ready.length > 0) {
482
+ const id = ready.shift();
483
+ visited += 1;
484
+ for (const successor of outgoing.get(id).sort(compareUnicodeScalars)) {
485
+ const next = indegree.get(successor) - 1;
486
+ indegree.set(successor, next);
487
+ if (next === 0) {
488
+ ready.push(successor);
489
+ ready.sort(compareUnicodeScalars);
490
+ }
491
+ }
492
+ }
493
+ return visited === milestones.size ? "closed" : "lineage_cycle";
494
+ }
495
+ function occurrenceIds(graph) {
496
+ return new Set(graph.occurrences
497
+ .map(({ occurrence_id }) => occurrence_id)
498
+ .filter((id) => id !== null));
499
+ }
500
+ export function reconstructHistoricalLinearHistory(evidence, options = {}) {
501
+ const limits = normalizeLimits(options.limits);
502
+ const selectedSnapshotCommitId = options.snapshotCommitId ??
503
+ evidence.resolvedEndpoint ?? evidence.requestedEndpoint;
504
+ const evidenceCauses = evidence.causes.map(({ cause, commitId }) => causeRecord(mapEvidenceCause(cause), "evidence", commitId));
505
+ if (evidence.status === "unavailable") {
506
+ return emptyResult(evidence, limits, selectedSnapshotCommitId, evidenceCauses);
507
+ }
508
+ if (!evidenceBindingValid(evidence)) {
509
+ return emptyResult(evidence, limits, selectedSnapshotCommitId, [
510
+ ...evidenceCauses,
511
+ causeRecord("repository_race", "evidence"),
512
+ ]);
513
+ }
514
+ if (!evidence.inspectedCommitIds.includes(selectedSnapshotCommitId)) {
515
+ return emptyResult(evidence, limits, selectedSnapshotCommitId, [
516
+ ...evidenceCauses,
517
+ causeRecord("unknown_revision", "evidence", selectedSnapshotCommitId),
518
+ ]);
519
+ }
520
+ const initial = evidence.snapshots.map(processSnapshot);
521
+ let segmentOrdinal = 0;
522
+ let previousWasValid = false;
523
+ const processed = initial.map((snapshot) => {
524
+ const valid = snapshot.validity === "semantic_valid";
525
+ if (valid && !previousWasValid)
526
+ segmentOrdinal += 1;
527
+ previousWasValid = valid;
528
+ return {
529
+ ...snapshot,
530
+ segmentOrdinal: valid ? segmentOrdinal : null,
531
+ };
532
+ });
533
+ const causes = [...evidenceCauses];
534
+ for (const snapshot of processed) {
535
+ if (snapshot.validity !== "semantic_valid") {
536
+ causes.push(causeRecord(snapshot.validity, "snapshot", snapshot.evidence.commitId));
537
+ }
538
+ else if (snapshot.assurance === "withheld" ||
539
+ snapshot.assurance === "unavailable") {
540
+ causes.push(causeRecord("assurance_withheld", "assurance", snapshot.evidence.commitId));
541
+ }
542
+ }
543
+ const valid = processed.filter((snapshot) => snapshot.validity === "semantic_valid" && snapshot.text !== null &&
544
+ snapshot.projection !== null && snapshot.segmentOrdinal !== null);
545
+ const indexByCommit = new Map(processed.map((snapshot, index) => [snapshot.evidence.commitId, index]));
546
+ const preflightFacts = [
547
+ [
548
+ "entityValueEpochs",
549
+ valid.reduce((sum, snapshot) => sum + semanticRecords(snapshot.projection.semantic).length, 0),
550
+ limits.entityValueEpochs,
551
+ ],
552
+ [
553
+ "transitionRecords",
554
+ valid.reduce((sum, snapshot, index) => {
555
+ const previous = index === 0 ? undefined : valid[index - 1];
556
+ return sum + (previous !== undefined &&
557
+ indexByCommit.get(snapshot.evidence.commitId) ===
558
+ indexByCommit.get(previous.evidence.commitId) + 1
559
+ ? 1
560
+ : 0);
561
+ }, 0),
562
+ limits.transitionRecords,
563
+ ],
564
+ [
565
+ "renderedGraphOccurrences",
566
+ valid.reduce((sum, snapshot) => sum + semanticRecords(snapshot.projection.semantic).length, 0),
567
+ limits.renderedGraphOccurrences,
568
+ ],
569
+ [
570
+ "historicalSourceBindings",
571
+ valid.reduce((sum, snapshot) => sum + countSourceBindings(snapshot.projection), 0),
572
+ limits.historicalSourceBindings,
573
+ ],
574
+ ];
575
+ const exceededPreflight = preflightFacts.filter(([, actual, limit]) => actual > limit);
576
+ if (exceededPreflight.length > 0) {
577
+ return outputLimitResult(evidence, limits, selectedSnapshotCommitId, valid.at(-1)?.evidence.commitId ?? null, [
578
+ ...causes,
579
+ ...exceededPreflight.map(([name, actual]) => causeRecord("hard_limit", "limit", null, name, actual)),
580
+ ]);
581
+ }
582
+ const attempts = new Map();
583
+ const sequenceInputs = valid.map((snapshot, index) => {
584
+ const previous = index === 0 ? undefined : valid[index - 1];
585
+ const connected = previous !== undefined &&
586
+ indexByCommit.get(snapshot.evidence.commitId) ===
587
+ indexByCommit.get(previous.evidence.commitId) + 1;
588
+ const attempt = connected
589
+ ? canonicalAdvanceAttempt(previous.text, previous.projection, snapshot.projection)
590
+ : null;
591
+ if (attempt !== null)
592
+ attempts.set(snapshot.evidence.commitId, attempt);
593
+ return {
594
+ commit_id: snapshot.evidence.commitId,
595
+ projection: snapshot.projection,
596
+ connected_to_previous: connected,
597
+ is_merge_commit: snapshot.evidence.isMergeCommit,
598
+ ...(attempt?.summaryVerified === true
599
+ ? { canonical_advance_candidate: attempt.candidate }
600
+ : {}),
601
+ };
602
+ });
603
+ const sequence = projectHistoricalTransitionSequence(sequenceInputs);
604
+ const sequenceByCommit = new Map(sequence.checkpoints.map((checkpoint) => [checkpoint.commit_id, checkpoint]));
605
+ for (const cause of sequence.causes) {
606
+ causes.push(causeRecord(cause.cause, "transition", cause.commit_id));
607
+ }
608
+ for (const checkpoint of sequence.checkpoints) {
609
+ if (checkpoint.transition.class === "ambiguous_edit") {
610
+ causes.push(causeRecord("ambiguous_edit", "transition", checkpoint.commit_id));
611
+ }
612
+ }
613
+ const occurrenceMemory = new Map();
614
+ const checkpoints = [];
615
+ for (const snapshot of valid) {
616
+ const sequenceCheckpoint = sequenceByCommit.get(snapshot.evidence.commitId);
617
+ if (sequenceCheckpoint === undefined) {
618
+ throw new Error("historical sequence checkpoint is missing");
619
+ }
620
+ const graph = graphFor(snapshot.evidence.commitId, snapshot.projection, sequenceCheckpoint.entity_value_epochs, sequenceCheckpoint.topology_epoch_id, occurrenceMemory);
621
+ for (const occurrence of graph.occurrences) {
622
+ if (occurrence.occurrence_id !== null) {
623
+ occurrenceMemory.set(occurrence.occurrence_id, occurrence);
624
+ }
625
+ }
626
+ checkpoints.push(deepFreeze({
627
+ commit_id: snapshot.evidence.commitId,
628
+ parent_commit_ids: [...snapshot.evidence.parentCommitIds],
629
+ blob_id: snapshot.evidence.blobId,
630
+ source_digest: snapshot.evidence.sourceDigest,
631
+ recorded_at: snapshot.evidence.recordedAt,
632
+ is_merge_commit: snapshot.evidence.isMergeCommit,
633
+ segment_ordinal: snapshot.segmentOrdinal,
634
+ assurance: snapshot.assurance,
635
+ semantic_digest: snapshot.projection.semantic_digest,
636
+ transition: sequenceCheckpoint.transition,
637
+ graph,
638
+ source_bindings: sourceBindings(snapshot.evidence, snapshot.projection),
639
+ }));
640
+ }
641
+ const checkpointByCommit = new Map(checkpoints.map((checkpoint) => [checkpoint.commit_id, checkpoint]));
642
+ const timelineEntries = processed.map((snapshot) => {
643
+ const checkpoint = checkpointByCommit.get(snapshot.evidence.commitId);
644
+ return deepFreeze({
645
+ commit_id: snapshot.evidence.commitId,
646
+ parent_commit_ids: [...snapshot.evidence.parentCommitIds],
647
+ blob_id: snapshot.evidence.blobId,
648
+ source_digest: snapshot.evidence.sourceDigest,
649
+ recorded_at: snapshot.evidence.recordedAt,
650
+ is_merge_commit: snapshot.evidence.isMergeCommit,
651
+ validity: snapshot.validity,
652
+ assurance: snapshot.assurance,
653
+ segment_ordinal: snapshot.segmentOrdinal,
654
+ semantic_digest: checkpoint?.semantic_digest ?? null,
655
+ topology_epoch_id: checkpoint?.graph.topology_epoch_id ?? null,
656
+ transition: checkpoint?.transition ?? null,
657
+ graph: checkpoint?.graph ?? null,
658
+ diagnostic_codes: [...snapshot.diagnosticCodes],
659
+ });
660
+ });
661
+ const timelineSegments = [];
662
+ for (let ordinal = 1; ordinal <= segmentOrdinal; ordinal += 1) {
663
+ const commits = checkpoints
664
+ .filter((checkpoint) => checkpoint.segment_ordinal === ordinal)
665
+ .map(({ commit_id }) => commit_id);
666
+ timelineSegments.push({
667
+ ordinal,
668
+ first_commit_id: commits[0],
669
+ last_commit_id: commits.at(-1),
670
+ checkpoint_commit_ids: commits,
671
+ });
672
+ }
673
+ const effectiveCheckpointId = checkpoints.at(-1)?.commit_id ?? null;
674
+ let selectedSnapshot = checkpointByCommit.get(selectedSnapshotCommitId) ?? null;
675
+ const transitionRecordCount = sequenceInputs.filter(({ connected_to_previous }) => connected_to_previous).length;
676
+ const entityEpochCount = sequence.checkpoints.reduce((sum, checkpoint) => sum + checkpoint.entity_value_epochs.length, 0);
677
+ const sourceBindingCount = checkpoints.reduce((sum, checkpoint) => sum + checkpoint.source_bindings.length, 0);
678
+ const timelineOccurrenceCount = checkpoints.reduce((sum, checkpoint) => sum + checkpoint.graph.occurrences.length, 0);
679
+ let outputLimited = false;
680
+ const limitFacts = [
681
+ ["entityValueEpochs", entityEpochCount, limits.entityValueEpochs],
682
+ ["transitionRecords", transitionRecordCount, limits.transitionRecords],
683
+ [
684
+ "renderedGraphOccurrences",
685
+ timelineOccurrenceCount,
686
+ limits.renderedGraphOccurrences,
687
+ ],
688
+ [
689
+ "historicalSourceBindings",
690
+ sourceBindingCount,
691
+ limits.historicalSourceBindings,
692
+ ],
693
+ ];
694
+ for (const [name, actual, limit] of limitFacts) {
695
+ if (actual > limit) {
696
+ outputLimited = true;
697
+ causes.push(causeRecord("hard_limit", "limit", null, name, actual));
698
+ }
699
+ }
700
+ if (selectedSnapshot !== null &&
701
+ (selectedSnapshot.graph.occurrences.length >
702
+ limits.renderedGraphOccurrences ||
703
+ selectedSnapshot.source_bindings.length >
704
+ limits.historicalSourceBindings))
705
+ selectedSnapshot = null;
706
+ let lineageAvailable = evidence.status === "complete" &&
707
+ processed.every(({ validity }) => validity === "semantic_valid") &&
708
+ sequence.causes.length === 0 &&
709
+ checkpoints.length > 0 &&
710
+ checkpoints.at(-1)?.commit_id === evidence.resolvedEndpoint &&
711
+ !outputLimited;
712
+ const states = new Map();
713
+ const frozenEvents = new Map();
714
+ const proofs = [];
715
+ let previousCheckpoint = null;
716
+ let previousProjection = null;
717
+ for (const checkpoint of checkpoints) {
718
+ const projection = valid.find(({ evidence: item }) => item.commitId === checkpoint.commit_id).projection;
719
+ const currentIds = occurrenceIds(checkpoint.graph);
720
+ if (previousCheckpoint !== null && checkpoint.transition.class === "canonical_advance") {
721
+ for (const [id, state] of states) {
722
+ if (state.current && !currentIds.has(id)) {
723
+ states.set(id, {
724
+ current: false,
725
+ occurrence: {
726
+ ...state.occurrence,
727
+ retired_at_commit_id: checkpoint.commit_id,
728
+ },
729
+ });
730
+ }
731
+ }
732
+ const attempt = attempts.get(checkpoint.commit_id);
733
+ if (attempt === undefined || !attempt.summaryVerified || previousProjection === null) {
734
+ lineageAvailable = false;
735
+ causes.push(causeRecord("topology_conflict", "lineage", checkpoint.commit_id));
736
+ }
737
+ else {
738
+ const previousIds = occurrenceIds(previousCheckpoint.graph);
739
+ proofs.push({
740
+ from_commit_id: previousCheckpoint.commit_id,
741
+ to_commit_id: checkpoint.commit_id,
742
+ from_blob_id: previousCheckpoint.blob_id,
743
+ to_blob_id: checkpoint.blob_id,
744
+ from_source_digest: previousCheckpoint.source_digest,
745
+ to_source_digest: checkpoint.source_digest,
746
+ planner_version: HISTORICAL_CANONICAL_ADVANCE_PLANNER_VERSION,
747
+ candidate_semantic_digest: attempt.candidate.candidate.semantic_digest,
748
+ removed_occurrence_ids: sorted([...previousIds].filter((id) => !currentIds.has(id))),
749
+ retained_occurrence_ids: sorted([...previousIds].filter((id) => currentIds.has(id))),
750
+ removed_task_ids: [...attempt.summary.removedTaskIds],
751
+ removed_gate_ids: [...attempt.summary.removedGateIds],
752
+ removed_milestone_ids: [...attempt.summary.removedMilestoneIds],
753
+ removed_work_event_ids: [...attempt.summary.removedWorkEventIds],
754
+ removed_assurance_record_ids: [
755
+ ...attempt.summary.removedAssuranceRecordIds,
756
+ ],
757
+ state_changed_milestone_ids: [
758
+ ...attempt.summary.stateChangedMilestoneIds,
759
+ ],
760
+ });
761
+ }
762
+ }
763
+ else if (previousCheckpoint !== null &&
764
+ checkpoint.transition.class !== "representation_only" &&
765
+ checkpoint.transition.class !== "evidence_extension" &&
766
+ checkpoint.transition.class !== "lifecycle_projection" &&
767
+ checkpoint.transition.class !== "future_plan_edit") {
768
+ lineageAvailable = false;
769
+ }
770
+ for (const occurrence of checkpoint.graph.occurrences) {
771
+ if (occurrence.occurrence_id !== null) {
772
+ states.set(occurrence.occurrence_id, {
773
+ current: true,
774
+ occurrence,
775
+ });
776
+ }
777
+ }
778
+ const taskOccurrenceById = new Map(checkpoint.graph.occurrences
779
+ .filter(({ entity_kind, occurrence_id }) => entity_kind === "task" && occurrence_id !== null)
780
+ .map((occurrence) => [occurrence.source_id, occurrence.occurrence_id]));
781
+ for (const event of projection.semantic.work_events) {
782
+ const taskOccurrenceId = taskOccurrenceById.get(event.task_id);
783
+ if (taskOccurrenceId === undefined) {
784
+ lineageAvailable = false;
785
+ causes.push(causeRecord("topology_conflict", "lineage", checkpoint.commit_id));
786
+ continue;
787
+ }
788
+ const frozen = frozenEvents.get(event.id);
789
+ if (frozen !== undefined &&
790
+ (frozen.task_occurrence_id !== taskOccurrenceId ||
791
+ canonicalJson(frozen.event) !== canonicalJson(event))) {
792
+ lineageAvailable = false;
793
+ causes.push(causeRecord("event_payload_changed", "lineage", checkpoint.commit_id));
794
+ continue;
795
+ }
796
+ frozenEvents.set(event.id, {
797
+ id: event.id,
798
+ task_occurrence_id: taskOccurrenceId,
799
+ first_observed_commit_id: frozen?.first_observed_commit_id ?? checkpoint.commit_id,
800
+ last_observed_commit_id: checkpoint.commit_id,
801
+ event,
802
+ });
803
+ }
804
+ previousCheckpoint = checkpoint;
805
+ previousProjection = projection;
806
+ }
807
+ let lineage = null;
808
+ if (lineageAvailable) {
809
+ const occurrences = [...states.values()].map(({ occurrence }) => occurrence);
810
+ occurrences.sort((left, right) => compareUnicodeScalars(left.occurrence_id, right.occurrence_id));
811
+ const topology = topologyClosedAndAcyclic(occurrences);
812
+ if (topology !== "closed") {
813
+ causes.push(causeRecord(topology, "lineage", evidence.resolvedEndpoint));
814
+ }
815
+ else if (occurrences.length > limits.renderedGraphOccurrences) {
816
+ causes.push(causeRecord("hard_limit", "limit", null, "renderedGraphOccurrences", occurrences.length));
817
+ }
818
+ else {
819
+ lineage = deepFreeze({
820
+ project_id: checkpoints[0].graph.project_id,
821
+ endpoint_checkpoint_id: checkpoints.at(-1).commit_id,
822
+ occurrences,
823
+ current_occurrence_ids: sorted([...states]
824
+ .filter(([, state]) => state.current)
825
+ .map(([id]) => id)),
826
+ retired_occurrence_ids: sorted([...states]
827
+ .filter(([, state]) => !state.current)
828
+ .map(([id]) => id)),
829
+ frozen_work_events: [...frozenEvents.values()].sort((left, right) => compareUnicodeScalars(left.id, right.id)),
830
+ canonical_advance_proofs: proofs,
831
+ });
832
+ }
833
+ }
834
+ const timeline = outputLimited
835
+ ? null
836
+ : deepFreeze({
837
+ entries: timelineEntries,
838
+ segments: timelineSegments,
839
+ });
840
+ const finalCauses = uniqueCauses(causes);
841
+ const status = evidence.status === "complete" &&
842
+ finalCauses.length === 0 && selectedSnapshot !== null &&
843
+ lineage !== null && timeline !== null
844
+ ? "complete"
845
+ : "incomplete";
846
+ return deepFreeze({
847
+ model: HISTORICAL_DAG_MODEL_ID,
848
+ model_version: HISTORICAL_DAG_MODEL_VERSION,
849
+ transition_model_version: HISTORICAL_TRANSITION_MODEL_VERSION,
850
+ status,
851
+ ancestry_profile: "first_parent",
852
+ evidence_status: evidence.status,
853
+ repository_id: evidence.repositoryId,
854
+ repository_relative_path: evidence.repositoryRelativePath,
855
+ repository_read_snapshot_id: evidence.repositoryReadSnapshotId,
856
+ requested_endpoint: evidence.requestedEndpoint,
857
+ resolved_endpoint: evidence.resolvedEndpoint,
858
+ requested_lower_boundary: evidence.requestedLowerBoundary,
859
+ resolved_lower_boundary: evidence.resolvedLowerBoundary,
860
+ effective_checkpoint_id: effectiveCheckpointId,
861
+ selected_snapshot_commit_id: selectedSnapshotCommitId,
862
+ selected_snapshot: selectedSnapshot,
863
+ checkpoints,
864
+ lineage,
865
+ timeline,
866
+ causes: finalCauses,
867
+ limits,
868
+ });
869
+ }
870
+ //# sourceMappingURL=historical-graph.js.map