@telora/daemon 0.22.187 → 0.22.208

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 (278) hide show
  1. package/build-info.json +6 -3
  2. package/dist/backends/index.d.ts.map +1 -1
  3. package/dist/backends/index.js +2 -0
  4. package/dist/backends/index.js.map +1 -1
  5. package/dist/completion/completion-fact-readers.d.ts +35 -0
  6. package/dist/completion/completion-fact-readers.d.ts.map +1 -0
  7. package/dist/completion/completion-fact-readers.js +70 -0
  8. package/dist/completion/completion-fact-readers.js.map +1 -0
  9. package/dist/completion/escalations.d.ts.map +1 -1
  10. package/dist/completion/escalations.js +2 -1
  11. package/dist/completion/escalations.js.map +1 -1
  12. package/dist/completion/event-dep-defaults.d.ts +27 -0
  13. package/dist/completion/event-dep-defaults.d.ts.map +1 -0
  14. package/dist/completion/event-dep-defaults.js +30 -0
  15. package/dist/completion/event-dep-defaults.js.map +1 -0
  16. package/dist/completion/event-phases.d.ts.map +1 -1
  17. package/dist/completion/event-phases.js +5 -13
  18. package/dist/completion/event-phases.js.map +1 -1
  19. package/dist/completion/event.d.ts +32 -0
  20. package/dist/completion/event.d.ts.map +1 -1
  21. package/dist/completion/event.js +31 -25
  22. package/dist/completion/event.js.map +1 -1
  23. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  24. package/dist/completion/review-exit-phase.js +11 -4
  25. package/dist/completion/review-exit-phase.js.map +1 -1
  26. package/dist/completion/session-hold.d.ts +13 -1
  27. package/dist/completion/session-hold.d.ts.map +1 -1
  28. package/dist/completion/session-hold.js +18 -0
  29. package/dist/completion/session-hold.js.map +1 -1
  30. package/dist/completion/standing-approval-convergence.d.ts.map +1 -1
  31. package/dist/completion/standing-approval-convergence.js +2 -1
  32. package/dist/completion/standing-approval-convergence.js.map +1 -1
  33. package/dist/completion/team-phase.d.ts.map +1 -1
  34. package/dist/completion/team-phase.js +1 -0
  35. package/dist/completion/team-phase.js.map +1 -1
  36. package/dist/completion/unclaimed-migration-notice-phase.d.ts +97 -0
  37. package/dist/completion/unclaimed-migration-notice-phase.d.ts.map +1 -0
  38. package/dist/completion/unclaimed-migration-notice-phase.js +270 -0
  39. package/dist/completion/unclaimed-migration-notice-phase.js.map +1 -0
  40. package/dist/directive/directive-executor.d.ts.map +1 -1
  41. package/dist/directive/directive-executor.js +1 -20
  42. package/dist/directive/directive-executor.js.map +1 -1
  43. package/dist/focus/focus-executor.d.ts +1 -0
  44. package/dist/focus/focus-executor.d.ts.map +1 -1
  45. package/dist/focus/focus-executor.js +18 -17
  46. package/dist/focus/focus-executor.js.map +1 -1
  47. package/dist/focus/focus-loop-registry.js +2 -2
  48. package/dist/focus/focus-loop-registry.js.map +1 -1
  49. package/dist/focus/focus-team-state.js +1 -1
  50. package/dist/focus/listener-spawn.d.ts.map +1 -1
  51. package/dist/focus/listener-spawn.js +5 -20
  52. package/dist/focus/listener-spawn.js.map +1 -1
  53. package/dist/focus/listener-state.d.ts.map +1 -1
  54. package/dist/focus/listener-state.js +1 -0
  55. package/dist/focus/listener-state.js.map +1 -1
  56. package/dist/focus/listener.d.ts +1 -0
  57. package/dist/focus/listener.d.ts.map +1 -1
  58. package/dist/focus/listener.js +9 -0
  59. package/dist/focus/listener.js.map +1 -1
  60. package/dist/focus/migration-claim-pass.d.ts +50 -0
  61. package/dist/focus/migration-claim-pass.d.ts.map +1 -0
  62. package/dist/focus/migration-claim-pass.js +234 -0
  63. package/dist/focus/migration-claim-pass.js.map +1 -0
  64. package/dist/focus/pending-spawn-pass-deps.d.ts.map +1 -1
  65. package/dist/focus/pending-spawn-pass-deps.js +2 -1
  66. package/dist/focus/pending-spawn-pass-deps.js.map +1 -1
  67. package/dist/focus/team-actionable-work.d.ts +21 -0
  68. package/dist/focus/team-actionable-work.d.ts.map +1 -1
  69. package/dist/focus/team-actionable-work.js +3 -0
  70. package/dist/focus/team-actionable-work.js.map +1 -1
  71. package/dist/git/types.d.ts.map +1 -1
  72. package/dist/git/types.js +2 -1
  73. package/dist/git/types.js.map +1 -1
  74. package/dist/loops/llm-client.d.ts +1 -1
  75. package/dist/loops/llm-client.d.ts.map +1 -1
  76. package/dist/loops/llm-client.js +29 -106
  77. package/dist/loops/llm-client.js.map +1 -1
  78. package/dist/migrations/migration-claim-lifecycle.d.ts +166 -0
  79. package/dist/migrations/migration-claim-lifecycle.d.ts.map +1 -0
  80. package/dist/migrations/migration-claim-lifecycle.js +198 -0
  81. package/dist/migrations/migration-claim-lifecycle.js.map +1 -0
  82. package/dist/migrations/migration-held-set.d.ts +171 -0
  83. package/dist/migrations/migration-held-set.d.ts.map +1 -0
  84. package/dist/migrations/migration-held-set.js +295 -0
  85. package/dist/migrations/migration-held-set.js.map +1 -0
  86. package/dist/migrations/migration-inventory.d.ts +187 -0
  87. package/dist/migrations/migration-inventory.d.ts.map +1 -0
  88. package/dist/migrations/migration-inventory.js +253 -0
  89. package/dist/migrations/migration-inventory.js.map +1 -0
  90. package/dist/migrations/migration-manifest.d.ts +114 -0
  91. package/dist/migrations/migration-manifest.d.ts.map +1 -0
  92. package/dist/migrations/migration-manifest.js +295 -0
  93. package/dist/migrations/migration-manifest.js.map +1 -0
  94. package/dist/prompt/team-prompt-base.js +1 -1
  95. package/dist/prompt-sections/migration-numbering-section.d.ts +58 -0
  96. package/dist/prompt-sections/migration-numbering-section.d.ts.map +1 -0
  97. package/dist/prompt-sections/migration-numbering-section.js +70 -0
  98. package/dist/prompt-sections/migration-numbering-section.js.map +1 -0
  99. package/dist/prompt-sections/migration-numbering-state.d.ts +30 -0
  100. package/dist/prompt-sections/migration-numbering-state.d.ts.map +1 -0
  101. package/dist/prompt-sections/migration-numbering-state.js +24 -0
  102. package/dist/prompt-sections/migration-numbering-state.js.map +1 -0
  103. package/dist/prompt-sections/unclaimed-migration-section.d.ts +38 -0
  104. package/dist/prompt-sections/unclaimed-migration-section.d.ts.map +1 -0
  105. package/dist/prompt-sections/unclaimed-migration-section.js +57 -0
  106. package/dist/prompt-sections/unclaimed-migration-section.js.map +1 -0
  107. package/dist/queries/agent-resolvable-promotion.d.ts +12 -0
  108. package/dist/queries/agent-resolvable-promotion.d.ts.map +1 -1
  109. package/dist/queries/agent-resolvable-promotion.js +17 -6
  110. package/dist/queries/agent-resolvable-promotion.js.map +1 -1
  111. package/dist/queries/claim-pass-focuses.d.ts +19 -0
  112. package/dist/queries/claim-pass-focuses.d.ts.map +1 -0
  113. package/dist/queries/claim-pass-focuses.js +72 -0
  114. package/dist/queries/claim-pass-focuses.js.map +1 -0
  115. package/dist/queries/daemon-connection.d.ts.map +1 -1
  116. package/dist/queries/daemon-connection.js +1 -0
  117. package/dist/queries/daemon-connection.js.map +1 -1
  118. package/dist/queries/delivery-gate-state.d.ts +3 -0
  119. package/dist/queries/delivery-gate-state.d.ts.map +1 -1
  120. package/dist/queries/delivery-gate-state.js +1 -1
  121. package/dist/queries/delivery-gate-state.js.map +1 -1
  122. package/dist/queries/focus-discovery.d.ts +25 -2
  123. package/dist/queries/focus-discovery.d.ts.map +1 -1
  124. package/dist/queries/focus-discovery.js +16 -9
  125. package/dist/queries/focus-discovery.js.map +1 -1
  126. package/dist/queries/focus-unclaimed-migration.d.ts +146 -0
  127. package/dist/queries/focus-unclaimed-migration.d.ts.map +1 -0
  128. package/dist/queries/focus-unclaimed-migration.js +266 -0
  129. package/dist/queries/focus-unclaimed-migration.js.map +1 -0
  130. package/dist/queries/live-focus-branches.d.ts +32 -0
  131. package/dist/queries/live-focus-branches.d.ts.map +1 -0
  132. package/dist/queries/live-focus-branches.js +66 -0
  133. package/dist/queries/live-focus-branches.js.map +1 -0
  134. package/dist/queries/migration-claims.d.ts +25 -0
  135. package/dist/queries/migration-claims.d.ts.map +1 -0
  136. package/dist/queries/migration-claims.js +79 -0
  137. package/dist/queries/migration-claims.js.map +1 -0
  138. package/dist/queries/schemas.d.ts +2 -0
  139. package/dist/queries/schemas.d.ts.map +1 -1
  140. package/dist/queries/schemas.js +6 -0
  141. package/dist/queries/schemas.js.map +1 -1
  142. package/dist/queries/shared.js +1 -1
  143. package/dist/queries/verify-own-holds.d.ts +89 -0
  144. package/dist/queries/verify-own-holds.d.ts.map +1 -0
  145. package/dist/queries/verify-own-holds.js +93 -0
  146. package/dist/queries/verify-own-holds.js.map +1 -0
  147. package/dist/session/session-lineage.d.ts.map +1 -1
  148. package/dist/session/session-lineage.js +3 -0
  149. package/dist/session/session-lineage.js.map +1 -1
  150. package/dist/shell/heartbeat-metadata.d.ts +48 -2
  151. package/dist/shell/heartbeat-metadata.d.ts.map +1 -1
  152. package/dist/shell/heartbeat-metadata.js +80 -3
  153. package/dist/shell/heartbeat-metadata.js.map +1 -1
  154. package/dist/shell/heartbeat.d.ts +13 -0
  155. package/dist/shell/heartbeat.d.ts.map +1 -1
  156. package/dist/shell/heartbeat.js +31 -10
  157. package/dist/shell/heartbeat.js.map +1 -1
  158. package/dist/shell/mcp-resolution.js +1 -1
  159. package/dist/shell/mcp-resolution.js.map +1 -1
  160. package/dist/shell/migration-held-set-publish.d.ts +170 -0
  161. package/dist/shell/migration-held-set-publish.d.ts.map +1 -0
  162. package/dist/shell/migration-held-set-publish.js +172 -0
  163. package/dist/shell/migration-held-set-publish.js.map +1 -0
  164. package/dist/shell/worker-pidfile.d.ts.map +1 -1
  165. package/dist/shell/worker-pidfile.js +1 -0
  166. package/dist/shell/worker-pidfile.js.map +1 -1
  167. package/dist/spawn/helpers.d.ts.map +1 -1
  168. package/dist/spawn/helpers.js +1 -0
  169. package/dist/spawn/helpers.js.map +1 -1
  170. package/dist/spawn/lead-launch.d.ts.map +1 -1
  171. package/dist/spawn/lead-launch.js +1 -0
  172. package/dist/spawn/lead-launch.js.map +1 -1
  173. package/dist/spawn/lead-process-ownership.d.ts.map +1 -1
  174. package/dist/spawn/lead-process-ownership.js +1 -0
  175. package/dist/spawn/lead-process-ownership.js.map +1 -1
  176. package/dist/spawn/one-shot-model.d.ts +145 -0
  177. package/dist/spawn/one-shot-model.d.ts.map +1 -0
  178. package/dist/spawn/one-shot-model.js +329 -0
  179. package/dist/spawn/one-shot-model.js.map +1 -0
  180. package/dist/spawn/process-group-kill.d.ts.map +1 -1
  181. package/dist/spawn/process-group-kill.js +1 -0
  182. package/dist/spawn/process-group-kill.js.map +1 -1
  183. package/dist/spawn/process-spawn.d.ts +18 -1
  184. package/dist/spawn/process-spawn.d.ts.map +1 -1
  185. package/dist/spawn/process-spawn.js +11 -6
  186. package/dist/spawn/process-spawn.js.map +1 -1
  187. package/dist/spawn/prompt-assembly.d.ts +115 -18
  188. package/dist/spawn/prompt-assembly.d.ts.map +1 -1
  189. package/dist/spawn/prompt-assembly.js +121 -2
  190. package/dist/spawn/prompt-assembly.js.map +1 -1
  191. package/dist/spawn/review-hold-park.d.ts.map +1 -1
  192. package/dist/spawn/review-hold-park.js +7 -3
  193. package/dist/spawn/review-hold-park.js.map +1 -1
  194. package/dist/spawn/routing-decider-spawn.d.ts +30 -0
  195. package/dist/spawn/routing-decider-spawn.d.ts.map +1 -1
  196. package/dist/spawn/routing-decider-spawn.js +84 -2
  197. package/dist/spawn/routing-decider-spawn.js.map +1 -1
  198. package/dist/spawn/routing-decider.d.ts.map +1 -1
  199. package/dist/spawn/routing-decider.js +22 -0
  200. package/dist/spawn/routing-decider.js.map +1 -1
  201. package/dist/spawn/spawn-and-acknowledge.d.ts +61 -0
  202. package/dist/spawn/spawn-and-acknowledge.d.ts.map +1 -0
  203. package/dist/spawn/spawn-and-acknowledge.js +117 -0
  204. package/dist/spawn/spawn-and-acknowledge.js.map +1 -0
  205. package/dist/spawn/spawn-cooldown.d.ts +15 -1
  206. package/dist/spawn/spawn-cooldown.d.ts.map +1 -1
  207. package/dist/spawn/spawn-cooldown.js +67 -19
  208. package/dist/spawn/spawn-cooldown.js.map +1 -1
  209. package/dist/spawn/spawn-decision.d.ts +10 -10
  210. package/dist/spawn/spawn-decision.d.ts.map +1 -1
  211. package/dist/spawn/spawn-decision.js +33 -5
  212. package/dist/spawn/spawn-decision.js.map +1 -1
  213. package/dist/templates/claude-md.d.ts +1 -1
  214. package/dist/templates/claude-md.d.ts.map +1 -1
  215. package/dist/templates/claude-md.js +24 -0
  216. package/dist/templates/claude-md.js.map +1 -1
  217. package/dist/testing/fixtures/index.js +1 -1
  218. package/dist/testing/fixtures/migration-namespace-repo.d.ts +63 -0
  219. package/dist/testing/fixtures/migration-namespace-repo.d.ts.map +1 -0
  220. package/dist/testing/fixtures/migration-namespace-repo.js +164 -0
  221. package/dist/testing/fixtures/migration-namespace-repo.js.map +1 -0
  222. package/dist/testing/fixtures/stubborn-lead-process.d.ts.map +1 -1
  223. package/dist/testing/fixtures/stubborn-lead-process.js +1 -0
  224. package/dist/testing/fixtures/stubborn-lead-process.js.map +1 -1
  225. package/dist/types/index.js +1 -1
  226. package/dist/verify/ai-inspection-runner.d.ts.map +1 -1
  227. package/dist/verify/ai-inspection-runner.js +26 -7
  228. package/dist/verify/ai-inspection-runner.js.map +1 -1
  229. package/dist/verify/de-restatement.d.ts +9 -2
  230. package/dist/verify/de-restatement.d.ts.map +1 -1
  231. package/dist/verify/de-restatement.js +14 -76
  232. package/dist/verify/de-restatement.js.map +1 -1
  233. package/dist/verify/default-on-guards.d.ts +5 -2
  234. package/dist/verify/default-on-guards.d.ts.map +1 -1
  235. package/dist/verify/default-on-guards.js +5 -3
  236. package/dist/verify/default-on-guards.js.map +1 -1
  237. package/dist/verify/no-own-delta-verdict.d.ts +16 -2
  238. package/dist/verify/no-own-delta-verdict.d.ts.map +1 -1
  239. package/dist/verify/no-own-delta-verdict.js +18 -2
  240. package/dist/verify/no-own-delta-verdict.js.map +1 -1
  241. package/dist/verify/verification-deps-default.d.ts.map +1 -1
  242. package/dist/verify/verification-deps-default.js +6 -0
  243. package/dist/verify/verification-deps-default.js.map +1 -1
  244. package/dist/verify/verification-deps.d.ts +10 -0
  245. package/dist/verify/verification-deps.d.ts.map +1 -1
  246. package/dist/verify/verification-deterministic.d.ts +7 -0
  247. package/dist/verify/verification-deterministic.d.ts.map +1 -1
  248. package/dist/verify/verification-deterministic.js.map +1 -1
  249. package/dist/verify/verification-engine.d.ts +1 -20
  250. package/dist/verify/verification-engine.d.ts.map +1 -1
  251. package/dist/verify/verification-engine.js +24 -55
  252. package/dist/verify/verification-engine.js.map +1 -1
  253. package/dist/verify/verification-evidence-unavailable-feedback.d.ts +3 -1
  254. package/dist/verify/verification-evidence-unavailable-feedback.d.ts.map +1 -1
  255. package/dist/verify/verification-evidence-unavailable-feedback.js +3 -1
  256. package/dist/verify/verification-evidence-unavailable-feedback.js.map +1 -1
  257. package/dist/verify/verification-feedback.d.ts +4 -12
  258. package/dist/verify/verification-feedback.d.ts.map +1 -1
  259. package/dist/verify/verification-feedback.js +7 -12
  260. package/dist/verify/verification-feedback.js.map +1 -1
  261. package/dist/verify/verification-history-reset.d.ts.map +1 -1
  262. package/dist/verify/verification-history-reset.js +11 -3
  263. package/dist/verify/verification-history-reset.js.map +1 -1
  264. package/dist/verify/verification-model-unavailable-hold.d.ts +92 -0
  265. package/dist/verify/verification-model-unavailable-hold.d.ts.map +1 -0
  266. package/dist/verify/verification-model-unavailable-hold.js +207 -0
  267. package/dist/verify/verification-model-unavailable-hold.js.map +1 -0
  268. package/dist/verify/verification-strategies.d.ts +32 -0
  269. package/dist/verify/verification-strategies.d.ts.map +1 -0
  270. package/dist/verify/verification-strategies.js +47 -0
  271. package/dist/verify/verification-strategies.js.map +1 -0
  272. package/dist/verify/verify-reconciliation.d.ts.map +1 -1
  273. package/dist/verify/verify-reconciliation.js +2 -1
  274. package/dist/verify/verify-reconciliation.js.map +1 -1
  275. package/dist/verify/verify-restatement.d.ts.map +1 -1
  276. package/dist/verify/verify-restatement.js +21 -8
  277. package/dist/verify/verify-restatement.js.map +1 -1
  278. package/package.json +2 -2
@@ -0,0 +1,295 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/migrations/claim-end-to-end.test.ts,
3
+ // packages/daemon/src/migrations/concurrent-claim-regression.test.ts,
4
+ // packages/daemon/src/migrations/held-set-grant-effect.test.ts,
5
+ // packages/daemon/src/migrations/migration-held-set.test.ts,
6
+ // packages/daemon/src/shell/migration-held-set-publish.test.ts
7
+ // PINNED-BY-END
8
+ /**
9
+ * What a product's migration numbers are CURRENTLY held by, across every ref
10
+ * that matters -- integration and every live focus branch.
11
+ *
12
+ * WHY THIS LIVES IN THE DAEMON AND THE GRANT DOES NOT
13
+ * ---------------------------------------------------
14
+ * The two halves of claiming a number sit on opposite sides of a boundary, and
15
+ * neither half can move:
16
+ *
17
+ * - Seeing what is held needs the REPO. It is a git-tree read across several
18
+ * refs, and only the daemon has the repository on disk. The product-api
19
+ * edge function has no git (`sessionStateHandler` says as much: "the server
20
+ * has no git access").
21
+ * - Granting a number needs the DATABASE, atomically. Two teams claiming at
22
+ * once must be separated by a constraint, not by timing, and the constraint
23
+ * is the partial unique index on `product_migration_claims`.
24
+ *
25
+ * So the held set has to cross the gap. The daemon PUBLISHES it -- per product,
26
+ * on the heartbeat, the way `metadata.verificationPostures` is published for
27
+ * the same reason ("posture is workstation-local config the API cannot itself
28
+ * derive; the daemon is the single source") -- and the edge handler grants
29
+ * against the published set plus the live claims it can see itself.
30
+ *
31
+ * THE OTHER SHAPE, AND WHY NOT IT. Serving the whole claim from the daemon was
32
+ * the alternative. It collapses: either the daemon reads-then-inserts, which is
33
+ * the two-worktrees race reborn one altitude up, or it still calls an edge
34
+ * handler to do the atomic insert -- in which case that handler exists anyway
35
+ * and the daemon is an extra hop in front of it. The decision is recorded on
36
+ * the delivery.
37
+ *
38
+ * WHY A STALE SNAPSHOT IS SAFE, AND WHERE IT IS NOT
39
+ * -------------------------------------------------
40
+ * The published set is only ever an INPUT TO THE CANDIDATE. It never decides
41
+ * anything: the unique index decides. A snapshot read a minute ago can make the
42
+ * handler propose a number that a concurrent claim just took -- and the insert
43
+ * fails, and the handler recomputes. A stale snapshot costs a retry; it cannot
44
+ * cost a collision.
45
+ *
46
+ * What it CAN cost is a number granted against branch tips that have since
47
+ * moved -- a migration landed on a sibling branch after the snapshot was taken,
48
+ * and no claim was recorded for it (a team that skipped the rule). That is why
49
+ * every reading carries `tips`: the exact sha each ref was at when it was read.
50
+ * The confirm pass re-checks a claim whose snapshot predates the tips its branch
51
+ * actually had. A snapshot with no recorded staleness is a silent read-then-write
52
+ * race wearing a different hat, so the tips are not optional decoration.
53
+ *
54
+ * WHAT AN UNREADABLE REF DOES
55
+ * ---------------------------
56
+ * It makes the whole reading INCOMPLETE, and the reading says so. A ref that
57
+ * could not be read proves nothing about what it holds, and a held set quietly
58
+ * missing one branch's numbers tells the allocator those numbers are free --
59
+ * failing open on exactly the question being asked. An incomplete reading is
60
+ * published as incomplete and the handler refuses to grant against it.
61
+ *
62
+ * A ref that does not EXIST is different and is not incompleteness: a deleted
63
+ * branch holds nothing. That distinction is the reader's (`migration-inventory`)
64
+ * and is simply carried through here.
65
+ *
66
+ * @module migration-held-set
67
+ */
68
+ import { readMigrationInventory, defaultMigrationInventoryDeps, } from './migration-inventory.js';
69
+ import { declaredOrdinalWidth } from './migration-manifest.js';
70
+ /** Real git, real declaration reader, real clock. */
71
+ export function defaultMigrationHeldSetDeps() {
72
+ return { ...defaultMigrationInventoryDeps(), now: () => Date.now() };
73
+ }
74
+ /**
75
+ * The ordinals a ref's JOURNAL registers, normalised to the product's width.
76
+ *
77
+ * WHY THE JOURNAL COUNTS AT ALL. On a hand-registered product the journal is
78
+ * half the record: a migration is its numbered filename AND its `idx` entry. A
79
+ * ref can register an idx whose file is not visible -- mid-rename, a file
80
+ * removed but the entry left, a journal edited ahead of the file -- and a held
81
+ * set built from filenames alone reports that number free. The allocator then
82
+ * grants it, and the product ends with two migrations at one position, which is
83
+ * the failure this whole mechanism exists to remove.
84
+ *
85
+ * WHY NORMALISATION IS REQUIRED, NOT COSMETIC. The journal records `idx` as a
86
+ * NUMBER (352) while the filename carries it zero-padded (`0352`). Unioning the
87
+ * two raw would put both `352` and `0352` in the held set -- two widths -- and
88
+ * the candidate rule refuses a product whose ordinals are not one fixed width.
89
+ * A naive union would therefore break every claim on exactly the products this
90
+ * fixes. So a journal idx is padded to the width the product uses.
91
+ *
92
+ * WHEN IT CANNOT BE NORMALISED, THE READING IS INCOMPLETE. An idx wider than
93
+ * the width, one that is not a plain number, and -- the case this argument was
94
+ * missing -- ANY idx at all when no width is established. Dropping such an
95
+ * entry silently is the fail-open; guessing a placement is worse; and using the
96
+ * raw value is worst, because it is neither dropped nor refused but published
97
+ * as though it were a number on this product. It is reported instead, and the
98
+ * caller refuses to grant against the reading rather than granting around the
99
+ * part it could not read.
100
+ *
101
+ * @param width The product's ordinal width, or null when none is established.
102
+ * See `resolveOrdinalWidth`.
103
+ */
104
+ export function journalOrdinals(journal, width) {
105
+ if (journal === undefined || journal.length === 0) {
106
+ return { ordinals: [], unnormalisable: [] };
107
+ }
108
+ const ordinals = [];
109
+ const unnormalisable = [];
110
+ for (const entry of journal) {
111
+ const idx = entry.idx.trim();
112
+ if (!/^\d+$/.test(idx) || width === null || idx.length > width) {
113
+ unnormalisable.push(entry.idx);
114
+ continue;
115
+ }
116
+ ordinals.push(idx.padStart(width, '0'));
117
+ }
118
+ return { ordinals, unnormalisable };
119
+ }
120
+ /**
121
+ * The width this product's ordinals are padded to, across the WHOLE reading.
122
+ *
123
+ * A PROPERTY OF THE PRODUCT, NOT OF ONE REF. It used to be derived per ref,
124
+ * from that ref's own filenames -- so a ref holding only a journal entry had no
125
+ * width, and a ref holding files a moment before the reading had one. The width
126
+ * a number is padded to cannot depend on which branch you happen to be looking
127
+ * at; it is the product's numbering convention, and it is read once for the
128
+ * whole reading.
129
+ *
130
+ * Declaration first (it fixes the width authoritatively -- no inventoried file
131
+ * can disagree with a pattern it had to match), then the files, which must all
132
+ * agree. Null when neither establishes one: a product with no declared width
133
+ * and no files. Every journal entry is then unnormalisable and the reading is
134
+ * incomplete, which is the honest answer -- the alternative was publishing a
135
+ * bare `351` and granting `352` into a four-digit namespace.
136
+ */
137
+ export function resolveOrdinalWidth(declaredWidth, fileOrdinals) {
138
+ if (declaredWidth !== null)
139
+ return declaredWidth;
140
+ const widths = [...new Set(fileOrdinals.map((o) => o.length))];
141
+ return widths.length === 1 ? (widths[0] ?? null) : null;
142
+ }
143
+ /** The commit `ref` points at, or null when it cannot be resolved. */
144
+ function resolveTip(repoPath, ref, deps) {
145
+ const result = deps.runGit(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], repoPath);
146
+ if (!result.success)
147
+ return null;
148
+ const tip = result.output.trim();
149
+ return tip === '' ? null : tip;
150
+ }
151
+ /** Ask one ref what it holds, or abort the whole reading. */
152
+ function readRefRaw(repoPath, ref, deps) {
153
+ // RESOLVE FIRST, THEN READ AT THE SHA. A branch is a MUTABLE pointer: read
154
+ // through the name and resolve afterwards, and the tip can name a commit
155
+ // the inventory never saw -- a reading that claims a provenance it does not
156
+ // have, which is worse than no provenance at all, because it looks sound.
157
+ // Reading at the resolved sha makes the two agree by construction: a sha is
158
+ // immutable, so nothing the branch does afterwards can move what was read.
159
+ const tip = resolveTip(repoPath, ref, deps);
160
+ if (tip === null) {
161
+ // No immutable point to read at. Not an absence -- `readMigrationInventory`
162
+ // distinguishes that -- so ask it, and let it say which.
163
+ const unresolved = readMigrationInventory(repoPath, ref, deps);
164
+ if (unresolved.kind === 'refAbsent') {
165
+ return { ok: { ref, tip: null, outcome: 'absent' } };
166
+ }
167
+ if (unresolved.kind === 'undeclared')
168
+ return { abort: { kind: 'undeclared' } };
169
+ if (unresolved.kind === 'declarationUnreadable') {
170
+ return { abort: { kind: 'declarationUnreadable', reason: unresolved.reason } };
171
+ }
172
+ // It read through the mutable name but has no sha: an anchorless reading.
173
+ // Its ordinals still count as TAKEN -- dropping them would be the
174
+ // fail-open this module exists to avoid -- but the reading is incomplete,
175
+ // because a claim granted against it could never be re-checked.
176
+ return {
177
+ ok: {
178
+ ref,
179
+ tip: null,
180
+ outcome: 'unreadable',
181
+ ...(unresolved.kind === 'read' ? { inventory: unresolved.inventory } : {}),
182
+ reason: `${ref} carries no resolvable sha, so the reading has no point to `
183
+ + 're-check a claim against',
184
+ },
185
+ };
186
+ }
187
+ // Everything below is read AT `tip`, never through `ref`.
188
+ const result = readMigrationInventory(repoPath, tip, deps);
189
+ switch (result.kind) {
190
+ case 'read':
191
+ return { ok: { ref, tip, outcome: 'read', inventory: result.inventory } };
192
+ case 'refAbsent':
193
+ // The sha resolved a moment ago and is gone now (a prune between the
194
+ // two calls). That is not an established absence of the BRANCH, so it
195
+ // is treated as unreadable rather than as "holds nothing".
196
+ return {
197
+ ok: {
198
+ ref,
199
+ tip,
200
+ outcome: 'unreadable',
201
+ reason: `${ref} resolved to ${tip}, which was then unreadable`,
202
+ },
203
+ };
204
+ case 'refUnreadable':
205
+ return { ok: { ref, tip: null, outcome: 'unreadable', reason: result.reason } };
206
+ case 'undeclared':
207
+ case 'declarationUnreadable':
208
+ // The declaration was read once above; a mid-walk change means the
209
+ // reading can no longer be trusted as a whole.
210
+ return {
211
+ abort: result.kind === 'undeclared'
212
+ ? { kind: 'undeclared' }
213
+ : { kind: 'declarationUnreadable', reason: result.reason },
214
+ };
215
+ }
216
+ }
217
+ /** Pass two: normalise one raw reading against the product's width. */
218
+ function foldRefReading(raw, width) {
219
+ const files = raw.inventory?.migrations.map((m) => m.ordinal) ?? [];
220
+ const journal = journalOrdinals(raw.inventory?.journal, width);
221
+ const ordinals = [...new Set([...files, ...journal.ordinals])].sort();
222
+ // An unnormalisable entry is a registered position this reader cannot place
223
+ // in the sequence. Granting around it would be granting against a partial
224
+ // view, so it makes the reading incomplete exactly as an unreadable ref does.
225
+ const unplaceable = journal.unnormalisable.length > 0
226
+ ? (width === null
227
+ ? `${raw.ref} registers journal entries (${journal.unnormalisable.join(', ')}) but this `
228
+ + 'product establishes no ordinal width -- no file to read one from and no fixed '
229
+ + 'width in its declaration -- so they cannot be placed in the sequence'
230
+ : `${raw.ref} registers journal entries whose idx cannot be placed in `
231
+ + `this product's numbering (${journal.unnormalisable.join(', ')})`)
232
+ : null;
233
+ const reason = raw.reason ?? unplaceable ?? undefined;
234
+ return {
235
+ reading: {
236
+ ref: raw.ref,
237
+ tip: raw.tip,
238
+ outcome: raw.outcome,
239
+ ordinals,
240
+ ...(reason !== undefined ? { reason } : {}),
241
+ },
242
+ complete: raw.outcome !== 'unreadable' && unplaceable === null,
243
+ };
244
+ }
245
+ /**
246
+ * Read every ref and fold what they hold into one set.
247
+ *
248
+ * @param repoPath The product's repo root.
249
+ * @param refs Integration plus every live focus branch. Duplicates are fine
250
+ * (a focus whose branch IS integration reads once).
251
+ */
252
+ export function computeMigrationHeldSet(repoPath, refs, deps = defaultMigrationHeldSetDeps()) {
253
+ let scheme;
254
+ let declaredWidth;
255
+ try {
256
+ const manifest = deps.readManifest(repoPath);
257
+ if (manifest === undefined)
258
+ return { kind: 'undeclared' };
259
+ scheme = manifest.scheme;
260
+ declaredWidth = declaredOrdinalWidth(manifest.filePattern);
261
+ }
262
+ catch (err) {
263
+ return { kind: 'declarationUnreadable', reason: String(err?.message ?? err) };
264
+ }
265
+ const raws = [];
266
+ for (const ref of [...new Set(refs)]) {
267
+ const outcome = readRefRaw(repoPath, ref, deps);
268
+ if ('abort' in outcome)
269
+ return outcome.abort;
270
+ raws.push(outcome.ok);
271
+ }
272
+ const width = resolveOrdinalWidth(declaredWidth, raws.flatMap((raw) => raw.inventory?.migrations.map((m) => m.ordinal) ?? []));
273
+ const readings = [];
274
+ const held = new Set();
275
+ let complete = true;
276
+ for (const raw of raws) {
277
+ const folded = foldRefReading(raw, width);
278
+ if (!folded.complete)
279
+ complete = false;
280
+ for (const ordinal of folded.reading.ordinals)
281
+ held.add(ordinal);
282
+ readings.push(folded.reading);
283
+ }
284
+ return {
285
+ kind: 'held',
286
+ heldSet: {
287
+ scheme,
288
+ held: [...held].sort(),
289
+ readings,
290
+ complete,
291
+ readAt: deps.now(),
292
+ },
293
+ };
294
+ }
295
+ //# sourceMappingURL=migration-held-set.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-held-set.js","sourceRoot":"","sources":["../../src/migrations/migration-held-set.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * What a git REF holds in a product's declared migration namespace.
3
+ *
4
+ * WHY IT READS THE GIT TREE AND NEVER A WORKING TREE
5
+ * --------------------------------------------------
6
+ * The whole point of this module is to answer the question for a branch that is
7
+ * NOT checked out. Two focuses are armed concurrently on one product, each in
8
+ * its own worktree; the allocator that grants a migration number has to know
9
+ * what the OTHER focus's branch already holds, and the daemon never checks that
10
+ * branch out. So every read here is `git ls-tree` / `git show` against a ref.
11
+ *
12
+ * A reader that looked at a working tree could only ever see the branch it is
13
+ * standing on -- which is exactly the blind spot that lets two teams take the
14
+ * same number.
15
+ *
16
+ * WHY THE OUTCOMES ARE KEPT DISTINCT
17
+ * ----------------------------------
18
+ * This reader answers with one of five things, and collapsing any pair of them
19
+ * breaks a consumer:
20
+ *
21
+ * - `undeclared` -- the product ships no `telora.migrations.json`.
22
+ * It opted out. Its teams keep numbering as they
23
+ * do today; nothing should hold or refuse.
24
+ * - `declarationUnreadable` -- a declaration EXISTS and is broken. A claim
25
+ * against this product must be REFUSED with the
26
+ * reason, not granted a guessed number.
27
+ * - `refAbsent` -- the ref does not exist (a deleted branch, a stale
28
+ * `branch_name`). It CLAIMS NOTHING. Holding here
29
+ * would let one stale row block every future claim
30
+ * on the product.
31
+ * - `refUnreadable` -- the ref exists and could not be read. It PROVES
32
+ * NOTHING. This is the one that must hold: read as
33
+ * an empty inventory it would say "every number is
34
+ * free", failing open on precisely the question
35
+ * this reader exists to answer.
36
+ * - `read` -- the ordinals, tags and journal entries the ref
37
+ * holds. Possibly EMPTY, which is a real answer:
38
+ * "this ref holds no migrations".
39
+ *
40
+ * The last two are the load-bearing pair. `refAbsent` and `refUnreadable` are
41
+ * both "git said no", and a reader that returned one kind for both would force
42
+ * its caller either to hold on a deleted branch forever or to treat an I/O
43
+ * failure as proof of absence. So absence is POSITIVELY ESTABLISHED rather than
44
+ * inferred from a non-zero exit: `probeRefExists` is three-valued, and only a
45
+ * healthy repository that still cannot resolve the ref yields `refAbsent`.
46
+ * Everything else -- including a probe that itself failed -- is `refUnreadable`.
47
+ * Both probes run only on the failure path, so the happy path pays nothing.
48
+ *
49
+ * SCOPE: THIS IS A READER
50
+ * -----------------------
51
+ * It answers "what does this ref hold?" for ONE ref. It does not compare two
52
+ * refs, judge a merge, or render a notice -- a comparison at the merge is the
53
+ * shape this focus abandoned (two teams told to renumber from the same union at
54
+ * the same moment collide again). The allocator consumes this to compute a
55
+ * candidate; nothing here decides anything.
56
+ *
57
+ * WHY THE GIT PRIMITIVE COMES FROM daemon-core
58
+ * --------------------------------------------
59
+ * `runGit` is taken from `@telora/daemon-core` rather than the daemon's own
60
+ * `git/types.ts` wrapper: this area is a rank-0 leaf in
61
+ * `scripts/daemon-layer-ranks.json` and `git/` sits above it, so importing the
62
+ * wrapper would be an upward import. The wrapper adds only a default timeout,
63
+ * which is passed explicitly here.
64
+ *
65
+ * @module migration-inventory
66
+ */
67
+ import { type GitResult } from '@telora/daemon-core';
68
+ import { type MigrationManifest, type MigrationManifestFs } from './migration-manifest.js';
69
+ /** One migration file a ref holds. */
70
+ export interface MigrationEntry {
71
+ /**
72
+ * The migration's number, captured as a STRING by the declared pattern.
73
+ *
74
+ * Never parsed to a number: both live schemes are zero-padded fixed width
75
+ * (`0352`, `20260614172757`), so string comparison IS ordinal comparison, and
76
+ * a parse could only lose the padding that makes that true.
77
+ */
78
+ ordinal: string;
79
+ /** The migration's name, recorded alongside the number on a claim. */
80
+ tag: string;
81
+ /** Repo-relative path of the file at the ref. */
82
+ path: string;
83
+ }
84
+ /** One entry the journal registers, when the product keeps a journal. */
85
+ export interface JournalEntry {
86
+ /** The registered index, kept as a string for the same reason as `ordinal`. */
87
+ idx: string;
88
+ /** The tag the journal registers at that index. */
89
+ tag: string;
90
+ }
91
+ /** Everything one ref holds in the declared migration namespace. */
92
+ export interface MigrationInventory {
93
+ /** The ref this was read from, as given (a branch name or a sha). */
94
+ ref: string;
95
+ /** Migrations at the ref, in the order git listed them. May be empty. */
96
+ migrations: MigrationEntry[];
97
+ /** Journal entries at the ref; undefined when the product declares no journal. */
98
+ journal: JournalEntry[] | undefined;
99
+ }
100
+ /** The five distinguishable outcomes of asking a ref what it holds. */
101
+ export type MigrationInventoryResult = {
102
+ kind: 'undeclared';
103
+ } | {
104
+ kind: 'declarationUnreadable';
105
+ reason: string;
106
+ } | {
107
+ kind: 'refAbsent';
108
+ ref: string;
109
+ } | {
110
+ kind: 'refUnreadable';
111
+ ref: string;
112
+ reason: string;
113
+ } | {
114
+ kind: 'read';
115
+ inventory: MigrationInventory;
116
+ };
117
+ /** Injected seams: git and the declaration reader. Tests pass fakes. */
118
+ export interface MigrationInventoryDeps {
119
+ /** Run git in `repoPath`. Must not throw; a failure is reported on the result. */
120
+ runGit: (args: string[], repoPath: string) => GitResult;
121
+ /** Read the product's declaration. Throws when the declaration is malformed. */
122
+ readManifest: (repoPath: string, fs?: MigrationManifestFs) => MigrationManifest | undefined;
123
+ }
124
+ /** Real git + real declaration reader. */
125
+ export declare function defaultMigrationInventoryDeps(): MigrationInventoryDeps;
126
+ /**
127
+ * Parse a journal document into its entries.
128
+ *
129
+ * Accepts the two shapes the hand-written products ship: a bare array of
130
+ * entries, or an object carrying an `entries` array. Anything else -- including
131
+ * an entry missing `idx` or `tag` -- is a parse FAILURE, because a journal we
132
+ * cannot read is the unreadable state, never an empty registration.
133
+ */
134
+ export declare function parseJournal(text: string): {
135
+ entries: JournalEntry[];
136
+ } | {
137
+ error: string;
138
+ };
139
+ /**
140
+ * Which of a ref's listed files are migrations, read through the declared
141
+ * pattern. Pure.
142
+ *
143
+ * A file in the migration dir that does NOT match the pattern is not a
144
+ * migration (a README, a .gitkeep, a `meta/` subdirectory) -- not a failure.
145
+ */
146
+ export declare function readMigrationsFromPaths(paths: readonly string[], manifest: MigrationManifest): MigrationEntry[];
147
+ /** What a ref-existence probe could establish. */
148
+ export type RefProbe = 'present' | 'absent' | 'indeterminate';
149
+ /**
150
+ * Does `ref` name a commit in this repo -- and could we even tell?
151
+ *
152
+ * THREE-VALUED ON PURPOSE. `git rev-parse --verify --quiet` exits non-zero for
153
+ * BOTH a missing ref and an operational failure (a corrupt object store, a
154
+ * timeout, a repo that is not there). Collapsing those to a boolean is a
155
+ * fail-OPEN: an unreadable branch is reported as holding nothing, the held set
156
+ * still reads complete, and the allocator grants a number that branch already
157
+ * holds -- which is the exact failure the reader exists to prevent.
158
+ *
159
+ * ABSENCE MUST BE POSITIVELY ESTABLISHED, and that takes two facts: the
160
+ * REPOSITORY answers, and the REF does not. So a failed ref probe is followed
161
+ * by a repo-health probe; only a healthy repo that still cannot resolve the ref
162
+ * proves the ref is gone. Anything else is `indeterminate`, and the caller
163
+ * treats that as unreadable.
164
+ *
165
+ * `--quiet` suppresses git's missing-ref message, so a genuine absence writes
166
+ * NOTHING to stderr. Output on stderr is therefore a second, independent signal
167
+ * that something operational went wrong, and is honoured even if the health
168
+ * probe happens to pass.
169
+ *
170
+ * Called ONLY after a read of the ref has already failed, so the happy path
171
+ * pays nothing for the distinction.
172
+ */
173
+ export declare function probeRefExists(repoPath: string, ref: string, deps: MigrationInventoryDeps): RefProbe;
174
+ /**
175
+ * What `ref` holds in `repoPath`'s declared migration namespace.
176
+ *
177
+ * Never throws. A malformed declaration, a missing ref, a failed git call and an
178
+ * unparseable journal each come back as their OWN kind, so a caller can refuse
179
+ * on a broken declaration, ignore an absent ref, and hold on an unreadable one
180
+ * without having to guess which it got.
181
+ *
182
+ * @param repoPath The product's repo root (`config.products[].repoPath`).
183
+ * @param ref Any git ref -- a branch name or a sha. Need not be checked out.
184
+ * @param deps Injectable git + declaration seams; defaults to the real ones.
185
+ */
186
+ export declare function readMigrationInventory(repoPath: string, ref: string, deps?: MigrationInventoryDeps): MigrationInventoryResult;
187
+ //# sourceMappingURL=migration-inventory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-inventory.d.ts","sourceRoot":"","sources":["../../src/migrations/migration-inventory.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAEH,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,yBAAyB,CAAC;AAEjC,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,kFAAkF;IAClF,OAAO,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;CACrC;AAED,uEAAuE;AACvE,MAAM,MAAM,wBAAwB,GAChC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAEpD,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,kFAAkF;IAClF,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC;IACxD,gFAAgF;IAChF,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,KAAK,iBAAiB,GAAG,SAAS,CAAC;CAC7F;AAED,0CAA0C;AAC1C,wBAAgB,6BAA6B,IAAI,sBAAsB,CAKtE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAiC1F;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,QAAQ,EAAE,iBAAiB,GAC1B,cAAc,EAAE,CAYlB;AAED,kDAAkD;AAClD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,eAAe,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,sBAAsB,GAC3B,QAAQ,CAQV;AA6BD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,sBAAwD,GAC7D,wBAAwB,CAwC1B"}