@cleocode/core 2026.10.12 → 2026.10.13

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 (255) hide show
  1. package/dist/build-provenance.json +2 -2
  2. package/dist/cloud/nexus-auth.d.ts.map +1 -1
  3. package/dist/cloud/nexus-auth.js +11 -2
  4. package/dist/cloud/nexus-auth.js.map +1 -1
  5. package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
  6. package/dist/cloud/nexus-cloud-status.js +18 -5
  7. package/dist/cloud/nexus-cloud-status.js.map +1 -1
  8. package/dist/cloud/nexus-credentials.d.ts +3 -2
  9. package/dist/cloud/nexus-credentials.d.ts.map +1 -1
  10. package/dist/cloud/nexus-credentials.js +58 -14
  11. package/dist/cloud/nexus-credentials.js.map +1 -1
  12. package/dist/cloud/nexus-logout.d.ts.map +1 -1
  13. package/dist/cloud/nexus-logout.js +12 -2
  14. package/dist/cloud/nexus-logout.js.map +1 -1
  15. package/dist/cloud/nexus-vault-state.d.ts.map +1 -1
  16. package/dist/cloud/nexus-vault-state.js +6 -26
  17. package/dist/cloud/nexus-vault-state.js.map +1 -1
  18. package/dist/config/registry.d.ts +21 -0
  19. package/dist/config/registry.d.ts.map +1 -1
  20. package/dist/config/registry.js +41 -0
  21. package/dist/config/registry.js.map +1 -1
  22. package/dist/dispatch/mvi-projection.d.ts.map +1 -1
  23. package/dist/dispatch/mvi-projection.js +2 -0
  24. package/dist/dispatch/mvi-projection.js.map +1 -1
  25. package/dist/docs/docs-ops.d.ts +15 -14
  26. package/dist/docs/docs-ops.d.ts.map +1 -1
  27. package/dist/docs/docs-ops.js +32 -96
  28. package/dist/docs/docs-ops.js.map +1 -1
  29. package/dist/docs/docs-read-model.d.ts +19 -10
  30. package/dist/docs/docs-read-model.d.ts.map +1 -1
  31. package/dist/docs/docs-read-model.js +31 -47
  32. package/dist/docs/docs-read-model.js.map +1 -1
  33. package/dist/docs/docs-update.d.ts +51 -1
  34. package/dist/docs/docs-update.d.ts.map +1 -1
  35. package/dist/docs/docs-update.js +235 -13
  36. package/dist/docs/docs-update.js.map +1 -1
  37. package/dist/docs/doctor.d.ts +26 -4
  38. package/dist/docs/doctor.d.ts.map +1 -1
  39. package/dist/docs/doctor.js +260 -102
  40. package/dist/docs/doctor.js.map +1 -1
  41. package/dist/docs/fetch-result.d.ts +30 -10
  42. package/dist/docs/fetch-result.d.ts.map +1 -1
  43. package/dist/docs/fetch-result.js +100 -15
  44. package/dist/docs/fetch-result.js.map +1 -1
  45. package/dist/docs/index.d.ts +1 -0
  46. package/dist/docs/index.d.ts.map +1 -1
  47. package/dist/docs/index.js +1 -0
  48. package/dist/docs/index.js.map +1 -1
  49. package/dist/docs/legacy-doc-migration.d.ts +182 -0
  50. package/dist/docs/legacy-doc-migration.d.ts.map +1 -0
  51. package/dist/docs/legacy-doc-migration.js +480 -0
  52. package/dist/docs/legacy-doc-migration.js.map +1 -0
  53. package/dist/docs/publications-store.d.ts +67 -0
  54. package/dist/docs/publications-store.d.ts.map +1 -0
  55. package/dist/docs/publications-store.js +193 -0
  56. package/dist/docs/publications-store.js.map +1 -0
  57. package/dist/docs/publish-pr.d.ts.map +1 -1
  58. package/dist/docs/publish-pr.js +31 -18
  59. package/dist/docs/publish-pr.js.map +1 -1
  60. package/dist/docs/section-leases.d.ts +233 -0
  61. package/dist/docs/section-leases.d.ts.map +1 -0
  62. package/dist/docs/section-leases.js +344 -0
  63. package/dist/docs/section-leases.js.map +1 -0
  64. package/dist/docs/section-merge.d.ts +159 -0
  65. package/dist/docs/section-merge.d.ts.map +1 -0
  66. package/dist/docs/section-merge.js +215 -0
  67. package/dist/docs/section-merge.js.map +1 -0
  68. package/dist/docs/section-write-path.d.ts +191 -0
  69. package/dist/docs/section-write-path.d.ts.map +1 -0
  70. package/dist/docs/section-write-path.js +423 -0
  71. package/dist/docs/section-write-path.js.map +1 -0
  72. package/dist/docs/stats.d.ts +56 -0
  73. package/dist/docs/stats.d.ts.map +1 -0
  74. package/dist/docs/stats.js +119 -0
  75. package/dist/docs/stats.js.map +1 -0
  76. package/dist/gc/cleanup.d.ts +1 -1
  77. package/dist/gc/cleanup.d.ts.map +1 -1
  78. package/dist/gc/cleanup.js +10 -1
  79. package/dist/gc/cleanup.js.map +1 -1
  80. package/dist/gc/janitor.js +1 -1
  81. package/dist/gc/janitor.js.map +1 -1
  82. package/dist/internal.d.ts +1 -1
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.js +1 -1
  85. package/dist/internal.js.map +1 -1
  86. package/dist/memory/pipeline-manifest-sqlite.d.ts +34 -0
  87. package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
  88. package/dist/memory/pipeline-manifest-sqlite.js +114 -0
  89. package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
  90. package/dist/nexus/analyze-orchestrator.d.ts +5 -1
  91. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  92. package/dist/nexus/analyze-orchestrator.js +25 -13
  93. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  94. package/dist/nexus/assessment-store.d.ts +8 -4
  95. package/dist/nexus/assessment-store.d.ts.map +1 -1
  96. package/dist/nexus/assessment-store.js +18 -27
  97. package/dist/nexus/assessment-store.js.map +1 -1
  98. package/dist/orchestration/index.d.ts.map +1 -1
  99. package/dist/orchestration/index.js +6 -0
  100. package/dist/orchestration/index.js.map +1 -1
  101. package/dist/orchestration/lean-change.d.ts +104 -5
  102. package/dist/orchestration/lean-change.d.ts.map +1 -1
  103. package/dist/orchestration/lean-change.js +162 -6
  104. package/dist/orchestration/lean-change.js.map +1 -1
  105. package/dist/orchestration/spawn-prompt.d.ts +6 -1
  106. package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
  107. package/dist/orchestration/spawn-prompt.js +3 -1
  108. package/dist/orchestration/spawn-prompt.js.map +1 -1
  109. package/dist/orchestration/spawn.d.ts.map +1 -1
  110. package/dist/orchestration/spawn.js +6 -0
  111. package/dist/orchestration/spawn.js.map +1 -1
  112. package/dist/release/open.d.ts.map +1 -1
  113. package/dist/release/open.js +6 -2
  114. package/dist/release/open.js.map +1 -1
  115. package/dist/release/plan.d.ts.map +1 -1
  116. package/dist/release/plan.js +33 -2
  117. package/dist/release/plan.js.map +1 -1
  118. package/dist/resources/admission-ledger.d.ts +5 -0
  119. package/dist/resources/admission-ledger.d.ts.map +1 -1
  120. package/dist/resources/admission-ledger.js +1 -1
  121. package/dist/resources/admission-ledger.js.map +1 -1
  122. package/dist/resources/run-admission.d.ts.map +1 -1
  123. package/dist/resources/run-admission.js +4 -1
  124. package/dist/resources/run-admission.js.map +1 -1
  125. package/dist/resources/run-governed.d.ts +29 -4
  126. package/dist/resources/run-governed.d.ts.map +1 -1
  127. package/dist/resources/run-governed.js +113 -73
  128. package/dist/resources/run-governed.js.map +1 -1
  129. package/dist/sentient/worktree-dispatch.d.ts +12 -1
  130. package/dist/sentient/worktree-dispatch.d.ts.map +1 -1
  131. package/dist/sentient/worktree-dispatch.js +19 -0
  132. package/dist/sentient/worktree-dispatch.js.map +1 -1
  133. package/dist/spawn/branch-lock.d.ts +11 -1
  134. package/dist/spawn/branch-lock.d.ts.map +1 -1
  135. package/dist/spawn/branch-lock.js +54 -4
  136. package/dist/spawn/branch-lock.js.map +1 -1
  137. package/dist/sticky/archive.d.ts +1 -0
  138. package/dist/sticky/archive.d.ts.map +1 -1
  139. package/dist/sticky/archive.js +13 -0
  140. package/dist/sticky/archive.js.map +1 -1
  141. package/dist/sticky/engine-ops.d.ts.map +1 -1
  142. package/dist/sticky/engine-ops.js +7 -0
  143. package/dist/sticky/engine-ops.js.map +1 -1
  144. package/dist/store/attachment-repair.d.ts +10 -4
  145. package/dist/store/attachment-repair.d.ts.map +1 -1
  146. package/dist/store/attachment-repair.js +52 -25
  147. package/dist/store/attachment-repair.js.map +1 -1
  148. package/dist/store/attachment-store.d.ts.map +1 -1
  149. package/dist/store/attachment-store.js +27 -18
  150. package/dist/store/attachment-store.js.map +1 -1
  151. package/dist/store/blob-keep.d.ts +83 -0
  152. package/dist/store/blob-keep.d.ts.map +1 -1
  153. package/dist/store/blob-keep.js +139 -0
  154. package/dist/store/blob-keep.js.map +1 -1
  155. package/dist/store/blob-ops.d.ts +24 -0
  156. package/dist/store/blob-ops.d.ts.map +1 -1
  157. package/dist/store/blob-ops.js +47 -0
  158. package/dist/store/blob-ops.js.map +1 -1
  159. package/dist/store/conduit-doc-history.d.ts +209 -0
  160. package/dist/store/conduit-doc-history.d.ts.map +1 -1
  161. package/dist/store/conduit-doc-history.js +315 -3
  162. package/dist/store/conduit-doc-history.js.map +1 -1
  163. package/dist/store/decision-adr-links.d.ts +11 -0
  164. package/dist/store/decision-adr-links.d.ts.map +1 -1
  165. package/dist/store/decision-adr-links.js +20 -1
  166. package/dist/store/decision-adr-links.js.map +1 -1
  167. package/dist/store/display-id-alias.d.ts +8 -2
  168. package/dist/store/display-id-alias.d.ts.map +1 -1
  169. package/dist/store/display-id-alias.js +8 -3
  170. package/dist/store/display-id-alias.js.map +1 -1
  171. package/dist/store/lock.d.ts +18 -0
  172. package/dist/store/lock.d.ts.map +1 -1
  173. package/dist/store/lock.js +41 -0
  174. package/dist/store/lock.js.map +1 -1
  175. package/dist/store/row-identity-registry.d.ts.map +1 -1
  176. package/dist/store/row-identity-registry.js +17 -0
  177. package/dist/store/row-identity-registry.js.map +1 -1
  178. package/dist/store/schema/cleo-project/conduit.d.ts +16 -0
  179. package/dist/store/schema/cleo-project/conduit.d.ts.map +1 -1
  180. package/dist/store/schema/cleo-project/conduit.js +8 -0
  181. package/dist/store/schema/cleo-project/conduit.js.map +1 -1
  182. package/dist/store/schema/cleo-project/docs.d.ts +294 -0
  183. package/dist/store/schema/cleo-project/docs.d.ts.map +1 -1
  184. package/dist/store/schema/cleo-project/docs.js +82 -0
  185. package/dist/store/schema/cleo-project/docs.js.map +1 -1
  186. package/dist/store/sync/hlc.d.ts +9 -0
  187. package/dist/store/sync/hlc.d.ts.map +1 -1
  188. package/dist/store/sync/hlc.js +9 -0
  189. package/dist/store/sync/hlc.js.map +1 -1
  190. package/dist/store/sync/merge/engine.d.ts.map +1 -1
  191. package/dist/store/sync/merge/engine.js +4 -2
  192. package/dist/store/sync/merge/engine.js.map +1 -1
  193. package/dist/store/sync/merge/rules.d.ts +12 -1
  194. package/dist/store/sync/merge/rules.d.ts.map +1 -1
  195. package/dist/store/sync/merge/rules.js +76 -1
  196. package/dist/store/sync/merge/rules.js.map +1 -1
  197. package/dist/store/sync/replica-registry.d.ts +6 -2
  198. package/dist/store/sync/replica-registry.d.ts.map +1 -1
  199. package/dist/store/sync/replica-registry.js +21 -9
  200. package/dist/store/sync/replica-registry.js.map +1 -1
  201. package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
  202. package/dist/store/sync/timestamp-canon.js +6 -0
  203. package/dist/store/sync/timestamp-canon.js.map +1 -1
  204. package/dist/store/table-classification.d.ts +58 -0
  205. package/dist/store/table-classification.d.ts.map +1 -1
  206. package/dist/store/table-classification.js +72 -0
  207. package/dist/store/table-classification.js.map +1 -1
  208. package/dist/store/twin-collapse.d.ts.map +1 -1
  209. package/dist/store/twin-collapse.js +4 -1
  210. package/dist/store/twin-collapse.js.map +1 -1
  211. package/dist/store/vault-manifest.d.ts +10 -1
  212. package/dist/store/vault-manifest.d.ts.map +1 -1
  213. package/dist/store/vault-manifest.js +59 -1
  214. package/dist/store/vault-manifest.js.map +1 -1
  215. package/dist/tasks/affected-scope.d.ts +11 -0
  216. package/dist/tasks/affected-scope.d.ts.map +1 -1
  217. package/dist/tasks/affected-scope.js +30 -11
  218. package/dist/tasks/affected-scope.js.map +1 -1
  219. package/dist/tasks/cancel-ops.d.ts.map +1 -1
  220. package/dist/tasks/cancel-ops.js +7 -2
  221. package/dist/tasks/cancel-ops.js.map +1 -1
  222. package/dist/tasks/change-set.d.ts +22 -0
  223. package/dist/tasks/change-set.d.ts.map +1 -1
  224. package/dist/tasks/change-set.js +119 -2
  225. package/dist/tasks/change-set.js.map +1 -1
  226. package/dist/tasks/complete.d.ts.map +1 -1
  227. package/dist/tasks/complete.js +7 -0
  228. package/dist/tasks/complete.js.map +1 -1
  229. package/dist/tasks/done-plan.d.ts +5 -0
  230. package/dist/tasks/done-plan.d.ts.map +1 -1
  231. package/dist/tasks/done-plan.js +54 -19
  232. package/dist/tasks/done-plan.js.map +1 -1
  233. package/dist/tasks/engine-wrap.d.ts +4 -0
  234. package/dist/tasks/engine-wrap.d.ts.map +1 -1
  235. package/dist/tasks/engine-wrap.js +20 -4
  236. package/dist/tasks/engine-wrap.js.map +1 -1
  237. package/dist/tasks/evidence-paths.d.ts +11 -0
  238. package/dist/tasks/evidence-paths.d.ts.map +1 -1
  239. package/dist/tasks/evidence-paths.js +12 -0
  240. package/dist/tasks/evidence-paths.js.map +1 -1
  241. package/dist/tools/fs.d.ts +7 -3
  242. package/dist/tools/fs.d.ts.map +1 -1
  243. package/dist/tools/fs.js +23 -8
  244. package/dist/tools/fs.js.map +1 -1
  245. package/dist/worktree/list.d.ts.map +1 -1
  246. package/dist/worktree/list.js +9 -0
  247. package/dist/worktree/list.js.map +1 -1
  248. package/dist/worktree/prune.d.ts.map +1 -1
  249. package/dist/worktree/prune.js +52 -1
  250. package/dist/worktree/prune.js.map +1 -1
  251. package/migrations/drizzle-cleo-project/20261013000000_t13360-migrated-doc-import/migration.sql +61 -0
  252. package/migrations/drizzle-cleo-project/20261014000000_t13362-docs-publications-ledger/migration.sql +45 -0
  253. package/package.json +13 -13
  254. package/templates/CLEO-INJECTION.md +1 -1
  255. package/templates/CLEO-REFERENCE.md +2 -2
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Section CRDT merge engine — the pure core of the CleoDocs re-architecture
3
+ * write path (T13355 · spec `cleodocs-rearch-design` §2 option b · D11165).
4
+ *
5
+ * Every document section is one Loro document (root `LoroText` named
6
+ * `content`), persisted as opaque snapshot bytes in the local-only
7
+ * `section_crdt_states` table. This module is the ONLY production importer of
8
+ * `llmtxt/crdt-primitives` (the llmtxt WASM-backed Loro primitives); the
9
+ * store orchestration in `section-write-path.ts` composes these functions
10
+ * with the canonical `conduit-doc-history.ts` accessors.
11
+ *
12
+ * Merge composition (spike finding, T13353): the WASM `crdt_merge_updates`
13
+ * is LOSSY in llmtxt@2026.5.15 — it returns an empty-doc snapshot for any
14
+ * input (kryptobaseddev/llmtxt#17, tracked in T13356). All merging here
15
+ * therefore composes updates via sequential {@link mergeSectionUpdates}
16
+ * (`crdt_apply_update`), which Loro makes idempotent and convergent:
17
+ * applying each peer's incremental update in turn yields every edit with an
18
+ * order-independent final text. `crdt_merge_updates` must NOT be used in the
19
+ * production path until the upstream fix lands.
20
+ *
21
+ * @module
22
+ * @task T13355
23
+ * @epic T13340
24
+ * @saga T13339
25
+ */
26
+ import { crdt_apply_update, crdt_get_text, crdt_make_incremental_update, crdt_make_state, } from 'llmtxt/crdt-primitives';
27
+ /**
28
+ * Deterministic section id for a split index. Section ids are positional
29
+ * (`s0` is the H1 + preamble, `s1` the first `## ` block, …): stable across
30
+ * devices for the same document shape and independent of heading text.
31
+ * Positional ids are a known simplification — heading-anchored ids are the
32
+ * filed follow-up (T13584).
33
+ *
34
+ * @param index - The section's position in {@link splitDocIntoSections}.
35
+ * @returns The section id recorded in `sections_modified`.
36
+ */
37
+ export function sectionId(index) {
38
+ return `s${index}`;
39
+ }
40
+ /**
41
+ * Split a markdown document into sections deterministically: the H1 +
42
+ * preamble (everything before the first `## ` heading) is section 0; each
43
+ * `## ` heading starts a new section that runs (with its nested `###`
44
+ * blocks) to the next `## ` or EOF. Concatenating the sections with
45
+ * {@link joinSections} reproduces the input byte-for-byte, so the split is
46
+ * lossless and order-stable.
47
+ *
48
+ * @param markdown - The full document text.
49
+ * @returns The sections in document order.
50
+ */
51
+ export function splitDocIntoSections(markdown) {
52
+ const lines = markdown.split('\n');
53
+ const sections = [];
54
+ let current = [];
55
+ for (const line of lines) {
56
+ if (line.startsWith('## ') && current.length > 0) {
57
+ sections.push(current.join('\n'));
58
+ current = [];
59
+ }
60
+ current.push(line);
61
+ }
62
+ sections.push(current.join('\n'));
63
+ return sections;
64
+ }
65
+ /**
66
+ * The inverse of {@link splitDocIntoSections}.
67
+ *
68
+ * @param sections - Sections in document order.
69
+ * @returns The reassembled document text.
70
+ */
71
+ export function joinSections(sections) {
72
+ return sections.join('\n');
73
+ }
74
+ /**
75
+ * Seed a section Loro state from full text (a new or re-based section).
76
+ *
77
+ * @param text - The section's full text.
78
+ * @returns A Loro snapshot encoding the section.
79
+ */
80
+ export function seedSectionState(text) {
81
+ return crdt_make_state(text);
82
+ }
83
+ /**
84
+ * Express one local edit as an incremental Loro update against a base
85
+ * state. The bundled primitives are append-shaped: the edit is the text to
86
+ * append to the base section (callers compute it as the tail of the new
87
+ * text when the old text is a prefix of it).
88
+ *
89
+ * @param base - The base section snapshot the edit was authored against.
90
+ * @param appended - The text the edit appends to the base.
91
+ * @returns The incremental update (the delta only, not a full snapshot).
92
+ */
93
+ export function makeSectionEditUpdate(base, appended) {
94
+ return crdt_make_incremental_update(base, appended);
95
+ }
96
+ /**
97
+ * Merge incremental section updates onto a base snapshot by sequential
98
+ * `crdt_apply_update`. This is the production merge composition: Loro import
99
+ * is idempotent and convergent, so the result carries every applied edit and
100
+ * is independent of update order. Do NOT replace this with
101
+ * `crdt_merge_updates` — that WASM export is lossy in llmtxt@2026.5.15
102
+ * (T13353 spike, T13356, kryptobaseddev/llmtxt#17).
103
+ *
104
+ * @param base - The base section snapshot.
105
+ * @param updates - Peer incremental updates, in any order.
106
+ * @returns The merged section snapshot.
107
+ */
108
+ export function mergeSectionUpdates(base, updates) {
109
+ let state = base;
110
+ for (const update of updates) {
111
+ state = crdt_apply_update(state, update);
112
+ }
113
+ return state;
114
+ }
115
+ /**
116
+ * Extract the plain text of a section snapshot.
117
+ *
118
+ * @param state - A Loro snapshot from {@link seedSectionState} or {@link mergeSectionUpdates}.
119
+ * @returns The section's current text.
120
+ */
121
+ export function sectionStateText(state) {
122
+ return crdt_get_text(state);
123
+ }
124
+ /**
125
+ * Apply one section's new text to its stored snapshot.
126
+ *
127
+ * `baseText` is the section text the incoming edit was AUTHORED AGAINST —
128
+ * the caller's concurrency reference (T13674). A base-less write passes the
129
+ * previous head's section text (single-writer assumption: the writer read
130
+ * the head and edited it). A write carrying a declared base passes the base
131
+ * version's section text, so a writer whose fetch predates a concurrent
132
+ * landing is recognized as stale instead of being mistaken for a
133
+ * single writer replacing the head.
134
+ *
135
+ * While the stored state equals the base text it holds no unlanded edit
136
+ * relative to what the writer saw, and any difference the writer authored
137
+ * is an ordinary edit — which the append-shaped primitives cannot express,
138
+ * so the section is re-seeded from the input and projects EXACTLY
139
+ * (mid-text replaces and deletions apply verbatim, never a fabricated
140
+ * concatenation — T13589). When the stored state differs from the base
141
+ * text, another writer's edit landed after the caller's fetch (T13674):
142
+ *
143
+ * - an incoming edit that is a pure APPEND relative to the common base
144
+ * (`nextText` extends `baseText`) composes onto the stored state, so
145
+ * both tails survive — this is never a fabrication;
146
+ * - any NON-append incoming edit (a replace or delete relative to the
147
+ * base) CANNOT be expressed with append-only primitives without
148
+ * inventing text, so the function reports `conflicted: true` and keeps
149
+ * the stored state untouched. The write path lands the incoming edit as
150
+ * its own append-only version row, keeps the stored projection as the
151
+ * head, and records the conflict on the head — both versions preserved,
152
+ * nothing fabricated.
153
+ *
154
+ * Semantics:
155
+ *
156
+ * - `stored === null` (first write of the section): seed a fresh state
157
+ * from the new text.
158
+ * - The stored text equals the new text: no-op (`changed: false`).
159
+ * - `baseText === null` (no base text available for this section — no
160
+ * head/base yet, or the reference version's blob is not resolvable
161
+ * locally): the single-writer assumption; re-seed from the new text
162
+ * (exact).
163
+ * - The stored text equals `baseText` (single writer against the
164
+ * reference): re-seed from the new text — the input projects exactly,
165
+ * deletions included.
166
+ * - The stored text differs from `baseText` and the incoming text equals
167
+ * `baseText`: the writer authored nothing new; no-op.
168
+ * - The stored text differs from `baseText` and the incoming text
169
+ * extends `baseText` (append): compose the incoming tail onto the
170
+ * stored state; both edits survive, `conflicted: false`.
171
+ * - The stored text differs from `baseText` and the incoming edit is a
172
+ * non-append relative to `baseText`: `conflicted: true`, stored state
173
+ * kept — the write path records the conflict and lands both versions.
174
+ *
175
+ * @param stored - The section's stored snapshot, or `null` when none exists.
176
+ * @param nextText - The section's new full text.
177
+ * @param baseText - The section's text in the version the incoming edit was
178
+ * authored against (previous head for base-less writes, declared base
179
+ * otherwise), or `null` when no reference text is available.
180
+ * @returns The resulting snapshot, whether it changed, and whether the edit
181
+ * is a non-append concurrent edit the caller must record as a conflict.
182
+ * @task T13674 (the baseText reference; T13589/T13595 for the merge shapes)
183
+ */
184
+ export function applySectionTextEdit(stored, nextText, baseText) {
185
+ if (stored === null) {
186
+ return { snapshot: seedSectionState(nextText), changed: true, conflicted: false };
187
+ }
188
+ const current = sectionStateText(stored);
189
+ if (current === nextText) {
190
+ return { snapshot: stored, changed: false, conflicted: false };
191
+ }
192
+ if (baseText === null || current === baseText) {
193
+ // Single-writer edit: project the input EXACTLY. The primitives can only
194
+ // append, so a re-seed is the honest encoding of a replace/delete — it
195
+ // never fabricates a concatenation no writer authored (T13589).
196
+ return { snapshot: seedSectionState(nextText), changed: true, conflicted: false };
197
+ }
198
+ if (nextText === baseText) {
199
+ // The writer authored nothing beyond the base; the stored (concurrent)
200
+ // edit stands.
201
+ return { snapshot: stored, changed: false, conflicted: false };
202
+ }
203
+ if (nextText.startsWith(baseText)) {
204
+ // Concurrent append-append: compose the incoming tail onto the stored
205
+ // state. Both tails survive and nothing is fabricated (T13585).
206
+ const update = makeSectionEditUpdate(stored, nextText.slice(baseText.length));
207
+ return { snapshot: mergeSectionUpdates(stored, [update]), changed: true, conflicted: false };
208
+ }
209
+ // Non-append concurrent edit (replace/delete relative to the base): not
210
+ // expressible with append-only primitives without inventing text — keep
211
+ // the stored state and let the write path record the conflict, landing
212
+ // the incoming edit as its own version row (T13595).
213
+ return { snapshot: stored, changed: false, conflicted: true };
214
+ }
215
+ //# sourceMappingURL=section-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-merge.js","sourceRoot":"","sources":["../../src/docs/section-merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,4BAA4B,EAC5B,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,IAAI,KAAK,EAAE,CAAC;AACrB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAClC,OAAO,GAAG,EAAE,CAAC;QACf,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,QAA2B;IACtD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,QAAgB;IAClE,OAAO,4BAA4B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,OAA0B;IAC1E,IAAI,KAAK,GAAG,IAAI,CAAC;IACjB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,KAAK,GAAG,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAqB,EACrB,QAAgB,EAChB,QAAuB;IAEvB,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACpF,CAAC;IACD,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9C,yEAAyE;QACzE,uEAAuE;QACvE,gEAAgE;QAChE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACpF,CAAC;IACD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,uEAAuE;QACvE,eAAe;QACf,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,sEAAsE;QACtE,gEAAgE;QAChE,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9E,OAAO,EAAE,QAAQ,EAAE,mBAAmB,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAC/F,CAAC;IACD,wEAAwE;IACxE,wEAAwE;IACxE,uEAAuE;IACvE,qDAAqD;IACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Docs update write path through the section CRDT merge layer (T13355 ·
3
+ * spec `cleodocs-rearch-design` §2 option b · D11165).
4
+ *
5
+ * Every `cleo docs update` carrying new inline text is projected through
6
+ * this module after the legacy whole-row write lands (`docs-update.ts`):
7
+ *
8
+ * 1. Resolve the doc's immutable uid, adopting a legacy doc into the
9
+ * conduit family on first sight ({@link ensureConduitDoc}).
10
+ * 2. Split the new text into sections and resolve the previous head's
11
+ * section texts from its version blob — the single-writer vs.
12
+ * concurrent discriminator (T13589).
13
+ * 3. Apply each section's edit to its local Loro state
14
+ * (`section-merge.ts`): while the stored state equals the head's text
15
+ * the input projects EXACTLY (re-seed; replaces and deletions apply
16
+ * verbatim — the primitives can only append, so re-seeding is the
17
+ * honest encoding); a concurrent edit that is a pure append composes
18
+ * onto the stored state so both survive (T13585); a concurrent
19
+ * NON-append edit is never fabricated into a merge — it is landed as
20
+ * its own version row and recorded as a head conflict (T13595).
21
+ * 4. Read the MERGED text back out of the merged Loro states — never out
22
+ * of the input — and write its content-addressed blob so the version
23
+ * row's `storage_key` resolves.
24
+ * 5. Append the merged text as a DAG version row
25
+ * (`appendConduitAttachmentVersion`, parent = the previous head's
26
+ * version) — history is append-only.
27
+ * 6. Project the merged text into the current-version row via
28
+ * {@link upsertConduitDocHead}: the max-HLC head wins and a losing
29
+ * concurrent head is recorded as a conflict, never silently shadowed
30
+ * (typed rule `docs.head.max-hlc`).
31
+ * 7. Rebase the local section states onto the landed version and rebuild
32
+ * the derived contributors rollup.
33
+ *
34
+ * Steps 1–7 run inside ONE `BEGIN IMMEDIATE` transaction
35
+ * ({@link withConduitDocWriteTransaction}, T13583): a failure after the
36
+ * version append rolls everything back, so the next update cannot fork
37
+ * onto a half-landed projection. The body uses only sync-driver accessors
38
+ * and sync `node:fs` calls, so no real I/O yield can admit another writer
39
+ * on the connection mid-transaction (the documented constraint).
40
+ *
41
+ * Loro owns LOCAL-ONLY section state; the syncing rows are owned by the
42
+ * Stage C journal merge engine — one merge authority per table (§2).
43
+ *
44
+ * @module
45
+ * @task T13355
46
+ * @epic T13340
47
+ * @saga T13339
48
+ */
49
+ import { type ConduitAttachmentVersionRow, type UpsertConduitDocHeadResult } from '../store/conduit-doc-history.js';
50
+ /**
51
+ * TTL granted by the write path's lease acquisitions (seconds). Inside
52
+ * llmtxt's 1–300 s range; long enough to cover an edit burst, short enough
53
+ * that a dead session's lease dies quickly (AC4: lease state never outlives
54
+ * its session without renew).
55
+ */
56
+ export declare const SECTION_LEASE_TTL_SECONDS = 60;
57
+ /** Input for {@link projectDocUpdateThroughSections}. */
58
+ export interface ProjectDocUpdateInput {
59
+ /** The doc's live slug (or a known old slug). */
60
+ readonly slug: string;
61
+ /** Revision author agent id. */
62
+ readonly authorAgentId: string;
63
+ /** The full new document text. */
64
+ readonly content: string;
65
+ /**
66
+ * The session performing the edit. Callers that know their session (tests,
67
+ * sync apply) pass it explicitly; otherwise the projection resolves the
68
+ * caller's BOUND session ({@link resolveBoundSession}, gate 16 — never a
69
+ * bare `getActiveSession()`). With no session the edit proceeds
70
+ * UNCOORDINATED: leases are per-session state, so there is nothing to
71
+ * acquire or renew under.
72
+ */
73
+ readonly sessionId?: string;
74
+ /**
75
+ * The candidate head's HLC (journal wire form). Omit to mint one locally:
76
+ * the mint ticks forward from this device's last issued HLC and never
77
+ * lands at or below the stored head, so a local write always advances the
78
+ * projection. Sync callers pass the remote HLC to exercise the max-HLC
79
+ * rule.
80
+ */
81
+ readonly hlc?: string;
82
+ /**
83
+ * Explicit DAG parent (sync apply). Omit to chain onto the current head's
84
+ * version; `null` forces a root version.
85
+ */
86
+ readonly parentVersionUid?: string | null;
87
+ /**
88
+ * The section texts of the version this edit was AUTHORED AGAINST
89
+ * (T13674) — the write path's stale-base discriminator. Callers that
90
+ * fetched a head and declared it pass its section texts (resolved and
91
+ * validated before any write); every section is then merged relative to
92
+ * that base, so an edit authored against a superseded head composes onto
93
+ * the stored state (append) or records a conflict (non-append) instead of
94
+ * re-seeding over a concurrent writer's edit. Omit (or null) to assert
95
+ * single-writer semantics against the previous head — the pre-T13674
96
+ * behavior.
97
+ */
98
+ readonly baseSections?: readonly string[] | null;
99
+ /** Optional change summary for the version row. */
100
+ readonly changeSummary?: string;
101
+ }
102
+ /** Outcome of {@link projectDocUpdateThroughSections}. */
103
+ export interface ProjectDocUpdateResult {
104
+ /** The doc's immutable uid (`conduit_attachments.uid`). */
105
+ readonly docUid: string;
106
+ /** Whether this call adopted the doc into the conduit family. */
107
+ readonly adopted: boolean;
108
+ /** The merged document text, extracted from the merged Loro states. */
109
+ readonly mergedText: string;
110
+ /** Section ids whose state changed in this projection. */
111
+ readonly sectionsModified: readonly string[];
112
+ /** The appended DAG version row. */
113
+ readonly version: ConduitAttachmentVersionRow;
114
+ /** The head upsert outcome (advanced vs. conflict recorded). */
115
+ readonly head: UpsertConduitDocHeadResult;
116
+ /**
117
+ * The head version uid this projection chained onto (`null` when the doc
118
+ * had no head yet). Callers that declared a base compare it against their
119
+ * base to detect a stale merge (T13674).
120
+ */
121
+ readonly previousHeadVersionUid: string | null;
122
+ /**
123
+ * Non-append concurrent section edits recorded as conflicts (T13595): the
124
+ * incoming edit could not be composed without fabricating text, so it was
125
+ * landed as its own version row (append-only history preserves both) while
126
+ * the head keeps the stored projection.
127
+ */
128
+ readonly conflicts: readonly SectionConflict[];
129
+ /**
130
+ * Advisory lease notices (T13356): sections this edit touched are leased
131
+ * by ANOTHER live session. The edit proceeded anyway — leases are
132
+ * advisory ("409 is a social signal, not a hard lock", llmtxt T285).
133
+ */
134
+ readonly leaseNotices: readonly SectionLeaseNotice[];
135
+ }
136
+ /** One recorded non-append concurrent section edit (T13595). */
137
+ export interface SectionConflict {
138
+ /** The section that could not be merged. */
139
+ readonly sectionId: string;
140
+ /** The version row carrying the incoming (authored) edit — the loser. */
141
+ readonly incomingVersionUid: string;
142
+ /** The projection version row the head kept — the winner. */
143
+ readonly projectionVersionUid: string;
144
+ }
145
+ /** One advisory lease notice (T13356): the section is leased elsewhere. */
146
+ export interface SectionLeaseNotice {
147
+ /** The leased section the edit touched. */
148
+ readonly sectionId: string;
149
+ /** The current holder agent id. */
150
+ readonly holder: string;
151
+ /** The current holder's session id. */
152
+ readonly sessionId: string;
153
+ /** When the blocking lease expires without renew (ISO-8601). */
154
+ readonly expiresAt: string;
155
+ }
156
+ /**
157
+ * Project one docs update through the section CRDT merge layer.
158
+ *
159
+ * The whole projection — adopt, section-state writes, version append, head
160
+ * upsert, rebase, contributors rebuild — is one transaction
161
+ * ({@link withConduitDocWriteTransaction}); the merged text is read back
162
+ * from the merged Loro states, so concurrent same-base section edits both
163
+ * survive into the version row and the head projection.
164
+ *
165
+ * Lease coordination (T13356, ADVISORY): before the transaction, the
166
+ * projection resolves the caller's session (input, else the bound session,
167
+ * gate 16) and acquires/renews a lease for every section the edit touches.
168
+ * A section leased by another live session yields a {@link SectionLeaseNotice}
169
+ * — the 409 equivalent as data. The edit PROCEEDS regardless: "409 is a
170
+ * social signal, not a hard lock" (llmtxt T285); the CRDT layer guarantees
171
+ * nothing is lost when coordination fails. Leases acquired for a projection
172
+ * that then rolls back are released, so a failed edit leaves no stale claim.
173
+ *
174
+ * @param projectRoot - Absolute path to the CLEO project root.
175
+ * @param input - The update facts.
176
+ * @returns The merge outcome.
177
+ */
178
+ export declare function projectDocUpdateThroughSections(projectRoot: string, input: ProjectDocUpdateInput): Promise<ProjectDocUpdateResult>;
179
+ /**
180
+ * Release every section lease this session holds for a doc — the "release
181
+ * on completion" path of T13356 AC2: a session calls this when it finishes
182
+ * editing the doc, so the sections are free for the next editor before the
183
+ * TTL would have expired them.
184
+ *
185
+ * @param projectRoot - Absolute path to the CLEO project root.
186
+ * @param slug - The doc's live slug (leases are keyed by it).
187
+ * @param sessionId - The finishing session (only its own leases are released).
188
+ * @returns The section ids released.
189
+ */
190
+ export declare function releaseSectionLeases(projectRoot: string, slug: string, sessionId: string): Promise<string[]>;
191
+ //# sourceMappingURL=section-write-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-write-path.d.ts","sourceRoot":"","sources":["../../src/docs/section-write-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAMH,OAAO,EAEL,KAAK,2BAA2B,EAShC,KAAK,0BAA0B,EAIhC,MAAM,iCAAiC,CAAC;AAgBzC;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,kCAAkC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACjD,mDAAmD;IACnD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,0DAA0D;AAC1D,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAC9C,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACtD;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,6DAA6D;IAC7D,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AA2GD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CAoOjC;AAYD;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAUnB"}