devchain-cli 0.22.0 → 0.23.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/dist/drizzle/0077_familiar_morbius.sql +6 -0
  2. package/dist/drizzle/0078_preserve_legacy_connection_ownership.sql +180 -0
  3. package/dist/drizzle/0079_premium_forgotten_one.sql +18 -0
  4. package/dist/drizzle/0080_lethal_thunderbolt_ross.sql +49 -0
  5. package/dist/drizzle/0081_early_kronos.sql +58 -0
  6. package/dist/drizzle/0082_huge_black_queen.sql +14 -0
  7. package/dist/drizzle/meta/0077_snapshot.json +6957 -0
  8. package/dist/drizzle/meta/0078_snapshot.json +6957 -0
  9. package/dist/drizzle/meta/0079_snapshot.json +7093 -0
  10. package/dist/drizzle/meta/0080_snapshot.json +7241 -0
  11. package/dist/drizzle/meta/0081_snapshot.json +7482 -0
  12. package/dist/drizzle/meta/0082_snapshot.json +7571 -0
  13. package/dist/drizzle/meta/_journal.json +42 -0
  14. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  15. package/dist/server/common/errors/error-types.d.ts +7 -0
  16. package/dist/server/common/errors/error-types.js +7 -1
  17. package/dist/server/common/errors/error-types.js.map +1 -1
  18. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.d.ts +8 -0
  19. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js +67 -0
  20. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js.map +1 -0
  21. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.d.ts +12 -0
  22. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js +322 -0
  23. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js.map +1 -0
  24. package/dist/server/modules/epic-time/epic-time.module.js +14 -4
  25. package/dist/server/modules/epic-time/epic-time.module.js.map +1 -1
  26. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.d.ts +30 -0
  27. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js +125 -0
  28. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js.map +1 -0
  29. package/dist/server/modules/epic-time/models/epic-time-local-day.d.ts +9 -0
  30. package/dist/server/modules/epic-time/models/epic-time-local-day.js +164 -0
  31. package/dist/server/modules/epic-time/models/epic-time-local-day.js.map +1 -0
  32. package/dist/server/modules/epic-time/models/epic-time.models.d.ts +138 -0
  33. package/dist/server/modules/epic-time/models/epic-time.models.js +2 -0
  34. package/dist/server/modules/epic-time/models/epic-time.models.js.map +1 -1
  35. package/dist/server/modules/epic-time/services/agent-time-accounting.service.d.ts +1 -0
  36. package/dist/server/modules/epic-time/services/agent-time-accounting.service.js +31 -8
  37. package/dist/server/modules/epic-time/services/agent-time-accounting.service.js.map +1 -1
  38. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.d.ts +37 -0
  39. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js +716 -0
  40. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js.map +1 -0
  41. package/dist/server/modules/epic-time/services/epic-time.service.d.ts +12 -3
  42. package/dist/server/modules/epic-time/services/epic-time.service.js +155 -69
  43. package/dist/server/modules/epic-time/services/epic-time.service.js.map +1 -1
  44. package/dist/server/modules/epic-time/services/epic-time.store.d.ts +29 -3
  45. package/dist/server/modules/epic-time/services/epic-time.store.js +524 -40
  46. package/dist/server/modules/epic-time/services/epic-time.store.js.map +1 -1
  47. package/dist/server/modules/epics/controllers/epics.controller.d.ts +10 -1
  48. package/dist/server/modules/epics/controllers/epics.controller.js +108 -2
  49. package/dist/server/modules/epics/controllers/epics.controller.js.map +1 -1
  50. package/dist/server/modules/epics/epics.module.js +8 -2
  51. package/dist/server/modules/epics/epics.module.js.map +1 -1
  52. package/dist/server/modules/epics/models/epic-relations.models.d.ts +34 -0
  53. package/dist/server/modules/epics/models/epic-relations.models.js +16 -0
  54. package/dist/server/modules/epics/models/epic-relations.models.js.map +1 -0
  55. package/dist/server/modules/epics/services/epic-relation-target-resolver.d.ts +8 -0
  56. package/dist/server/modules/epics/services/epic-relation-target-resolver.js +37 -0
  57. package/dist/server/modules/epics/services/epic-relation-target-resolver.js.map +1 -0
  58. package/dist/server/modules/epics/services/epic-relations.service.d.ts +52 -0
  59. package/dist/server/modules/epics/services/epic-relations.service.js +135 -0
  60. package/dist/server/modules/epics/services/epic-relations.service.js.map +1 -0
  61. package/dist/server/modules/epics/services/epics.service.d.ts +9 -2
  62. package/dist/server/modules/epics/services/epics.service.js +115 -11
  63. package/dist/server/modules/epics/services/epics.service.js.map +1 -1
  64. package/dist/server/modules/events/catalog/broadcast-registry.js +16 -0
  65. package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
  66. package/dist/server/modules/events/catalog/epic.relations.invalidated.d.ts +12 -0
  67. package/dist/server/modules/events/catalog/epic.relations.invalidated.js +13 -0
  68. package/dist/server/modules/events/catalog/epic.relations.invalidated.js.map +1 -0
  69. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.d.ts +12 -0
  70. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js +13 -0
  71. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js.map +1 -0
  72. package/dist/server/modules/events/catalog/index.d.ts +27 -4
  73. package/dist/server/modules/events/catalog/index.js +6 -0
  74. package/dist/server/modules/events/catalog/index.js.map +1 -1
  75. package/dist/server/modules/events/catalog/integration.connection.created.d.ts +4 -1
  76. package/dist/server/modules/events/catalog/integration.connection.created.js +5 -2
  77. package/dist/server/modules/events/catalog/integration.connection.created.js.map +1 -1
  78. package/dist/server/modules/events/catalog/integration.connection.deleted.d.ts +4 -1
  79. package/dist/server/modules/events/catalog/integration.connection.deleted.js +5 -2
  80. package/dist/server/modules/events/catalog/integration.connection.deleted.js.map +1 -1
  81. package/dist/server/modules/events/catalog/integration.connection.legacy.d.ts +85 -0
  82. package/dist/server/modules/events/catalog/integration.connection.legacy.js +41 -0
  83. package/dist/server/modules/events/catalog/integration.connection.legacy.js.map +1 -0
  84. package/dist/server/modules/events/catalog/integration.connection.updated.d.ts +4 -1
  85. package/dist/server/modules/events/catalog/integration.connection.updated.js +5 -2
  86. package/dist/server/modules/events/catalog/integration.connection.updated.js.map +1 -1
  87. package/dist/server/modules/events/services/committed-event-replay.d.ts +10 -0
  88. package/dist/server/modules/events/services/committed-event-replay.js +30 -0
  89. package/dist/server/modules/events/services/committed-event-replay.js.map +1 -0
  90. package/dist/server/modules/events/services/committed-event.store.d.ts +1 -0
  91. package/dist/server/modules/events/services/committed-event.store.js +12 -7
  92. package/dist/server/modules/events/services/committed-event.store.js.map +1 -1
  93. package/dist/server/modules/events/services/durable-event-registry.service.d.ts +9 -2
  94. package/dist/server/modules/events/services/durable-event-registry.service.js.map +1 -1
  95. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.d.ts +7 -0
  96. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js +158 -50
  97. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js.map +1 -1
  98. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.d.ts +4 -0
  99. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js +73 -33
  100. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js.map +1 -1
  101. package/dist/server/modules/external-integrations/connections/integration-connections.controller.d.ts +6 -3
  102. package/dist/server/modules/external-integrations/connections/integration-connections.controller.js +67 -12
  103. package/dist/server/modules/external-integrations/connections/integration-connections.controller.js.map +1 -1
  104. package/dist/server/modules/external-integrations/connections/integration-connections.service.d.ts +35 -3
  105. package/dist/server/modules/external-integrations/connections/integration-connections.service.js +178 -17
  106. package/dist/server/modules/external-integrations/connections/integration-connections.service.js.map +1 -1
  107. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.d.ts +6 -3
  108. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js +51 -11
  109. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js.map +1 -1
  110. package/dist/server/modules/external-integrations/external-integrations.module.js +6 -1
  111. package/dist/server/modules/external-integrations/external-integrations.module.js.map +1 -1
  112. package/dist/server/modules/external-integrations/models/external-edit-session.models.d.ts +1 -0
  113. package/dist/server/modules/external-integrations/models/external-provider.models.d.ts +6 -2
  114. package/dist/server/modules/external-integrations/models/external-provider.models.js +2 -3
  115. package/dist/server/modules/external-integrations/models/external-provider.models.js.map +1 -1
  116. package/dist/server/modules/external-integrations/models/external-time-mutation.models.d.ts +33 -2
  117. package/dist/server/modules/external-integrations/models/external-time-mutation.models.js +14 -0
  118. package/dist/server/modules/external-integrations/models/external-time-mutation.models.js.map +1 -1
  119. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.d.ts +10 -9
  120. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js +81 -46
  121. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js.map +1 -1
  122. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.d.ts +22 -20
  123. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js +136 -64
  124. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js.map +1 -1
  125. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.d.ts +7 -0
  126. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js +289 -4
  127. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js.map +1 -1
  128. package/dist/server/modules/external-integrations/my-work/external-my-work.service.d.ts +12 -7
  129. package/dist/server/modules/external-integrations/my-work/external-my-work.service.js +114 -54
  130. package/dist/server/modules/external-integrations/my-work/external-my-work.service.js.map +1 -1
  131. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.d.ts +19 -6
  132. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js +346 -46
  133. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js.map +1 -1
  134. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.d.ts +2 -1
  135. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js +3 -2
  136. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js.map +1 -1
  137. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.d.ts +6 -1
  138. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js +19 -0
  139. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js.map +1 -1
  140. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.d.ts +15 -2
  141. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js +33 -6
  142. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js.map +1 -1
  143. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.d.ts +13 -6
  144. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js +212 -75
  145. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js.map +1 -1
  146. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.d.ts +13 -4
  147. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js +98 -0
  148. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js.map +1 -1
  149. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.d.ts +10 -4
  150. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js +57 -18
  151. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js.map +1 -1
  152. package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +139 -0
  153. package/dist/server/modules/mcp/dtos/mcp.dto.js +41 -1
  154. package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
  155. package/dist/server/modules/mcp/services/handlers/epic-context.d.ts +2 -0
  156. package/dist/server/modules/mcp/services/handlers/epic-tools.d.ts +4 -0
  157. package/dist/server/modules/mcp/services/handlers/epic-tools.js +275 -0
  158. package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -1
  159. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.d.ts +2 -1
  160. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js +13 -8
  161. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js.map +1 -1
  162. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.d.ts +1 -1
  163. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js +1 -1
  164. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js.map +1 -1
  165. package/dist/server/modules/mcp/tool-descriptors/binding-types.d.ts +2 -0
  166. package/dist/server/modules/mcp/tool-descriptors/binding-types.js.map +1 -1
  167. package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js +6 -0
  168. package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js.map +1 -1
  169. package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js +132 -0
  170. package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js.map +1 -1
  171. package/dist/server/modules/projects/controllers/projects.controller.js +1 -4
  172. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  173. package/dist/server/modules/projects/projects.module.js +2 -0
  174. package/dist/server/modules/projects/projects.module.js.map +1 -1
  175. package/dist/server/modules/projects/services/projects.service.d.ts +4 -1
  176. package/dist/server/modules/projects/services/projects.service.js +23 -2
  177. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  178. package/dist/server/modules/providers/adapters/claude.adapter.d.ts +4 -2
  179. package/dist/server/modules/providers/adapters/claude.adapter.js +31 -0
  180. package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
  181. package/dist/server/modules/providers/services/provider-mcp-ensure.service.d.ts +7 -0
  182. package/dist/server/modules/providers/services/provider-mcp-ensure.service.js +69 -4
  183. package/dist/server/modules/providers/services/provider-mcp-ensure.service.js.map +1 -1
  184. package/dist/server/modules/session-reader/data/pricing.json +452 -56
  185. package/dist/server/modules/session-reader/services/session-reader.service.d.ts +2 -0
  186. package/dist/server/modules/session-reader/services/session-reader.service.js +85 -9
  187. package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
  188. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js +8 -2
  189. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js.map +1 -1
  190. package/dist/server/modules/session-reader/services/transcript-watcher.service.js +44 -17
  191. package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
  192. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +1 -0
  193. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +1 -0
  194. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
  195. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +11 -1
  196. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
  197. package/dist/server/modules/sessions/utils/claude-config.d.ts +4 -1
  198. package/dist/server/modules/sessions/utils/claude-config.js +265 -60
  199. package/dist/server/modules/sessions/utils/claude-config.js.map +1 -1
  200. package/dist/server/modules/storage/db/schema.d.ts +1109 -98
  201. package/dist/server/modules/storage/db/schema.js +141 -3
  202. package/dist/server/modules/storage/db/schema.js.map +1 -1
  203. package/dist/server/modules/storage/db/sqlite-json.d.ts +1 -0
  204. package/dist/server/modules/storage/db/sqlite-json.js +4 -0
  205. package/dist/server/modules/storage/db/sqlite-json.js.map +1 -1
  206. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +58 -10
  207. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  208. package/dist/server/modules/storage/local/delegates/epic.delegate.d.ts +30 -5
  209. package/dist/server/modules/storage/local/delegates/epic.delegate.js +718 -14
  210. package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -1
  211. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.d.ts +52 -0
  212. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js +659 -0
  213. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js.map +1 -0
  214. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.d.ts +2 -1
  215. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js +17 -4
  216. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js.map +1 -1
  217. package/dist/server/modules/storage/local/delegates/integration.delegate.d.ts +23 -8
  218. package/dist/server/modules/storage/local/delegates/integration.delegate.js +238 -52
  219. package/dist/server/modules/storage/local/delegates/integration.delegate.js.map +1 -1
  220. package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +2 -0
  221. package/dist/server/modules/storage/local/delegates/project.delegate.js +180 -130
  222. package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -1
  223. package/dist/server/modules/storage/local/local-storage.service.d.ts +45 -10
  224. package/dist/server/modules/storage/local/local-storage.service.js +99 -15
  225. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  226. package/dist/server/modules/storage/models/domain.models.d.ts +170 -0
  227. package/dist/server/modules/storage/models/domain.models.js +9 -0
  228. package/dist/server/modules/storage/models/domain.models.js.map +1 -1
  229. package/dist/server/templates/teams-dev.json +311 -301
  230. package/dist/server/tsconfig.tsbuildinfo +1 -1
  231. package/dist/server/ui/assets/{ExternalRichEditor-DMtnZIMZ.js → ExternalRichEditor-BG9SopK8.js} +1 -1
  232. package/dist/server/ui/assets/{ReviewDetailPage-BmyiM5IR.js → ReviewDetailPage-DNo7Qx3m.js} +1 -1
  233. package/dist/server/ui/assets/{ReviewsPage-CQ87BMwT.js → ReviewsPage-Cay-IObR.js} +1 -1
  234. package/dist/server/ui/assets/index-C1Fdqua-.js +1197 -0
  235. package/dist/server/ui/assets/index-CV8ydm7w.css +32 -0
  236. package/dist/server/ui/assets/{useReviewSubscription-DjbEUzt4.js → useReviewSubscription-sAAfh4k9.js} +1 -1
  237. package/dist/server/ui/index.html +2 -2
  238. package/dist/templates/teams-dev.json +311 -301
  239. package/package.json +19 -2
  240. package/dist/server/ui/assets/index-BX6Yze-A.js +0 -1177
  241. package/dist/server/ui/assets/index-qoQEv_u4.css +0 -32
@@ -0,0 +1,716 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.EpicEstimateLoggingService = void 0;
16
+ const node_crypto_1 = require("node:crypto");
17
+ const common_1 = require("@nestjs/common");
18
+ const error_types_1 = require("../../../common/errors/error-types");
19
+ const external_time_mutation_service_1 = require("../../external-integrations/my-work/external-time-mutation.service");
20
+ const external_provider_models_1 = require("../../external-integrations/models/external-provider.models");
21
+ const external_time_mutation_store_1 = require("../../external-integrations/sessions/external-time-mutation.store");
22
+ const storage_interface_1 = require("../../storage/interfaces/storage.interface");
23
+ const epic_time_models_1 = require("../models/epic-time.models");
24
+ const epic_time_daily_allocator_1 = require("../models/epic-time-daily-allocator");
25
+ const epic_time_local_day_1 = require("../models/epic-time-local-day");
26
+ const epic_time_service_1 = require("./epic-time.service");
27
+ const MILLIS_PER_MINUTE = 60_000;
28
+ const MAX_ENTRIES_PER_REQUEST = 10;
29
+ const MAX_DAILY_LEDGER_ENTRIES = 3_660;
30
+ const REQUEST_KEY_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31
+ function dailyOperationId(keyHash, index) {
32
+ return `daily:${keyHash}:${index}`;
33
+ }
34
+ function settlementOutcomeForPhase(phase) {
35
+ switch (phase) {
36
+ case 'succeeded':
37
+ return 'logged';
38
+ case 'failed':
39
+ case 'not_applied':
40
+ return 'not_logged';
41
+ default:
42
+ return null;
43
+ }
44
+ }
45
+ function isOperationInProgress(phase) {
46
+ return phase === 'pending' || phase === 'dispatched';
47
+ }
48
+ let EpicEstimateLoggingService = class EpicEstimateLoggingService {
49
+ storage;
50
+ epicTime;
51
+ timeMutations;
52
+ constructor(storage, epicTime, timeMutations) {
53
+ this.storage = storage;
54
+ this.epicTime = epicTime;
55
+ this.timeMutations = timeMutations;
56
+ }
57
+ async getState(input) {
58
+ const context = await this.resolveContext(input);
59
+ const stored = await this.storage.getExternalEstimateLogState(context.identity);
60
+ const state = stored ? (await this.reconcilePending(context, stored)).state : null;
61
+ return this.snapshot(context, state);
62
+ }
63
+ async setLoggedMinutes(input) {
64
+ const context = await this.resolveContext(input);
65
+ const projection = this.epicTime.getDailyProjection(context.epic.id, input.timeZone);
66
+ const state = await this.storage.setExternalEstimateLoggedMinutes({
67
+ ...context.identity,
68
+ loggedMinutes: input.loggedMinutes,
69
+ expectedRevision: input.expectedRevision,
70
+ aggregationTimeZone: projection.canonicalTimeZone,
71
+ currentDailyTotals: projection.currentByDate,
72
+ });
73
+ return this.snapshot(context, state);
74
+ }
75
+ async createTimeEntry(input) {
76
+ const context = await this.resolveContext(input);
77
+ const requestKey = this.requireRequestKey(input.requestKey);
78
+ const dailySnapshot = this.requireDailySnapshot(input.dailySnapshot, input.estimateTotalMinutes);
79
+ this.requireNonnegativeInteger(input.estimateTotalMinutes, 'Estimate total minutes');
80
+ this.requireNonnegativeInteger(input.expectedRevision, 'Expected revision');
81
+ const projection = this.epicTime.getDailyProjection(context.epic.id, input.timeZone);
82
+ const checkpoint = await this.storage.getExternalEstimateLogDailyCheckpoint(context.identity);
83
+ const state = checkpoint?.state ?? null;
84
+ if (state?.pendingOperationId) {
85
+ throw new error_types_1.BusyError('An estimate operation is pending for this task.', {
86
+ reason: 'estimate_operation_pending',
87
+ });
88
+ }
89
+ if (input.expectedRevision !== (state?.revision ?? 0)) {
90
+ throw new error_types_1.OptimisticLockError('External estimate log state', context.identity.remoteTaskId, {
91
+ expectedRevision: input.expectedRevision,
92
+ actualRevision: state?.revision ?? 0,
93
+ });
94
+ }
95
+ const keyHash = (0, node_crypto_1.createHash)('sha256').update(requestKey).digest('hex');
96
+ for (let index = 0; index < MAX_ENTRIES_PER_REQUEST; index += 1) {
97
+ if (this.timeMutations.inspectOperation(dailyOperationId(keyHash, index))) {
98
+ throw new error_types_1.ConflictError('The request key was already used.', {
99
+ reason: 'request_key_reused',
100
+ });
101
+ }
102
+ }
103
+ const liveMinutesByDate = new Map(projection.currentByDate.map((day) => [day.activityDate, day.minutes]));
104
+ for (const captured of dailySnapshot) {
105
+ if ((liveMinutesByDate.get(captured.activityDate) ?? 0) < captured.minutes) {
106
+ throw new error_types_1.ValidationError('Estimate snapshot exceeds the current estimate on a date.', {
107
+ reason: 'estimate_snapshot_ahead',
108
+ });
109
+ }
110
+ }
111
+ const allocation = (0, epic_time_daily_allocator_1.allocateDailyEstimateExport)({
112
+ liveByDate: projection.currentByDate,
113
+ capturedByDate: dailySnapshot,
114
+ ledgerByDate: checkpoint?.days.map((day) => ({
115
+ activityDate: day.activityDate,
116
+ minutes: day.loggedMinutes,
117
+ })) ?? [],
118
+ unallocatedCreditMinutes: checkpoint?.unallocatedLoggedMinutes ?? 0,
119
+ storedCanonicalTimeZone: state?.aggregationTimeZone ?? null,
120
+ currentCanonicalTimeZone: projection.canonicalTimeZone,
121
+ });
122
+ if (allocation.status === 'zone_rebind_required' || allocation.status === 'real_shrink') {
123
+ throw new error_types_1.ValidationError('The estimate checkpoint requires a Set-logged rebaseline.', {
124
+ reason: 'rebaseline_required',
125
+ });
126
+ }
127
+ if (allocation.status === 'stale_capture') {
128
+ throw new error_types_1.ValidationError('The estimate snapshot is stale; recapture the current estimate.', {
129
+ reason: 'estimate_snapshot_stale',
130
+ });
131
+ }
132
+ if (allocation.entryChunks.length === 0) {
133
+ throw new error_types_1.ValidationError('There is no new estimate time to log.', {
134
+ reason: 'estimate_up_to_date',
135
+ });
136
+ }
137
+ const dispatchChunks = allocation.entryChunks.slice(0, MAX_ENTRIES_PER_REQUEST);
138
+ const hasMore = allocation.entryChunks.length > dispatchChunks.length;
139
+ let entriesLogged = 0;
140
+ let minutesLogged = 0;
141
+ let expectedRevision = input.expectedRevision;
142
+ for (const [index, chunk] of dispatchChunks.entries()) {
143
+ const operationId = dailyOperationId(keyHash, index);
144
+ if (chunk.durationMs > external_provider_models_1.MAX_TIME_ENTRY_DURATION_MS) {
145
+ throw new error_types_1.ValidationError('Estimate delta exceeds the maximum time-entry duration.', {
146
+ reason: 'duration_limit',
147
+ });
148
+ }
149
+ const current = await this.storage.getExternalEstimateLogState(context.identity);
150
+ if ((current?.revision ?? 0) !== expectedRevision || current?.pendingOperationId) {
151
+ if (entriesLogged > 0) {
152
+ return this.createOutcome('partially_logged', entriesLogged, minutesLogged, true, 'concurrent_write', context);
153
+ }
154
+ if (current?.pendingOperationId) {
155
+ throw new error_types_1.BusyError('An estimate operation is pending for this task.', {
156
+ reason: 'estimate_operation_pending',
157
+ });
158
+ }
159
+ throw new error_types_1.OptimisticLockError('External estimate log state', context.identity.remoteTaskId, {
160
+ expectedRevision,
161
+ actualRevision: current?.revision ?? 0,
162
+ });
163
+ }
164
+ let prepared;
165
+ try {
166
+ prepared = this.requirePendingValue(await this.storage.prepareExternalEstimateLogOperation({
167
+ ...context.identity,
168
+ operationId,
169
+ deltaMinutes: chunk.minutes,
170
+ estimateTotalMinutes: input.estimateTotalMinutes,
171
+ startedAt: chunk.startedAt,
172
+ connectionId: context.connection.id,
173
+ connectionGeneration: context.connection.generation,
174
+ expectedRevision,
175
+ activityDate: chunk.activityDate,
176
+ aggregationTimeZone: projection.canonicalTimeZone,
177
+ capturedDailyTotals: dailySnapshot,
178
+ }), operationId);
179
+ }
180
+ catch (error) {
181
+ if (error instanceof error_types_1.OptimisticLockError && entriesLogged > 0) {
182
+ return this.createOutcome('partially_logged', entriesLogged, minutesLogged, true, 'concurrent_write', context);
183
+ }
184
+ throw error;
185
+ }
186
+ let result;
187
+ try {
188
+ result = await this.timeMutations.createEstimateTimeEntry(input.projectId, input.provider, input.remoteTaskId, {
189
+ durationMs: chunk.durationMs,
190
+ startedAt: chunk.startedAt,
191
+ note: epic_time_models_1.EXTERNAL_ESTIMATE_TIME_ENTRY_NOTE,
192
+ }, operationId, input.expectedEpoch);
193
+ }
194
+ catch (error) {
195
+ await this.clearKnownFailureFromCurrentCall(context, prepared, operationId);
196
+ if (entriesLogged > 0) {
197
+ return this.createOutcome('partially_logged', entriesLogged, minutesLogged, true, 'provider_error', context);
198
+ }
199
+ throw error;
200
+ }
201
+ const inspection = this.requireMatchingInspection(prepared, operationId);
202
+ if (result.outcome === 'created') {
203
+ if (inspection.phase !== 'succeeded') {
204
+ throw new error_types_1.ConflictError('Provider receipt did not confirm the estimate operation.', {
205
+ reason: 'receipt_phase_mismatch',
206
+ });
207
+ }
208
+ try {
209
+ const confirmed = await this.storage.confirmExternalEstimateLogOperation({
210
+ ...context.identity,
211
+ operationId,
212
+ expectedRevision: prepared.revision,
213
+ });
214
+ expectedRevision = confirmed.revision;
215
+ }
216
+ catch (error) {
217
+ if (!(error instanceof error_types_1.OptimisticLockError))
218
+ throw error;
219
+ return this.createOutcome('partially_logged', entriesLogged, minutesLogged, true, 'concurrent_write', context);
220
+ }
221
+ entriesLogged += 1;
222
+ minutesLogged += chunk.minutes;
223
+ continue;
224
+ }
225
+ const unknown = await this.markOutcomeUnknown(context, prepared, inspection);
226
+ return this.createOutcome('outcome_unknown', entriesLogged, minutesLogged, true, 'outcome_unknown', context, unknown);
227
+ }
228
+ return this.createOutcome('logged', entriesLogged, minutesLogged, hasMore, hasMore ? 'entry_cap' : 'completed', context);
229
+ }
230
+ async createOutcome(outcome, entriesLogged, minutesLogged, hasMore, stoppedReason, context, state) {
231
+ const finalState = state ?? (await this.storage.getExternalEstimateLogState(context.identity));
232
+ return {
233
+ outcome,
234
+ entriesLogged,
235
+ minutesLogged,
236
+ hasMore,
237
+ stoppedReason,
238
+ snapshot: await this.snapshot(context, finalState),
239
+ };
240
+ }
241
+ async resolveOperation(input) {
242
+ const context = await this.resolveContext(input);
243
+ const state = await this.requirePendingState(context.identity, input.operationId);
244
+ this.assertExpectedRevision(state, input.expectedRevision);
245
+ if (input.action === 'verify') {
246
+ return this.verify(context, state);
247
+ }
248
+ return this.resolveManually(context, state, input.action);
249
+ }
250
+ async verify(context, state) {
251
+ if (state.pendingResolution !== null) {
252
+ const resumed = await this.resumeStoredResolution(context, state);
253
+ if (resumed.outcome === null) {
254
+ throw new error_types_1.BusyError('The estimate operation is still in progress.', {
255
+ reason: 'operation_in_progress',
256
+ });
257
+ }
258
+ return {
259
+ outcome: resumed.outcome,
260
+ snapshot: await this.snapshot(context, resumed.state),
261
+ };
262
+ }
263
+ const inspection = this.timeMutations.inspectOperation(state.pendingOperationId);
264
+ if (!inspection) {
265
+ return { outcome: 'unresolved', snapshot: await this.snapshot(context, state) };
266
+ }
267
+ this.assertInspectionMatches(state, inspection);
268
+ const terminalOutcome = settlementOutcomeForPhase(inspection.phase);
269
+ if (terminalOutcome !== null) {
270
+ const reconciled = await this.reconcilePending(context, state);
271
+ return {
272
+ outcome: reconciled.outcome ?? terminalOutcome,
273
+ snapshot: await this.snapshot(context, reconciled.state),
274
+ };
275
+ }
276
+ if (isOperationInProgress(inspection.phase)) {
277
+ throw new error_types_1.BusyError('The estimate operation is still in progress.', {
278
+ reason: 'operation_in_progress',
279
+ });
280
+ }
281
+ if (inspection.phase !== 'outcome_unknown') {
282
+ return { outcome: 'unresolved', snapshot: await this.snapshot(context, state) };
283
+ }
284
+ if (!this.isCurrentPendingEpoch(context, state)) {
285
+ throw new error_types_1.ConflictError('Provider verification is unavailable after connection replacement.', {
286
+ reason: 'connection_superseded',
287
+ });
288
+ }
289
+ await this.timeMutations.verifyOperation(context.request.projectId, context.request.provider, state.pendingOperationId, context.connection.generation);
290
+ const reconciled = await this.reconcilePending(context, state);
291
+ if (reconciled.state.pendingOperationId === null) {
292
+ const after = this.timeMutations.inspectOperation(state.pendingOperationId);
293
+ return {
294
+ outcome: reconciled.outcome ??
295
+ (after ? settlementOutcomeForPhase(after.phase) : null) ??
296
+ 'not_logged',
297
+ snapshot: await this.snapshot(context, reconciled.state),
298
+ };
299
+ }
300
+ return { outcome: 'unresolved', snapshot: await this.snapshot(context, reconciled.state) };
301
+ }
302
+ async resolveManually(context, state, resolution) {
303
+ const inspection = this.timeMutations.inspectOperation(state.pendingOperationId);
304
+ if (inspection) {
305
+ this.assertInspectionMatches(state, inspection);
306
+ const terminal = await this.settleExactTerminalReceipt(context, state, inspection);
307
+ if (terminal) {
308
+ return {
309
+ outcome: terminal.outcome,
310
+ snapshot: await this.snapshot(context, terminal.state),
311
+ };
312
+ }
313
+ if (isOperationInProgress(inspection.phase)) {
314
+ throw new error_types_1.BusyError('The estimate operation is still in progress.', {
315
+ reason: 'operation_in_progress',
316
+ });
317
+ }
318
+ if (inspection.phase !== 'outcome_unknown' &&
319
+ inspection.phase !== 'abandoned_unknown' &&
320
+ this.isCurrentPendingEpoch(context, state)) {
321
+ throw new error_types_1.ConflictError('The estimate operation is not eligible for manual resolution.', {
322
+ reason: 'manual_resolution_unavailable',
323
+ });
324
+ }
325
+ }
326
+ const storedState = await this.storage.storeExternalEstimateLogResolution({
327
+ ...context.identity,
328
+ operationId: state.pendingOperationId,
329
+ resolution,
330
+ expectedRevision: state.revision,
331
+ });
332
+ const stored = this.requirePendingValue(storedState, state.pendingOperationId);
333
+ const applied = await this.resumeStoredResolution(context, stored);
334
+ if (applied.outcome === null) {
335
+ throw new error_types_1.BusyError('The estimate operation is still in progress.', {
336
+ reason: 'operation_in_progress',
337
+ });
338
+ }
339
+ return { outcome: applied.outcome, snapshot: await this.snapshot(context, applied.state) };
340
+ }
341
+ async reconcilePending(context, state) {
342
+ if (state.pendingOperationId === null) {
343
+ return { state, outcome: null };
344
+ }
345
+ const inspection = this.timeMutations.inspectOperation(state.pendingOperationId);
346
+ if (!inspection) {
347
+ if (state.pendingResolution === null) {
348
+ return { state, outcome: null };
349
+ }
350
+ return this.resumeStoredResolution(context, state);
351
+ }
352
+ this.assertInspectionMatches(state, inspection);
353
+ const terminal = await this.settleExactTerminalReceipt(context, state, inspection);
354
+ if (terminal) {
355
+ return terminal;
356
+ }
357
+ if (inspection.phase === 'outcome_unknown' && state.pendingPhase === 'prepared') {
358
+ const marked = await this.storage.markExternalEstimateLogOperationOutcomeUnknown({
359
+ ...context.identity,
360
+ operationId: state.pendingOperationId,
361
+ expectedRevision: state.revision,
362
+ });
363
+ if (marked.pendingOperationId !== null && marked.pendingResolution !== null) {
364
+ return this.resumeStoredResolution(context, marked);
365
+ }
366
+ return { state: marked, outcome: null };
367
+ }
368
+ if (state.pendingResolution !== null) {
369
+ return this.resumeStoredResolution(context, state);
370
+ }
371
+ return { state, outcome: null };
372
+ }
373
+ async resumeStoredResolution(context, state) {
374
+ const inspection = this.timeMutations.inspectOperation(state.pendingOperationId);
375
+ if (inspection) {
376
+ this.assertInspectionMatches(state, inspection);
377
+ const terminal = await this.settleExactTerminalReceipt(context, state, inspection);
378
+ if (terminal) {
379
+ return terminal;
380
+ }
381
+ if (isOperationInProgress(inspection.phase)) {
382
+ return { state, outcome: null };
383
+ }
384
+ if (inspection.phase === 'outcome_unknown' && this.isCurrentPendingEpoch(context, state)) {
385
+ await this.timeMutations.acknowledgeOperation(context.request.projectId, context.request.provider, state.pendingOperationId, context.connection.generation);
386
+ const afterAcknowledgement = this.timeMutations.inspectOperation(state.pendingOperationId);
387
+ if (afterAcknowledgement) {
388
+ this.assertInspectionMatches(state, afterAcknowledgement);
389
+ const terminalAfterAcknowledgement = await this.settleExactTerminalReceipt(context, state, afterAcknowledgement);
390
+ if (terminalAfterAcknowledgement) {
391
+ return terminalAfterAcknowledgement;
392
+ }
393
+ if (isOperationInProgress(afterAcknowledgement.phase)) {
394
+ return { state, outcome: null };
395
+ }
396
+ }
397
+ }
398
+ }
399
+ const applied = await this.storage.applyExternalEstimateLogResolution({
400
+ ...context.identity,
401
+ operationId: state.pendingOperationId,
402
+ expectedRevision: state.revision,
403
+ });
404
+ return { state: applied, outcome: state.pendingResolution };
405
+ }
406
+ async settleExactTerminalReceipt(context, state, inspection) {
407
+ const outcome = settlementOutcomeForPhase(inspection.phase);
408
+ if (outcome === null)
409
+ return null;
410
+ let current = state;
411
+ while (true) {
412
+ try {
413
+ let settled;
414
+ if (outcome === 'logged') {
415
+ settled = await this.storage.confirmExternalEstimateLogOperation({
416
+ ...context.identity,
417
+ operationId: state.pendingOperationId,
418
+ expectedRevision: current.revision,
419
+ });
420
+ }
421
+ else {
422
+ settled = await this.storage.clearExternalEstimateLogOperation({
423
+ ...context.identity,
424
+ operationId: state.pendingOperationId,
425
+ expectedRevision: current.revision,
426
+ });
427
+ }
428
+ return { state: settled, outcome };
429
+ }
430
+ catch (error) {
431
+ if (!(error instanceof error_types_1.OptimisticLockError))
432
+ throw error;
433
+ const refreshed = await this.storage.getExternalEstimateLogState(context.identity);
434
+ if (!refreshed || refreshed.pendingOperationId === null) {
435
+ if (refreshed)
436
+ return { state: refreshed, outcome };
437
+ throw error;
438
+ }
439
+ if (refreshed.pendingOperationId !== state.pendingOperationId ||
440
+ refreshed.revision === current.revision) {
441
+ throw error;
442
+ }
443
+ current = refreshed;
444
+ }
445
+ }
446
+ }
447
+ async clearKnownFailureFromCurrentCall(context, prepared, operationId) {
448
+ const inspection = this.timeMutations.inspectOperation(operationId);
449
+ if (inspection) {
450
+ this.assertInspectionMatches(prepared, inspection);
451
+ if (inspection.phase === 'outcome_unknown') {
452
+ await this.markOutcomeUnknown(context, prepared, inspection);
453
+ return;
454
+ }
455
+ if (inspection.phase !== 'failed' && inspection.phase !== 'not_applied') {
456
+ return;
457
+ }
458
+ }
459
+ await this.storage.clearExternalEstimateLogOperation({
460
+ ...context.identity,
461
+ operationId,
462
+ expectedRevision: prepared.revision,
463
+ });
464
+ }
465
+ async markOutcomeUnknown(context, prepared, inspection) {
466
+ this.assertInspectionMatches(prepared, inspection);
467
+ if (inspection.phase !== 'outcome_unknown') {
468
+ throw new error_types_1.ConflictError('Provider receipt is not outcome-unknown.', {
469
+ reason: 'receipt_phase_mismatch',
470
+ });
471
+ }
472
+ if (prepared.pendingPhase === 'outcome_unknown') {
473
+ return prepared;
474
+ }
475
+ return this.storage.markExternalEstimateLogOperationOutcomeUnknown({
476
+ ...context.identity,
477
+ operationId: prepared.pendingOperationId,
478
+ expectedRevision: prepared.revision,
479
+ });
480
+ }
481
+ async snapshot(context, state) {
482
+ if (!state) {
483
+ return {
484
+ state: null,
485
+ initialized: false,
486
+ revision: 0,
487
+ loggedMinutes: 0,
488
+ aggregationTimeZone: null,
489
+ days: [],
490
+ unallocatedLoggedMinutes: 0,
491
+ pendingDisposition: 'none',
492
+ canVerify: false,
493
+ verifyExpiresAt: null,
494
+ };
495
+ }
496
+ const checkpoint = await this.storage.getExternalEstimateLogDailyCheckpoint(context.identity);
497
+ if (!checkpoint) {
498
+ throw new error_types_1.NotFoundError('External estimate log state');
499
+ }
500
+ return {
501
+ state: checkpoint.state,
502
+ initialized: true,
503
+ revision: checkpoint.state.revision,
504
+ loggedMinutes: checkpoint.state.loggedMinutes,
505
+ aggregationTimeZone: checkpoint.state.aggregationTimeZone,
506
+ days: checkpoint.days,
507
+ unallocatedLoggedMinutes: checkpoint.unallocatedLoggedMinutes,
508
+ ...this.pendingDispositionFields(context, checkpoint.state),
509
+ };
510
+ }
511
+ pendingDispositionFields(context, state) {
512
+ if (state.pendingOperationId === null) {
513
+ return {
514
+ pendingDisposition: 'none',
515
+ canVerify: false,
516
+ verifyExpiresAt: null,
517
+ };
518
+ }
519
+ const inspection = this.timeMutations.inspectOperation(state.pendingOperationId);
520
+ if (inspection) {
521
+ this.assertInspectionMatches(state, inspection);
522
+ }
523
+ if (inspection?.phase === 'pending' || inspection?.phase === 'dispatched') {
524
+ return {
525
+ pendingDisposition: 'busy',
526
+ canVerify: false,
527
+ verifyExpiresAt: null,
528
+ };
529
+ }
530
+ if (state.pendingResolution !== null) {
531
+ return {
532
+ pendingDisposition: 'finishing',
533
+ canVerify: false,
534
+ verifyExpiresAt: null,
535
+ };
536
+ }
537
+ const currentEpoch = this.isCurrentPendingEpoch(context, state);
538
+ const disposition = inspection?.phase === 'outcome_unknown' && currentEpoch ? 'outcome_unknown' : 'manual_review';
539
+ return {
540
+ pendingDisposition: disposition,
541
+ canVerify: inspection?.canVerify === true && currentEpoch,
542
+ verifyExpiresAt: inspection?.canVerify === true && currentEpoch ? inspection.expiresAt : null,
543
+ };
544
+ }
545
+ requireMatchingInspection(state, operationId) {
546
+ const inspection = this.timeMutations.inspectOperation(operationId);
547
+ if (!inspection) {
548
+ throw new error_types_1.ConflictError('Provider receipt is unavailable.', {
549
+ reason: 'receipt_missing',
550
+ });
551
+ }
552
+ this.assertInspectionMatches(state, inspection);
553
+ return inspection;
554
+ }
555
+ assertInspectionMatches(state, inspection) {
556
+ const tuple = inspection.tuple;
557
+ if (inspection.operationId !== state.pendingOperationId ||
558
+ inspection.kind !== 'create' ||
559
+ tuple.provider !== state.provider ||
560
+ tuple.remoteTaskId !== state.remoteTaskId ||
561
+ tuple.connectionId !== state.pendingConnectionId ||
562
+ tuple.connectionGeneration !== state.pendingConnectionGeneration ||
563
+ tuple.effectiveStartedAt !== state.pendingStartedAt ||
564
+ tuple.durationMs !== state.pendingDeltaMinutes * MILLIS_PER_MINUTE ||
565
+ tuple.noteFingerprint !== (0, external_time_mutation_store_1.timeEntryNoteFingerprint)(epic_time_models_1.EXTERNAL_ESTIMATE_TIME_ENTRY_NOTE)) {
566
+ throw new error_types_1.ConflictError('Provider receipt does not match the pending estimate operation.', {
567
+ reason: 'receipt_tuple_mismatch',
568
+ });
569
+ }
570
+ }
571
+ async requirePendingState(identity, operationId) {
572
+ const state = await this.storage.getExternalEstimateLogState(identity);
573
+ if (!state || state.pendingOperationId === null) {
574
+ throw new error_types_1.NotFoundError('Pending estimate operation');
575
+ }
576
+ if (state.pendingOperationId !== operationId) {
577
+ throw new error_types_1.ConflictError('A different estimate operation is pending.', {
578
+ reason: 'estimate_operation_mismatch',
579
+ });
580
+ }
581
+ return state;
582
+ }
583
+ requirePendingValue(state, operationId) {
584
+ if (state.pendingOperationId !== operationId) {
585
+ throw new error_types_1.ConflictError('Estimate operation state changed unexpectedly.', {
586
+ reason: 'estimate_operation_mismatch',
587
+ });
588
+ }
589
+ return state;
590
+ }
591
+ async resolveContext(input) {
592
+ const request = this.normalizeContext(input);
593
+ const identity = {
594
+ provider: request.provider,
595
+ remoteScopeKey: request.remoteScopeKey,
596
+ remoteTaskId: request.remoteTaskId,
597
+ };
598
+ const [link, connection] = await Promise.all([
599
+ this.storage.findExternalTaskLink(identity.provider, identity.remoteScopeKey, identity.remoteTaskId),
600
+ this.storage.getIntegrationConnection({
601
+ projectId: request.projectId,
602
+ provider: request.provider,
603
+ }),
604
+ ]);
605
+ if (!link) {
606
+ throw new error_types_1.NotFoundError('External task link');
607
+ }
608
+ if (connection?.projectId !== request.projectId || connection.provider !== request.provider) {
609
+ throw new error_types_1.ValidationError('Connect the integration before changing estimate time.', {
610
+ reason: 'not_connected',
611
+ });
612
+ }
613
+ if (connection.generation !== request.expectedEpoch) {
614
+ throw new error_types_1.ConflictError('The connection changed; reload with the current epoch.', {
615
+ reason: 'connection_epoch_mismatch',
616
+ });
617
+ }
618
+ if (link.connectionId !== connection.id) {
619
+ throw new error_types_1.ConflictError('The external task link does not belong to the current connection.', {
620
+ reason: 'link_connection_mismatch',
621
+ });
622
+ }
623
+ const epic = await this.storage.getEpic(link.epicId);
624
+ if (epic.projectId !== request.projectId) {
625
+ throw new error_types_1.NotFoundError('External task link');
626
+ }
627
+ return { request, identity, link, epic, connection };
628
+ }
629
+ normalizeContext(input) {
630
+ const projectId = this.requireIdentifier(input.projectId, 'Project');
631
+ const remoteScopeKey = this.requireIdentifier(input.remoteScopeKey, 'Remote scope');
632
+ const remoteTaskId = this.requireIdentifier(input.remoteTaskId, 'Remote task');
633
+ if (input.provider !== 'clickup' && input.provider !== 'jira') {
634
+ throw new error_types_1.ValidationError('Integration provider is invalid.');
635
+ }
636
+ if (!Number.isSafeInteger(input.expectedEpoch) || input.expectedEpoch < 1) {
637
+ throw new error_types_1.ValidationError('Connection epoch must be a positive whole number.');
638
+ }
639
+ return {
640
+ projectId,
641
+ provider: input.provider,
642
+ remoteScopeKey,
643
+ remoteTaskId,
644
+ expectedEpoch: input.expectedEpoch,
645
+ };
646
+ }
647
+ isCurrentPendingEpoch(context, state) {
648
+ return (context.connection.id === state.pendingConnectionId &&
649
+ context.connection.generation === state.pendingConnectionGeneration);
650
+ }
651
+ assertExpectedRevision(state, expectedRevision) {
652
+ this.requireNonnegativeInteger(expectedRevision, 'Expected revision');
653
+ if (state.revision !== expectedRevision) {
654
+ throw this.optimisticLock(state, expectedRevision);
655
+ }
656
+ }
657
+ optimisticLock(state, expectedRevision) {
658
+ return new error_types_1.OptimisticLockError('External estimate log state', state.remoteTaskId, {
659
+ expectedRevision,
660
+ actualRevision: state.revision,
661
+ });
662
+ }
663
+ requireRequestKey(value) {
664
+ const normalized = value?.trim();
665
+ if (!normalized || !REQUEST_KEY_PATTERN.test(normalized)) {
666
+ throw new error_types_1.ValidationError('Estimate request key must be a fresh UUID.');
667
+ }
668
+ return normalized.toLowerCase();
669
+ }
670
+ requireDailySnapshot(entries, estimateTotalMinutes) {
671
+ if (!Array.isArray(entries) || entries.length > MAX_DAILY_LEDGER_ENTRIES) {
672
+ throw new error_types_1.ValidationError(`The daily snapshot may carry at most ${MAX_DAILY_LEDGER_ENTRIES} activity dates.`);
673
+ }
674
+ let previousDate = null;
675
+ let total = 0;
676
+ for (const entry of entries) {
677
+ if (!(0, epic_time_local_day_1.isValidActivityDate)(entry?.activityDate ?? '')) {
678
+ throw new error_types_1.ValidationError('The daily snapshot contains an invalid activity date.');
679
+ }
680
+ if (!Number.isSafeInteger(entry?.minutes) || entry.minutes < 0) {
681
+ throw new error_types_1.ValidationError('Daily snapshot minutes must be nonnegative whole numbers.');
682
+ }
683
+ if (previousDate !== null && entry.activityDate.localeCompare(previousDate) <= 0) {
684
+ throw new error_types_1.ValidationError('Daily snapshot dates must be unique and ascending.');
685
+ }
686
+ previousDate = entry.activityDate;
687
+ total += entry.minutes;
688
+ }
689
+ if (total !== estimateTotalMinutes) {
690
+ throw new error_types_1.ValidationError('The daily snapshot must sum to the estimate total.', {
691
+ reason: 'snapshot_sum_mismatch',
692
+ });
693
+ }
694
+ return [...entries];
695
+ }
696
+ requireIdentifier(value, label) {
697
+ const normalized = value?.trim();
698
+ if (!normalized) {
699
+ throw new error_types_1.ValidationError(`${label} identifier is required.`);
700
+ }
701
+ return normalized;
702
+ }
703
+ requireNonnegativeInteger(value, label) {
704
+ if (!Number.isSafeInteger(value) || value < 0) {
705
+ throw new error_types_1.ValidationError(`${label} must be a nonnegative whole number.`);
706
+ }
707
+ }
708
+ };
709
+ exports.EpicEstimateLoggingService = EpicEstimateLoggingService;
710
+ exports.EpicEstimateLoggingService = EpicEstimateLoggingService = __decorate([
711
+ (0, common_1.Injectable)(),
712
+ __param(0, (0, common_1.Inject)(storage_interface_1.STORAGE_SERVICE)),
713
+ __metadata("design:paramtypes", [Object, epic_time_service_1.EpicTimeService,
714
+ external_time_mutation_service_1.ExternalTimeMutationService])
715
+ ], EpicEstimateLoggingService);
716
+ //# sourceMappingURL=epic-estimate-logging.service.js.map