perttool 0.10.5 → 0.11.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 (241) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/README.md +274 -606
  3. package/dist/application/cli-facade.d.ts +4 -10
  4. package/dist/application/cli-facade.d.ts.map +1 -1
  5. package/dist/application/cli-facade.js +1 -1
  6. package/dist/application/cli-facade.js.map +1 -1
  7. package/dist/application/contract10-candidate.d.ts +33 -0
  8. package/dist/application/contract10-candidate.d.ts.map +1 -0
  9. package/dist/application/contract10-candidate.js +38 -0
  10. package/dist/application/contract10-candidate.js.map +1 -0
  11. package/dist/application/contract10-planning-history.d.ts +4 -0
  12. package/dist/application/contract10-planning-history.d.ts.map +1 -0
  13. package/dist/application/contract10-planning-history.js +28 -0
  14. package/dist/application/contract10-planning-history.js.map +1 -0
  15. package/dist/application/contract10-planning.d.ts +61 -0
  16. package/dist/application/contract10-planning.d.ts.map +1 -0
  17. package/dist/application/contract10-planning.js +165 -0
  18. package/dist/application/contract10-planning.js.map +1 -0
  19. package/dist/application/contract10-runtime.d.ts +185 -0
  20. package/dist/application/contract10-runtime.d.ts.map +1 -0
  21. package/dist/application/contract10-runtime.js +236 -0
  22. package/dist/application/contract10-runtime.js.map +1 -0
  23. package/dist/application/contract8-milestone-acceptance.d.ts +6 -22
  24. package/dist/application/contract8-milestone-acceptance.d.ts.map +1 -1
  25. package/dist/application/contract8-milestone-acceptance.js +21 -99
  26. package/dist/application/contract8-milestone-acceptance.js.map +1 -1
  27. package/dist/application/contract8-milestone-read.d.ts +25 -0
  28. package/dist/application/contract8-milestone-read.d.ts.map +1 -0
  29. package/dist/application/contract8-milestone-read.js +111 -0
  30. package/dist/application/contract8-milestone-read.js.map +1 -0
  31. package/dist/application/contract9-assurance-evaluate.d.ts +3 -0
  32. package/dist/application/contract9-assurance-evaluate.d.ts.map +1 -0
  33. package/dist/application/contract9-assurance-evaluate.js +75 -0
  34. package/dist/application/contract9-assurance-evaluate.js.map +1 -0
  35. package/dist/application/contract9-assurance.d.ts +1 -2
  36. package/dist/application/contract9-assurance.d.ts.map +1 -1
  37. package/dist/application/contract9-assurance.js +2 -56
  38. package/dist/application/contract9-assurance.js.map +1 -1
  39. package/dist/application/contract9-candidate.d.ts +8 -0
  40. package/dist/application/contract9-candidate.d.ts.map +1 -1
  41. package/dist/application/contract9-candidate.js +36 -11
  42. package/dist/application/contract9-candidate.js.map +1 -1
  43. package/dist/application/contract9-mixed-mutation.d.ts +3 -0
  44. package/dist/application/contract9-mixed-mutation.d.ts.map +1 -1
  45. package/dist/application/contract9-mixed-mutation.js +5 -4
  46. package/dist/application/contract9-mixed-mutation.js.map +1 -1
  47. package/dist/application/contract9-temporal.js +1 -1
  48. package/dist/application/contract9-temporal.js.map +1 -1
  49. package/dist/application/target-actuals-analysis.d.ts.map +1 -1
  50. package/dist/application/target-actuals-analysis.js +10 -6
  51. package/dist/application/target-actuals-analysis.js.map +1 -1
  52. package/dist/application/target-temporal-analysis.d.ts +1 -0
  53. package/dist/application/target-temporal-analysis.d.ts.map +1 -1
  54. package/dist/application/target-temporal-analysis.js +5 -1
  55. package/dist/application/target-temporal-analysis.js.map +1 -1
  56. package/dist/cli.js +761 -87
  57. package/dist/cli.js.map +1 -1
  58. package/dist/command/contract10-discovery.d.ts +17 -0
  59. package/dist/command/contract10-discovery.d.ts.map +1 -0
  60. package/dist/command/contract10-discovery.js +304 -0
  61. package/dist/command/contract10-discovery.js.map +1 -0
  62. package/dist/command/contract10-usage.d.ts +24 -0
  63. package/dist/command/contract10-usage.d.ts.map +1 -0
  64. package/dist/command/contract10-usage.js +49 -0
  65. package/dist/command/contract10-usage.js.map +1 -0
  66. package/dist/core/index.d.ts +11 -2
  67. package/dist/core/index.d.ts.map +1 -1
  68. package/dist/core/index.js +3 -1
  69. package/dist/core/index.js.map +1 -1
  70. package/dist/help/assurance-guide.js +1 -1
  71. package/dist/help/assurance-guide.js.map +1 -1
  72. package/dist/help/contract10-guide.d.ts +10 -0
  73. package/dist/help/contract10-guide.d.ts.map +1 -0
  74. package/dist/help/contract10-guide.js +244 -0
  75. package/dist/help/contract10-guide.js.map +1 -0
  76. package/dist/index.d.ts +18 -8
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +8 -5
  79. package/dist/index.js.map +1 -1
  80. package/dist/milestone-acceptance/advance.d.ts +9 -0
  81. package/dist/milestone-acceptance/advance.d.ts.map +1 -1
  82. package/dist/milestone-acceptance/advance.js +29 -2
  83. package/dist/milestone-acceptance/advance.js.map +1 -1
  84. package/dist/milestone-acceptance/mutation.d.ts +2 -0
  85. package/dist/milestone-acceptance/mutation.d.ts.map +1 -1
  86. package/dist/milestone-acceptance/mutation.js +60 -20
  87. package/dist/milestone-acceptance/mutation.js.map +1 -1
  88. package/dist/model/source-result.d.ts +12 -0
  89. package/dist/model/source-result.d.ts.map +1 -0
  90. package/dist/model/source-result.js +16 -0
  91. package/dist/model/source-result.js.map +1 -0
  92. package/dist/mutation/advance-deletion.d.ts +1 -0
  93. package/dist/mutation/advance-deletion.d.ts.map +1 -1
  94. package/dist/mutation/advance-deletion.js +4 -1
  95. package/dist/mutation/advance-deletion.js.map +1 -1
  96. package/dist/mutation/validated-source.d.ts +30 -0
  97. package/dist/mutation/validated-source.d.ts.map +1 -0
  98. package/dist/mutation/validated-source.js +58 -0
  99. package/dist/mutation/validated-source.js.map +1 -0
  100. package/dist/node/planning-token-store.d.ts +3 -0
  101. package/dist/node/planning-token-store.d.ts.map +1 -0
  102. package/dist/node/planning-token-store.js +90 -0
  103. package/dist/node/planning-token-store.js.map +1 -0
  104. package/dist/parser/document-parser.d.ts +1 -0
  105. package/dist/parser/document-parser.d.ts.map +1 -1
  106. package/dist/parser/document-parser.js +1 -1
  107. package/dist/parser/document-parser.js.map +1 -1
  108. package/dist/perttool-vscode-private-0.0.0-d1578b9.vsix +0 -0
  109. package/dist/planning-pool/advance.d.ts +10 -0
  110. package/dist/planning-pool/advance.d.ts.map +1 -0
  111. package/dist/planning-pool/advance.js +158 -0
  112. package/dist/planning-pool/advance.js.map +1 -0
  113. package/dist/planning-pool/format.d.ts +12 -0
  114. package/dist/planning-pool/format.d.ts.map +1 -0
  115. package/dist/planning-pool/format.js +199 -0
  116. package/dist/planning-pool/format.js.map +1 -0
  117. package/dist/planning-pool/history-guard.d.ts +19 -0
  118. package/dist/planning-pool/history-guard.d.ts.map +1 -0
  119. package/dist/planning-pool/history-guard.js +123 -0
  120. package/dist/planning-pool/history-guard.js.map +1 -0
  121. package/dist/planning-pool/history-types.d.ts +174 -0
  122. package/dist/planning-pool/history-types.d.ts.map +1 -0
  123. package/dist/planning-pool/history-types.js +2 -0
  124. package/dist/planning-pool/history-types.js.map +1 -0
  125. package/dist/planning-pool/history.d.ts +11 -0
  126. package/dist/planning-pool/history.d.ts.map +1 -0
  127. package/dist/planning-pool/history.js +977 -0
  128. package/dist/planning-pool/history.js.map +1 -0
  129. package/dist/planning-pool/human.d.ts +9 -0
  130. package/dist/planning-pool/human.d.ts.map +1 -0
  131. package/dist/planning-pool/human.js +305 -0
  132. package/dist/planning-pool/human.js.map +1 -0
  133. package/dist/planning-pool/intent-types.d.ts +76 -0
  134. package/dist/planning-pool/intent-types.d.ts.map +1 -0
  135. package/dist/planning-pool/intent-types.js +2 -0
  136. package/dist/planning-pool/intent-types.js.map +1 -0
  137. package/dist/planning-pool/intent.d.ts +7 -0
  138. package/dist/planning-pool/intent.d.ts.map +1 -0
  139. package/dist/planning-pool/intent.js +590 -0
  140. package/dist/planning-pool/intent.js.map +1 -0
  141. package/dist/planning-pool/observation-types.d.ts +143 -0
  142. package/dist/planning-pool/observation-types.d.ts.map +1 -0
  143. package/dist/planning-pool/observation-types.js +2 -0
  144. package/dist/planning-pool/observation-types.js.map +1 -0
  145. package/dist/planning-pool/observation.d.ts +8 -0
  146. package/dist/planning-pool/observation.d.ts.map +1 -0
  147. package/dist/planning-pool/observation.js +582 -0
  148. package/dist/planning-pool/observation.js.map +1 -0
  149. package/dist/planning-pool/projection.d.ts +28 -0
  150. package/dist/planning-pool/projection.d.ts.map +1 -0
  151. package/dist/planning-pool/projection.js +148 -0
  152. package/dist/planning-pool/projection.js.map +1 -0
  153. package/dist/planning-pool/public-core.d.ts +32 -0
  154. package/dist/planning-pool/public-core.d.ts.map +1 -0
  155. package/dist/planning-pool/public-core.js +122 -0
  156. package/dist/planning-pool/public-core.js.map +1 -0
  157. package/dist/planning-pool/reshape-normalize.d.ts +15 -0
  158. package/dist/planning-pool/reshape-normalize.d.ts.map +1 -0
  159. package/dist/planning-pool/reshape-normalize.js +688 -0
  160. package/dist/planning-pool/reshape-normalize.js.map +1 -0
  161. package/dist/planning-pool/reshape-token.d.ts +20 -0
  162. package/dist/planning-pool/reshape-token.d.ts.map +1 -0
  163. package/dist/planning-pool/reshape-token.js +193 -0
  164. package/dist/planning-pool/reshape-token.js.map +1 -0
  165. package/dist/planning-pool/reshape-types.d.ts +183 -0
  166. package/dist/planning-pool/reshape-types.d.ts.map +1 -0
  167. package/dist/planning-pool/reshape-types.js +2 -0
  168. package/dist/planning-pool/reshape-types.js.map +1 -0
  169. package/dist/planning-pool/reshape.d.ts +13 -0
  170. package/dist/planning-pool/reshape.d.ts.map +1 -0
  171. package/dist/planning-pool/reshape.js +1434 -0
  172. package/dist/planning-pool/reshape.js.map +1 -0
  173. package/dist/planning-pool/source-lexical.d.ts +30 -0
  174. package/dist/planning-pool/source-lexical.d.ts.map +1 -0
  175. package/dist/planning-pool/source-lexical.js +158 -0
  176. package/dist/planning-pool/source-lexical.js.map +1 -0
  177. package/dist/planning-pool/source-types.d.ts +169 -0
  178. package/dist/planning-pool/source-types.d.ts.map +1 -0
  179. package/dist/planning-pool/source-types.js +2 -0
  180. package/dist/planning-pool/source-types.js.map +1 -0
  181. package/dist/planning-pool/source-values.d.ts +16 -0
  182. package/dist/planning-pool/source-values.d.ts.map +1 -0
  183. package/dist/planning-pool/source-values.js +120 -0
  184. package/dist/planning-pool/source-values.js.map +1 -0
  185. package/dist/planning-pool/source.d.ts +18 -0
  186. package/dist/planning-pool/source.d.ts.map +1 -0
  187. package/dist/planning-pool/source.js +811 -0
  188. package/dist/planning-pool/source.js.map +1 -0
  189. package/dist/planning-pool/window-types.d.ts +133 -0
  190. package/dist/planning-pool/window-types.d.ts.map +1 -0
  191. package/dist/planning-pool/window-types.js +2 -0
  192. package/dist/planning-pool/window-types.js.map +1 -0
  193. package/dist/planning-pool/window.d.ts +6 -0
  194. package/dist/planning-pool/window.d.ts.map +1 -0
  195. package/dist/planning-pool/window.js +625 -0
  196. package/dist/planning-pool/window.js.map +1 -0
  197. package/dist/schema/registry.d.ts +1 -1
  198. package/dist/schema/registry.d.ts.map +1 -1
  199. package/dist/schema/registry.js +17 -3
  200. package/dist/schema/registry.js.map +1 -1
  201. package/dist/temporal-schedule/format.d.ts.map +1 -1
  202. package/dist/temporal-schedule/format.js +4 -54
  203. package/dist/temporal-schedule/format.js.map +1 -1
  204. package/dist/temporal-schedule/source-lexical.d.ts +9 -0
  205. package/dist/temporal-schedule/source-lexical.d.ts.map +1 -1
  206. package/dist/temporal-schedule/source-lexical.js +21 -10
  207. package/dist/temporal-schedule/source-lexical.js.map +1 -1
  208. package/dist/temporal-schedule/source.d.ts.map +1 -1
  209. package/dist/temporal-schedule/source.js +3 -13
  210. package/dist/temporal-schedule/source.js.map +1 -1
  211. package/dist/version.d.ts +1 -1
  212. package/dist/version.js +1 -1
  213. package/package.json +1 -1
  214. package/schemas/{Perttool.AdvanceResult.v3.schema.json → Perttool.AdvanceResult.v4.schema.json} +42 -4
  215. package/schemas/Perttool.AgentGuidanceResult.v1.schema.json +1 -1
  216. package/schemas/Perttool.AnalysisResult.v7.schema.json +1 -1
  217. package/schemas/Perttool.CheckResult.v6.schema.json +1 -1
  218. package/schemas/Perttool.CliError.v1.schema.json +1 -1
  219. package/schemas/Perttool.CommandHelpResult.v1.schema.json +2 -2
  220. package/schemas/Perttool.ExportResult.v1.schema.json +1 -1
  221. package/schemas/Perttool.FormatResult.v1.schema.json +1 -1
  222. package/schemas/Perttool.GuideResult.v1.schema.json +1 -1
  223. package/schemas/Perttool.HistoricalGraphResult.v1.schema.json +1 -1
  224. package/schemas/Perttool.ImportResult.v1.schema.json +1 -1
  225. package/schemas/Perttool.InitResult.v1.schema.json +1 -1
  226. package/schemas/Perttool.MilestoneAcceptanceMigrationResult.v1.schema.json +1 -1
  227. package/schemas/Perttool.MilestoneAcceptanceResult.v1.schema.json +1 -1
  228. package/schemas/Perttool.MutationResult.v6.schema.json +1 -1
  229. package/schemas/Perttool.NextResult.v8.schema.json +7 -4
  230. package/schemas/Perttool.PlanAssuranceResult.v2.schema.json +1 -1
  231. package/schemas/Perttool.PlanningMutationResult.v1.schema.json +567 -0
  232. package/schemas/Perttool.PlanningObservationRequest.v1.schema.json +6 -0
  233. package/schemas/Perttool.PlanningPoolResult.v1.schema.json +469 -0
  234. package/schemas/Perttool.PlanningReshapePreflightResult.v1.schema.json +906 -0
  235. package/schemas/Perttool.PlanningReshapeRequest.v1.schema.json +19 -0
  236. package/schemas/Perttool.ProjectHistoryResult.v1.schema.json +1 -1
  237. package/schemas/Perttool.ProjectResult.v5.schema.json +1 -1
  238. package/schemas/Perttool.SchemaResult.v1.schema.json +1 -1
  239. package/schemas/{Perttool.UnitMigrationResult.v4.schema.json → Perttool.UnitMigrationResult.v5.schema.json} +42 -17
  240. package/schemas/Perttool.VelocityObservationResult.v1.schema.json +1 -1
  241. package/schemas/Perttool.WindowMutationRequest.v1.schema.json +6 -0
@@ -0,0 +1,688 @@
1
+ import { sha256DigestUtf8 } from "../model/sha256.js";
2
+ export const PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION = "Perttool.PlanningReshapeRequest.v1";
3
+ export const PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2 = "Perttool.PlanningReshapeRequest.v2";
4
+ export const PLANNING_RESHAPE_NORMALIZATION_CONTRACT = "perttool.planning-reshape-normalization@1";
5
+ export const PLANNING_RESHAPE_NORMALIZATION_CONTRACT_2 = "perttool.planning-reshape-normalization@2";
6
+ export const PLANNING_RESHAPE_REQUEST_UTF8_LIMIT = 8_388_608;
7
+ export const PLANNING_RESHAPE_NORMALIZED_LIMITS = Object.freeze({
8
+ affectedWorks: 2_048,
9
+ semanticRows: 50_000,
10
+ relationshipDispositions: 200_000,
11
+ });
12
+ function diagnostic(message, code = "PTPOOL-110") {
13
+ return Object.freeze({
14
+ code,
15
+ severity: "error",
16
+ message,
17
+ data: Object.freeze({}),
18
+ });
19
+ }
20
+ function isRecord(value) {
21
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22
+ }
23
+ function closedRecord(value, required, optional, label, diagnostics) {
24
+ if (!isRecord(value)) {
25
+ diagnostics.push(diagnostic(`${label} must be an object`));
26
+ return null;
27
+ }
28
+ const allowed = new Set([...required, ...optional]);
29
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
30
+ const missing = required.filter((key) => !Object.hasOwn(value, key));
31
+ if (unknown.length > 0)
32
+ diagnostics.push(diagnostic(`${label} has unknown fields: ${unknown.join(", ")}`));
33
+ if (missing.length > 0)
34
+ diagnostics.push(diagnostic(`${label} is missing fields: ${missing.join(", ")}`));
35
+ return unknown.length === 0 && missing.length === 0 ? value : null;
36
+ }
37
+ function stringValue(value, label, diagnostics, nonEmpty = true) {
38
+ if (typeof value !== "string" || (nonEmpty && value.length === 0)) {
39
+ diagnostics.push(diagnostic(`${label} must be ${nonEmpty ? "a non-empty" : "a"} string`));
40
+ return null;
41
+ }
42
+ return value;
43
+ }
44
+ function integerValue(value, label, diagnostics) {
45
+ if (!Number.isSafeInteger(value) || value < 0) {
46
+ diagnostics.push(diagnostic(`${label} must be a non-negative safe integer`));
47
+ return null;
48
+ }
49
+ return value;
50
+ }
51
+ const qualifiedIdPattern = /^[A-Za-z][A-Za-z0-9_-]*::[A-Za-z][A-Za-z0-9_-]*$/u;
52
+ const elementIdPattern = /^[A-Za-z][A-Za-z0-9_-]*$/u;
53
+ const digestPattern = /^sha256:[0-9a-f]{64}$/u;
54
+ function qualifiedId(value, label, diagnostics) {
55
+ const result = stringValue(value, label, diagnostics);
56
+ if (result !== null && !qualifiedIdPattern.test(result)) {
57
+ diagnostics.push(diagnostic(`${label} must be a fully qualified planning identity`));
58
+ return null;
59
+ }
60
+ return result;
61
+ }
62
+ function enumValue(value, values, label, diagnostics) {
63
+ if (typeof value !== "string" || !values.includes(value)) {
64
+ diagnostics.push(diagnostic(`${label} has an unknown value`));
65
+ return null;
66
+ }
67
+ return value;
68
+ }
69
+ function nullableQualifiedId(value, label, diagnostics) {
70
+ return value === null ? null : qualifiedId(value, label, diagnostics) ?? undefined;
71
+ }
72
+ function parseCreatedWork(value, diagnostics) {
73
+ const record = closedRecord(value, ["work_id", "title", "insert_after_work_id"], [], "created Work", diagnostics);
74
+ if (record === null)
75
+ return null;
76
+ const workId = qualifiedId(record["work_id"], "created Work work_id", diagnostics);
77
+ const title = stringValue(record["title"], "created Work title", diagnostics);
78
+ const anchor = nullableQualifiedId(record["insert_after_work_id"], "created Work insertion anchor", diagnostics);
79
+ return workId === null || title === null || anchor === undefined
80
+ ? null
81
+ : Object.freeze({ work_id: workId, title, insert_after_work_id: anchor });
82
+ }
83
+ function parseWorkTitleDisposition(value, diagnostics) {
84
+ const record = closedRecord(value, ["work_id", "title"], [], "Work title disposition", diagnostics);
85
+ if (record === null)
86
+ return null;
87
+ const workId = qualifiedId(record["work_id"], "Work title disposition work_id", diagnostics);
88
+ const title = stringValue(record["title"], "Work title disposition title", diagnostics);
89
+ return workId === null || title === null
90
+ ? null
91
+ : Object.freeze({ work_id: workId, title });
92
+ }
93
+ function parseExistingOrigin(record, diagnostics) {
94
+ const checked = closedRecord(record, ["kind", "work_id", "start_utf16", "end_utf16", "source_text"], [], "existing semantic origin", diagnostics);
95
+ if (checked === null || checked["kind"] !== "existing")
96
+ return null;
97
+ const workId = qualifiedId(checked["work_id"], "semantic origin work_id", diagnostics);
98
+ const start = integerValue(checked["start_utf16"], "semantic origin start_utf16", diagnostics);
99
+ const end = integerValue(checked["end_utf16"], "semantic origin end_utf16", diagnostics);
100
+ const sourceText = stringValue(checked["source_text"], "semantic origin source_text", diagnostics, false);
101
+ if (start !== null && end !== null && end < start)
102
+ diagnostics.push(diagnostic("semantic origin end_utf16 precedes start_utf16"));
103
+ return workId === null || start === null || end === null || sourceText === null || end < start
104
+ ? null
105
+ : Object.freeze({ kind: "existing", work_id: workId, start_utf16: start, end_utf16: end, source_text: sourceText });
106
+ }
107
+ function parseCreatedOrigin(record, diagnostics) {
108
+ const checked = closedRecord(record, ["kind", "source_text", "asserted_new_meaning"], [], "created semantic origin", diagnostics);
109
+ if (checked === null || checked["kind"] !== "created")
110
+ return null;
111
+ const sourceText = stringValue(checked["source_text"], "created semantic origin source_text", diagnostics, false);
112
+ if (checked["asserted_new_meaning"] !== true)
113
+ diagnostics.push(diagnostic("created semantic origin requires asserted_new_meaning true"));
114
+ return sourceText === null || checked["asserted_new_meaning"] !== true
115
+ ? null
116
+ : Object.freeze({ kind: "created", source_text: sourceText, asserted_new_meaning: true });
117
+ }
118
+ function parseOrigin(value, diagnostics) {
119
+ if (!isRecord(value)) {
120
+ diagnostics.push(diagnostic("semantic origin must be an object"));
121
+ return null;
122
+ }
123
+ if (value["kind"] === "existing")
124
+ return parseExistingOrigin(value, diagnostics);
125
+ if (value["kind"] === "created")
126
+ return parseCreatedOrigin(value, diagnostics);
127
+ diagnostics.push(diagnostic("semantic origin has an unknown kind"));
128
+ return null;
129
+ }
130
+ function parseDestination(value, diagnostics) {
131
+ if (!isRecord(value)) {
132
+ diagnostics.push(diagnostic("semantic destination must be an object"));
133
+ return null;
134
+ }
135
+ if (value["kind"] === "work") {
136
+ const record = closedRecord(value, ["kind", "work_id", "position", "text"], [], "Work semantic destination", diagnostics);
137
+ if (record === null)
138
+ return null;
139
+ const workId = qualifiedId(record["work_id"], "semantic destination work_id", diagnostics);
140
+ const position = integerValue(record["position"], "semantic destination position", diagnostics);
141
+ const text = stringValue(record["text"], "semantic destination text", diagnostics, false);
142
+ return workId === null || position === null || text === null
143
+ ? null
144
+ : Object.freeze({ kind: "work", work_id: workId, position, text });
145
+ }
146
+ if (value["kind"] === "discard") {
147
+ const record = closedRecord(value, ["kind"], ["reason"], "discard destination", diagnostics);
148
+ if (record === null)
149
+ return null;
150
+ const reason = record["reason"] === undefined
151
+ ? undefined
152
+ : stringValue(record["reason"], "discard reason", diagnostics, false) ?? undefined;
153
+ return record["reason"] !== undefined && reason === undefined
154
+ ? null
155
+ : Object.freeze({ kind: "discard", ...(reason === undefined ? {} : { reason }) });
156
+ }
157
+ diagnostics.push(diagnostic("semantic destination has an unknown kind"));
158
+ return null;
159
+ }
160
+ function parseSemanticElement(value, diagnostics) {
161
+ const record = closedRecord(value, ["element_id", "origin", "destination"], [], "semantic element", diagnostics);
162
+ if (record === null)
163
+ return null;
164
+ const elementId = stringValue(record["element_id"], "semantic element_id", diagnostics);
165
+ if (elementId !== null && !elementIdPattern.test(elementId)) {
166
+ diagnostics.push(diagnostic("semantic element_id is invalid"));
167
+ }
168
+ const origin = parseOrigin(record["origin"], diagnostics);
169
+ const destination = parseDestination(record["destination"], diagnostics);
170
+ return elementId === null || !elementIdPattern.test(elementId) || origin === null || destination === null
171
+ ? null
172
+ : Object.freeze({ element_id: elementId, origin, destination });
173
+ }
174
+ function parseEntityDisposition(value, diagnostics) {
175
+ const record = closedRecord(value, ["entity_kind", "entity_id", "action"], ["reason"], "planning entity disposition", diagnostics);
176
+ if (record === null)
177
+ return null;
178
+ const kind = enumValue(record["entity_kind"], ["event", "activity"], "planning entity kind", diagnostics);
179
+ const id = qualifiedId(record["entity_id"], "planning entity id", diagnostics);
180
+ const action = enumValue(record["action"], ["retain", "create", "project", "defer", "discard"], "planning entity action", diagnostics);
181
+ const reason = record["reason"] === undefined ? undefined : stringValue(record["reason"], "planning entity reason", diagnostics, false) ?? undefined;
182
+ return kind === null || id === null || action === null || (record["reason"] !== undefined && reason === undefined)
183
+ ? null
184
+ : Object.freeze({ entity_kind: kind, entity_id: id, action, ...(reason === undefined ? {} : { reason }) });
185
+ }
186
+ function parseAssociationDisposition(value, diagnostics) {
187
+ const record = closedRecord(value, ["entity_kind", "entity_id", "origin_work_id", "destination_work_id"], [], "association disposition", diagnostics);
188
+ if (record === null)
189
+ return null;
190
+ const kind = enumValue(record["entity_kind"], ["event", "activity"], "association entity kind", diagnostics);
191
+ const entityId = qualifiedId(record["entity_id"], "association entity id", diagnostics);
192
+ const origin = nullableQualifiedId(record["origin_work_id"], "association origin Work", diagnostics);
193
+ const destination = nullableQualifiedId(record["destination_work_id"], "association destination Work", diagnostics);
194
+ if (origin === null && destination === null)
195
+ diagnostics.push(diagnostic("association disposition must retain one endpoint"));
196
+ return kind === null || entityId === null || origin === undefined || destination === undefined || (origin === null && destination === null)
197
+ ? null
198
+ : Object.freeze({ entity_kind: kind, entity_id: entityId, origin_work_id: origin, destination_work_id: destination });
199
+ }
200
+ function parseProjectionLinkDisposition(value, diagnostics) {
201
+ const record = closedRecord(value, ["strict_kind", "strict_id", "origin_work_id", "destination_work_id"], [], "projection-link disposition", diagnostics);
202
+ if (record === null)
203
+ return null;
204
+ const kind = enumValue(record["strict_kind"], ["milestone", "task"], "strict entity kind", diagnostics);
205
+ const strictId = qualifiedId(record["strict_id"], "strict entity id", diagnostics);
206
+ const origin = nullableQualifiedId(record["origin_work_id"], "projection-link origin Work", diagnostics);
207
+ const destination = nullableQualifiedId(record["destination_work_id"], "projection-link destination Work", diagnostics);
208
+ if (origin === null && destination === null)
209
+ diagnostics.push(diagnostic("projection-link disposition must retain one endpoint"));
210
+ return kind === null || strictId === null || origin === undefined || destination === undefined || (origin === null && destination === null)
211
+ ? null
212
+ : Object.freeze({ strict_kind: kind, strict_id: strictId, origin_work_id: origin, destination_work_id: destination });
213
+ }
214
+ function parseStringSet(value, label, diagnostics) {
215
+ if (!Array.isArray(value)) {
216
+ diagnostics.push(diagnostic(`${label} must be an array`));
217
+ return null;
218
+ }
219
+ const result = value.map((item) => qualifiedId(item, label, diagnostics));
220
+ return result.some((item) => item === null)
221
+ ? null
222
+ : Object.freeze(result.sort(compareUnicodeScalars));
223
+ }
224
+ function optionalQualifiedId(record, field, label, diagnostics) {
225
+ return record[field] === undefined
226
+ ? undefined
227
+ : qualifiedId(record[field], label, diagnostics) ?? null;
228
+ }
229
+ function optionalQualifiedIdSet(record, field, label, diagnostics) {
230
+ return record[field] === undefined
231
+ ? undefined
232
+ : parseStringSet(record[field], label, diagnostics) ?? null;
233
+ }
234
+ function optionalText(record, field, label, diagnostics) {
235
+ return record[field] === undefined
236
+ ? undefined
237
+ : stringValue(record[field], label, diagnostics, false) ?? null;
238
+ }
239
+ function validateDependencyDispositionShape(action, finalDependent, finalPrerequisite, representedBy, diagnostics) {
240
+ validateDependencyEndpoints(action, finalDependent, finalPrerequisite, diagnostics);
241
+ validateDependencyOwners(action, representedBy, diagnostics);
242
+ }
243
+ function validateDependencyEndpoints(action, finalDependent, finalPrerequisite, diagnostics) {
244
+ if (action === "retain" && (finalDependent !== undefined || finalPrerequisite !== undefined)) {
245
+ diagnostics.push(diagnostic("retained dependency cannot declare final endpoints"));
246
+ }
247
+ if (action !== null && action !== "rebind" && (finalDependent !== undefined || finalPrerequisite !== undefined)) {
248
+ diagnostics.push(diagnostic("only a rebound dependency may declare final endpoints"));
249
+ }
250
+ if (action === "rebind" && (typeof finalDependent !== "string" || typeof finalPrerequisite !== "string")) {
251
+ diagnostics.push(diagnostic("rebound dependency requires both final endpoints"));
252
+ }
253
+ }
254
+ function validateDependencyOwners(action, representedBy, diagnostics) {
255
+ if (action === "represented" && (!Array.isArray(representedBy) || representedBy.length === 0)) {
256
+ diagnostics.push(diagnostic("represented dependency requires final owners"));
257
+ }
258
+ if (action !== null && action !== "represented" && representedBy !== undefined) {
259
+ diagnostics.push(diagnostic("only a represented dependency may cite final owners"));
260
+ }
261
+ }
262
+ function hasNull(values) {
263
+ return values.some((value) => value === null);
264
+ }
265
+ function parseDependencyDisposition(value, diagnostics) {
266
+ const record = closedRecord(value, ["dependent_work_id", "prerequisite_work_id", "action"], ["final_dependent_work_id", "final_prerequisite_work_id", "represented_by", "reason"], "dependency disposition", diagnostics);
267
+ if (record === null)
268
+ return null;
269
+ const dependent = qualifiedId(record["dependent_work_id"], "dependent Work", diagnostics);
270
+ const prerequisite = qualifiedId(record["prerequisite_work_id"], "prerequisite Work", diagnostics);
271
+ const action = enumValue(record["action"], ["retain", "create", "rebind", "represented", "no_longer_required"], "dependency action", diagnostics);
272
+ const finalDependent = optionalQualifiedId(record, "final_dependent_work_id", "final dependent Work", diagnostics);
273
+ const finalPrerequisite = optionalQualifiedId(record, "final_prerequisite_work_id", "final prerequisite Work", diagnostics);
274
+ const representedBy = optionalQualifiedIdSet(record, "represented_by", "represented owner", diagnostics);
275
+ const reason = optionalText(record, "reason", "dependency reason", diagnostics);
276
+ validateDependencyDispositionShape(action, finalDependent, finalPrerequisite, representedBy, diagnostics);
277
+ if (hasNull([dependent, prerequisite, action, finalDependent, finalPrerequisite, representedBy, reason]))
278
+ return null;
279
+ return Object.freeze({
280
+ dependent_work_id: dependent,
281
+ prerequisite_work_id: prerequisite,
282
+ action: action,
283
+ ...(finalDependent === undefined ? {} : { final_dependent_work_id: finalDependent }),
284
+ ...(finalPrerequisite === undefined ? {} : { final_prerequisite_work_id: finalPrerequisite }),
285
+ ...(representedBy === undefined ? {} : { represented_by: representedBy }),
286
+ ...(reason === undefined ? {} : { reason: reason }),
287
+ });
288
+ }
289
+ function parseWindowMembershipDisposition(value, diagnostics) {
290
+ const record = closedRecord(value, ["window_id", "origin_work_id", "destination_work_id"], [], "Window membership disposition", diagnostics);
291
+ if (record === null)
292
+ return null;
293
+ const windowId = qualifiedId(record["window_id"], "Window id", diagnostics);
294
+ const origin = qualifiedId(record["origin_work_id"], "Window origin Work", diagnostics);
295
+ const destination = nullableQualifiedId(record["destination_work_id"], "Window destination Work", diagnostics);
296
+ return windowId === null || origin === null || destination === undefined
297
+ ? null
298
+ : Object.freeze({ window_id: windowId, origin_work_id: origin, destination_work_id: destination });
299
+ }
300
+ function parseResidualAction(value, diagnostics) {
301
+ const record = closedRecord(value, ["work_id", "text"], [], "residual-description action", diagnostics);
302
+ if (record === null)
303
+ return null;
304
+ const workId = qualifiedId(record["work_id"], "residual-description Work", diagnostics);
305
+ const text = stringValue(record["text"], "residual-description text", diagnostics, false);
306
+ return workId === null || text === null ? null : Object.freeze({ work_id: workId, text });
307
+ }
308
+ function parseArray(value, label, diagnostics, parse) {
309
+ if (!Array.isArray(value)) {
310
+ diagnostics.push(diagnostic(`${label} must be an array`));
311
+ return null;
312
+ }
313
+ const result = value.map((item) => parse(item, diagnostics));
314
+ return result.some((item) => item === null)
315
+ ? null
316
+ : Object.freeze(result);
317
+ }
318
+ function unique(values, key, label, diagnostics) {
319
+ const seen = new Set();
320
+ for (const value of values) {
321
+ const identity = key(value);
322
+ if (seen.has(identity)) {
323
+ diagnostics.push(diagnostic(`Duplicate ${label} ${identity}`));
324
+ return false;
325
+ }
326
+ seen.add(identity);
327
+ }
328
+ return true;
329
+ }
330
+ function compareUnicodeScalars(left, right) {
331
+ const leftPoints = Array.from(left, (value) => value.codePointAt(0));
332
+ const rightPoints = Array.from(right, (value) => value.codePointAt(0));
333
+ const length = Math.min(leftPoints.length, rightPoints.length);
334
+ for (let index = 0; index < length; index += 1) {
335
+ const difference = leftPoints[index] - rightPoints[index];
336
+ if (difference !== 0)
337
+ return difference;
338
+ }
339
+ return leftPoints.length - rightPoints.length;
340
+ }
341
+ function sorted(values, key) {
342
+ return Object.freeze([...values].sort((left, right) => compareUnicodeScalars(key(left), key(right))));
343
+ }
344
+ function canonicalValue(value) {
345
+ if (value === null || typeof value !== "object")
346
+ return JSON.stringify(value);
347
+ if (Array.isArray(value))
348
+ return `[${value.map(canonicalValue).join(",")}]`;
349
+ const record = value;
350
+ const keys = Object.keys(record).sort(compareUnicodeScalars);
351
+ const fields = keys.map((key) => `${JSON.stringify(key)}:${canonicalValue(record[key])}`);
352
+ return `{${fields.join(",")}}`;
353
+ }
354
+ function rawRequestBytes(input) {
355
+ try {
356
+ const text = typeof input === "string" ? input : JSON.stringify(input);
357
+ return new TextEncoder().encode(text).byteLength;
358
+ }
359
+ catch {
360
+ return null;
361
+ }
362
+ }
363
+ function parsedInput(input, diagnostics) {
364
+ if (typeof input !== "string")
365
+ return input;
366
+ try {
367
+ return JSON.parse(input);
368
+ }
369
+ catch {
370
+ diagnostics.push(diagnostic("Planning reshape request is not valid JSON"));
371
+ return null;
372
+ }
373
+ }
374
+ function arrayLength(record, field) {
375
+ const value = record[field];
376
+ return Array.isArray(value) ? value.length : 0;
377
+ }
378
+ function exceedsNormalizedLimits(record, diagnostics) {
379
+ const relationCount = [
380
+ "planning_entity_dispositions",
381
+ "association_dispositions",
382
+ "projection_link_dispositions",
383
+ "dependency_dispositions",
384
+ "window_membership_dispositions",
385
+ ].reduce((total, field) => total + arrayLength(record, field), 0);
386
+ const violations = [
387
+ arrayLength(record, "affected_work_ids") > PLANNING_RESHAPE_NORMALIZED_LIMITS.affectedWorks,
388
+ arrayLength(record, "semantic_elements") > PLANNING_RESHAPE_NORMALIZED_LIMITS.semanticRows,
389
+ relationCount > PLANNING_RESHAPE_NORMALIZED_LIMITS.relationshipDispositions,
390
+ ];
391
+ if (!violations.some(Boolean))
392
+ return false;
393
+ diagnostics.push(diagnostic("Planning reshape request count limit exceeded", "PTPOOL-115"));
394
+ return true;
395
+ }
396
+ function failedNormalization(diagnostics) {
397
+ return Object.freeze({
398
+ ok: false,
399
+ request: null,
400
+ canonicalUtf8: null,
401
+ preflightHash: null,
402
+ diagnostics: Object.freeze(diagnostics),
403
+ });
404
+ }
405
+ const requestFieldsV1 = Object.freeze([
406
+ "request_schema_version",
407
+ "normalization_contract",
408
+ "source_digest",
409
+ "intent",
410
+ "affected_work_ids",
411
+ "created_works",
412
+ "removed_work_ids",
413
+ "semantic_elements",
414
+ "planning_entity_dispositions",
415
+ "association_dispositions",
416
+ "projection_link_dispositions",
417
+ "dependency_dispositions",
418
+ "window_membership_dispositions",
419
+ "final_work_order",
420
+ "add_residual_description",
421
+ "strict_fragment",
422
+ "window_close",
423
+ ]);
424
+ const requestFieldsV2 = Object.freeze([
425
+ ...requestFieldsV1,
426
+ "work_title_dispositions",
427
+ ]);
428
+ function planningRequestRecord(input, diagnostics) {
429
+ const byteLength = rawRequestBytes(input);
430
+ if (byteLength === null)
431
+ diagnostics.push(diagnostic("Planning reshape request cannot be serialized"));
432
+ if (byteLength !== null && byteLength > PLANNING_RESHAPE_REQUEST_UTF8_LIMIT) {
433
+ diagnostics.push(diagnostic(`Planning reshape request exceeds ${PLANNING_RESHAPE_REQUEST_UTF8_LIMIT} UTF-8 bytes`, "PTPOOL-115"));
434
+ }
435
+ const parsed = parsedInput(input, diagnostics);
436
+ const version = isRecord(parsed) ? parsed["request_schema_version"] : null;
437
+ const fields = version === PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2
438
+ ? requestFieldsV2
439
+ : requestFieldsV1;
440
+ const record = closedRecord(parsed, fields, [], "Planning reshape request", diagnostics);
441
+ if (record !== null)
442
+ exceedsNormalizedLimits(record, diagnostics);
443
+ return diagnostics.length === 0 ? record : null;
444
+ }
445
+ function nullableStringValue(value, label, diagnostics) {
446
+ return value === null ? null : stringValue(value, label, diagnostics) ?? undefined;
447
+ }
448
+ function parseExistingCarryTarget(value, diagnostics) {
449
+ const record = closedRecord(value, ["kind", "window_id"], [], "existing carry-over target", diagnostics);
450
+ const windowId = record === null ? null : qualifiedId(record["window_id"], "carry-over Window", diagnostics);
451
+ return record === null || record["kind"] !== "existing" || windowId === null
452
+ ? null
453
+ : Object.freeze({ kind: "existing", window_id: windowId });
454
+ }
455
+ function parseNewCarryTarget(value, diagnostics) {
456
+ const record = closedRecord(value, ["kind", "window_id", "title", "objective", "start", "end"], [], "new carry-over target", diagnostics);
457
+ if (record === null || record["kind"] !== "new")
458
+ return null;
459
+ const windowId = qualifiedId(record["window_id"], "new carry-over Window", diagnostics);
460
+ const title = stringValue(record["title"], "new carry-over Window title", diagnostics);
461
+ const objective = stringValue(record["objective"], "new carry-over Window objective", diagnostics);
462
+ const start = nullableStringValue(record["start"], "new carry-over Window start", diagnostics);
463
+ const end = nullableStringValue(record["end"], "new carry-over Window end", diagnostics);
464
+ return windowId === null || title === null || objective === null || start === undefined || end === undefined
465
+ ? null
466
+ : Object.freeze({ kind: "new", window_id: windowId, title, objective, start, end });
467
+ }
468
+ function parseCarryTarget(value, diagnostics) {
469
+ if (value === null)
470
+ return null;
471
+ const kind = isRecord(value) ? value["kind"] : undefined;
472
+ if (kind === "existing")
473
+ return parseExistingCarryTarget(value, diagnostics);
474
+ if (kind === "new")
475
+ return parseNewCarryTarget(value, diagnostics);
476
+ diagnostics.push(diagnostic("carry_over_target must be null, existing, or new"));
477
+ return null;
478
+ }
479
+ function parseWindowClose(value, diagnostics) {
480
+ if (value === null)
481
+ return null;
482
+ const record = closedRecord(value, ["window_id", "objective_disposition", "carry_over_work_ids", "carry_over_target"], [], "Window close", diagnostics);
483
+ if (record === null)
484
+ return null;
485
+ const windowId = qualifiedId(record["window_id"], "closed Window", diagnostics);
486
+ const works = parseStringSet(record["carry_over_work_ids"], "carry-over Work", diagnostics);
487
+ const target = parseCarryTarget(record["carry_over_target"], diagnostics);
488
+ if (record["objective_disposition"] !== "discard")
489
+ diagnostics.push(diagnostic("Window close must discard its objective"));
490
+ if (works !== null && (works.length === 0) !== (target === null)) {
491
+ diagnostics.push(diagnostic("Window close requires a target exactly when carry-over is non-empty"));
492
+ }
493
+ return windowId === null || works === null || record["objective_disposition"] !== "discard"
494
+ ? null
495
+ : Object.freeze({
496
+ window_id: windowId,
497
+ objective_disposition: "discard",
498
+ carry_over_work_ids: works,
499
+ carry_over_target: target,
500
+ });
501
+ }
502
+ function parseStrictFragment(value, diagnostics) {
503
+ if (value === null)
504
+ return null;
505
+ const base = closedRecord(value, ["kind"], ["event_ids", "activity_ids", "task_ids", "milestone_ids"], "strict fragment", diagnostics);
506
+ if (base === null)
507
+ return null;
508
+ const kind = enumValue(base["kind"], ["project", "defer"], "strict fragment kind", diagnostics);
509
+ if (kind === "project") {
510
+ const exact = closedRecord(value, ["kind", "event_ids", "activity_ids"], [], "projection strict fragment", diagnostics);
511
+ if (exact === null)
512
+ return null;
513
+ const events = parseStringSet(exact["event_ids"], "projection Event", diagnostics);
514
+ const activities = parseStringSet(exact["activity_ids"], "projection Activity", diagnostics);
515
+ return events === null || activities === null
516
+ ? null
517
+ : Object.freeze({ kind, event_ids: events, activity_ids: activities });
518
+ }
519
+ if (kind === "defer") {
520
+ const exact = closedRecord(value, ["kind", "task_ids", "milestone_ids"], [], "deferral strict fragment", diagnostics);
521
+ if (exact === null)
522
+ return null;
523
+ const tasks = parseStringSet(exact["task_ids"], "deferral Task", diagnostics);
524
+ const milestones = parseStringSet(exact["milestone_ids"], "deferral Milestone", diagnostics);
525
+ return tasks === null || milestones === null
526
+ ? null
527
+ : Object.freeze({ kind, task_ids: tasks, milestone_ids: milestones });
528
+ }
529
+ return null;
530
+ }
531
+ function parseFinalWorkOrder(value, diagnostics) {
532
+ if (!Array.isArray(value)) {
533
+ diagnostics.push(diagnostic("final_work_order must be an array"));
534
+ return null;
535
+ }
536
+ return Object.freeze(value.map((item) => qualifiedId(item, "final_work_order", diagnostics)).filter((item) => item !== null));
537
+ }
538
+ function parseRequestFields(record, diagnostics) {
539
+ const version = record["request_schema_version"] === PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2
540
+ ? PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2
541
+ : PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION;
542
+ const sourceDigest = stringValue(record["source_digest"], "source_digest", diagnostics);
543
+ if (sourceDigest !== null && !digestPattern.test(sourceDigest))
544
+ diagnostics.push(diagnostic("source_digest must be a lowercase SHA-256 identity"));
545
+ const intent = enumValue(record["intent"], ["reshape", "project", "defer", "archive", "composite"], "reshape intent", diagnostics);
546
+ const affected = parseStringSet(record["affected_work_ids"], "affected Work", diagnostics);
547
+ const created = parseArray(record["created_works"], "created_works", diagnostics, parseCreatedWork);
548
+ const titles = version === PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2
549
+ ? parseArray(record["work_title_dispositions"], "work_title_dispositions", diagnostics, parseWorkTitleDisposition)
550
+ : Object.freeze([]);
551
+ const removed = parseStringSet(record["removed_work_ids"], "removed Work", diagnostics);
552
+ const elements = parseArray(record["semantic_elements"], "semantic_elements", diagnostics, parseSemanticElement);
553
+ const entities = parseArray(record["planning_entity_dispositions"], "planning_entity_dispositions", diagnostics, parseEntityDisposition);
554
+ const associations = parseArray(record["association_dispositions"], "association_dispositions", diagnostics, parseAssociationDisposition);
555
+ const links = parseArray(record["projection_link_dispositions"], "projection_link_dispositions", diagnostics, parseProjectionLinkDisposition);
556
+ const dependencies = parseArray(record["dependency_dispositions"], "dependency_dispositions", diagnostics, parseDependencyDisposition);
557
+ const memberships = parseArray(record["window_membership_dispositions"], "window_membership_dispositions", diagnostics, parseWindowMembershipDisposition);
558
+ const order = parseFinalWorkOrder(record["final_work_order"], diagnostics);
559
+ const residual = parseArray(record["add_residual_description"], "add_residual_description", diagnostics, parseResidualAction);
560
+ const strictFragment = parseStrictFragment(record["strict_fragment"], diagnostics);
561
+ const windowClose = parseWindowClose(record["window_close"], diagnostics);
562
+ if (hasNull([sourceDigest, intent, affected, created, titles, removed, elements, entities, associations, links, dependencies, memberships, order, residual]))
563
+ return null;
564
+ return Object.freeze({
565
+ version, sourceDigest: sourceDigest, intent: intent, affected: affected, created: created, titles: titles,
566
+ removed: removed, elements: elements, entities: entities, associations: associations,
567
+ links: links, dependencies: dependencies, memberships: memberships, order: order, residual: residual,
568
+ strictFragment, windowClose,
569
+ });
570
+ }
571
+ function validateRequestUniqueness(fields, diagnostics) {
572
+ unique(fields.affected, (item) => item, "affected Work", diagnostics);
573
+ unique(fields.created, (item) => item.work_id, "created Work", diagnostics);
574
+ unique(fields.titles, (item) => item.work_id, "Work title disposition", diagnostics);
575
+ unique(fields.removed, (item) => item, "removed Work", diagnostics);
576
+ unique(fields.elements, (item) => item.element_id, "semantic element", diagnostics);
577
+ unique(fields.entities, (item) => `${item.entity_kind}:${item.entity_id}`, "planning entity disposition", diagnostics);
578
+ unique(fields.associations, (item) => `${item.entity_kind}:${item.entity_id}:${item.origin_work_id ?? "-"}:${item.destination_work_id ?? "-"}`, "association disposition", diagnostics);
579
+ unique(fields.links, (item) => `${item.strict_kind}:${item.strict_id}:${item.origin_work_id ?? "-"}:${item.destination_work_id ?? "-"}`, "projection-link disposition", diagnostics);
580
+ unique(fields.dependencies, (item) => `${item.dependent_work_id}:${item.prerequisite_work_id}`, "dependency disposition", diagnostics);
581
+ unique(fields.memberships, (item) => `${item.window_id}:${item.origin_work_id}`, "Window membership disposition", diagnostics);
582
+ unique(fields.order, (item) => item, "final Work order", diagnostics);
583
+ unique(fields.residual, (item) => item.work_id, "residual-description action", diagnostics);
584
+ if (fields.strictFragment?.kind === "project") {
585
+ unique(fields.strictFragment.event_ids, (item) => item, "projection Event", diagnostics);
586
+ unique(fields.strictFragment.activity_ids, (item) => item, "projection Activity", diagnostics);
587
+ }
588
+ if (fields.strictFragment?.kind === "defer") {
589
+ unique(fields.strictFragment.task_ids, (item) => item, "deferral Task", diagnostics);
590
+ unique(fields.strictFragment.milestone_ids, (item) => item, "deferral Milestone", diagnostics);
591
+ }
592
+ if (fields.windowClose !== null) {
593
+ unique(fields.windowClose.carry_over_work_ids, (item) => item, "carry-over Work", diagnostics);
594
+ }
595
+ }
596
+ function normalizedRequest(fields) {
597
+ const version2 = fields.version === PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2;
598
+ return Object.freeze({
599
+ request_schema_version: fields.version,
600
+ normalization_contract: version2
601
+ ? PLANNING_RESHAPE_NORMALIZATION_CONTRACT_2
602
+ : PLANNING_RESHAPE_NORMALIZATION_CONTRACT,
603
+ source_digest: fields.sourceDigest,
604
+ intent: fields.intent,
605
+ affected_work_ids: fields.affected,
606
+ created_works: sorted(fields.created, (item) => item.work_id),
607
+ ...(version2
608
+ ? { work_title_dispositions: sorted(fields.titles, (item) => item.work_id) }
609
+ : {}),
610
+ removed_work_ids: fields.removed,
611
+ semantic_elements: fields.elements,
612
+ planning_entity_dispositions: sorted(fields.entities, (item) => `${item.entity_kind}:${item.entity_id}`),
613
+ association_dispositions: sorted(fields.associations, (item) => `${item.entity_kind}:${item.entity_id}:${item.origin_work_id ?? "-"}:${item.destination_work_id ?? "-"}`),
614
+ projection_link_dispositions: sorted(fields.links, (item) => `${item.strict_kind}:${item.strict_id}:${item.origin_work_id ?? "-"}:${item.destination_work_id ?? "-"}`),
615
+ dependency_dispositions: sorted(fields.dependencies, (item) => `${item.dependent_work_id}:${item.prerequisite_work_id}`),
616
+ window_membership_dispositions: sorted(fields.memberships, (item) => `${item.window_id}:${item.origin_work_id}`),
617
+ final_work_order: fields.order,
618
+ add_residual_description: sorted(fields.residual, (item) => item.work_id),
619
+ strict_fragment: fields.strictFragment,
620
+ window_close: fields.windowClose,
621
+ });
622
+ }
623
+ export function planningReshapeSha256(text) {
624
+ return sha256DigestUtf8(text);
625
+ }
626
+ export function canonicalPlanningReshapeJson(value) {
627
+ return canonicalValue(value);
628
+ }
629
+ function validateRequestIdentities(record, diagnostics) {
630
+ const version = record["request_schema_version"];
631
+ if (version !== PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION && version !== PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2) {
632
+ diagnostics.push(diagnostic("Planning reshape request schema identity is unsupported"));
633
+ }
634
+ const expected = version === PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION_2
635
+ ? PLANNING_RESHAPE_NORMALIZATION_CONTRACT_2
636
+ : PLANNING_RESHAPE_NORMALIZATION_CONTRACT;
637
+ if (record["normalization_contract"] !== expected) {
638
+ diagnostics.push(diagnostic("Planning reshape normalization identity is unsupported"));
639
+ }
640
+ }
641
+ function validateRequestIntent(fields, diagnostics) {
642
+ if (fields.version === PLANNING_RESHAPE_REQUEST_SCHEMA_VERSION &&
643
+ fields.dependencies.some(({ action }) => action === "create")) {
644
+ diagnostics.push(diagnostic("created dependency requires PlanningReshapeRequest.v2"));
645
+ }
646
+ if ((fields.intent === "project") !== (fields.strictFragment?.kind === "project")) {
647
+ diagnostics.push(diagnostic("project intent requires exactly one projection strict fragment"));
648
+ }
649
+ if ((fields.intent === "defer") !== (fields.strictFragment?.kind === "defer")) {
650
+ diagnostics.push(diagnostic("defer intent requires exactly one deferral strict fragment"));
651
+ }
652
+ if (fields.intent !== "project" && fields.intent !== "defer" && fields.strictFragment !== null) {
653
+ diagnostics.push(diagnostic("strict_fragment requires project or defer intent"));
654
+ }
655
+ if (fields.windowClose !== null && fields.intent !== "composite") {
656
+ diagnostics.push(diagnostic("window_close requires composite intent"));
657
+ }
658
+ if (fields.windowClose !== null && fields.affected.length === 0) {
659
+ diagnostics.push(diagnostic("window_close requires an audited affected Work set"));
660
+ }
661
+ }
662
+ function successfulNormalization(fields) {
663
+ const request = normalizedRequest(fields);
664
+ const canonicalUtf8 = canonicalValue(request);
665
+ return Object.freeze({
666
+ ok: true,
667
+ request,
668
+ canonicalUtf8,
669
+ preflightHash: planningReshapeSha256(canonicalUtf8),
670
+ diagnostics: Object.freeze([]),
671
+ });
672
+ }
673
+ export function normalizePlanningReshapeRequest(input) {
674
+ const diagnostics = [];
675
+ const record = planningRequestRecord(input, diagnostics);
676
+ if (record === null)
677
+ return failedNormalization(diagnostics);
678
+ validateRequestIdentities(record, diagnostics);
679
+ const fields = parseRequestFields(record, diagnostics);
680
+ if (fields === null)
681
+ return failedNormalization(diagnostics);
682
+ validateRequestIntent(fields, diagnostics);
683
+ validateRequestUniqueness(fields, diagnostics);
684
+ if (diagnostics.length > 0)
685
+ return failedNormalization(diagnostics);
686
+ return successfulNormalization(fields);
687
+ }
688
+ //# sourceMappingURL=reshape-normalize.js.map