@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.21

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 (195) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +76 -2
  3. package/cordis.patch.yml +363 -0
  4. package/docs/architecture.md +692 -0
  5. package/docs/autorun-guide.md +248 -0
  6. package/docs/backlog.md +2022 -0
  7. package/docs/contract.md +938 -0
  8. package/docs/development.md +990 -0
  9. package/docs/host-compaction-seam.md +225 -0
  10. package/docs/host-protected-head-ask.md +80 -0
  11. package/docs/knowledge-repo.md +1332 -0
  12. package/docs/plans-carriage.md +571 -0
  13. package/docs/project-state.md +642 -0
  14. package/docs/provider.md +98 -0
  15. package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
  16. package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
  17. package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
  18. package/docs/verify.md +561 -0
  19. package/lib/archive.d.ts +161 -0
  20. package/lib/archive.js +229 -0
  21. package/lib/archive.js.map +1 -0
  22. package/lib/arrival.d.ts +23 -0
  23. package/lib/arrival.js +125 -0
  24. package/lib/arrival.js.map +1 -0
  25. package/lib/artifact-query.d.ts +62 -0
  26. package/lib/artifact-query.js +187 -0
  27. package/lib/artifact-query.js.map +1 -0
  28. package/lib/attach.d.ts +46 -0
  29. package/lib/attach.js +120 -0
  30. package/lib/attach.js.map +1 -0
  31. package/lib/auto-save.d.ts +145 -0
  32. package/lib/auto-save.js +334 -0
  33. package/lib/auto-save.js.map +1 -0
  34. package/lib/aux-message.d.ts +17 -0
  35. package/lib/aux-message.js +20 -0
  36. package/lib/aux-message.js.map +1 -0
  37. package/lib/client.js +1419 -0
  38. package/lib/client.js.map +1 -0
  39. package/lib/codegraph.d.ts +76 -0
  40. package/lib/codegraph.js +190 -0
  41. package/lib/codegraph.js.map +1 -0
  42. package/lib/commands.d.ts +52 -0
  43. package/lib/commands.js +946 -0
  44. package/lib/commands.js.map +1 -0
  45. package/lib/compose.d.ts +30 -0
  46. package/lib/compose.js +185 -0
  47. package/lib/compose.js.map +1 -0
  48. package/lib/continue-core.d.ts +245 -0
  49. package/lib/continue-core.js +405 -0
  50. package/lib/continue-core.js.map +1 -0
  51. package/lib/diag.d.ts +55 -0
  52. package/lib/diag.js +186 -0
  53. package/lib/diag.js.map +1 -0
  54. package/lib/engine-core.d.ts +301 -0
  55. package/lib/engine-core.js +531 -0
  56. package/lib/engine-core.js.map +1 -0
  57. package/lib/engine-note.d.ts +35 -0
  58. package/lib/engine-note.js +32 -0
  59. package/lib/engine-note.js.map +1 -0
  60. package/lib/engine.d.ts +329 -0
  61. package/lib/engine.js +1179 -0
  62. package/lib/engine.js.map +1 -0
  63. package/lib/enrich-queue.d.ts +66 -0
  64. package/lib/enrich-queue.js +126 -0
  65. package/lib/enrich-queue.js.map +1 -0
  66. package/lib/enrich-store.d.ts +31 -0
  67. package/lib/enrich-store.js +103 -0
  68. package/lib/enrich-store.js.map +1 -0
  69. package/lib/enrich-wire.d.ts +163 -0
  70. package/lib/enrich-wire.js +353 -0
  71. package/lib/enrich-wire.js.map +1 -0
  72. package/lib/enrich.d.ts +102 -0
  73. package/lib/enrich.js +267 -0
  74. package/lib/enrich.js.map +1 -0
  75. package/lib/gitops.d.ts +107 -0
  76. package/lib/gitops.js +539 -0
  77. package/lib/gitops.js.map +1 -0
  78. package/lib/index.d.ts +241 -0
  79. package/lib/index.js +1162 -0
  80. package/lib/index.js.map +1 -0
  81. package/lib/indexing.d.ts +98 -0
  82. package/lib/indexing.js +223 -0
  83. package/lib/indexing.js.map +1 -0
  84. package/lib/injections.d.ts +90 -0
  85. package/lib/injections.js +165 -0
  86. package/lib/injections.js.map +1 -0
  87. package/lib/last-resort.d.ts +24 -0
  88. package/lib/last-resort.js +27 -0
  89. package/lib/last-resort.js.map +1 -0
  90. package/lib/log-shape.d.ts +38 -0
  91. package/lib/log-shape.js +93 -0
  92. package/lib/log-shape.js.map +1 -0
  93. package/lib/models-command.d.ts +74 -0
  94. package/lib/models-command.js +100 -0
  95. package/lib/models-command.js.map +1 -0
  96. package/lib/notice.d.ts +74 -0
  97. package/lib/notice.js +92 -0
  98. package/lib/notice.js.map +1 -0
  99. package/lib/plans.d.ts +154 -0
  100. package/lib/plans.js +315 -0
  101. package/lib/plans.js.map +1 -0
  102. package/lib/preset.d.ts +186 -0
  103. package/lib/preset.js +212 -0
  104. package/lib/preset.js.map +1 -0
  105. package/lib/prompt-keys.d.ts +7 -0
  106. package/lib/prompt-keys.js +8 -0
  107. package/lib/prompt-keys.js.map +1 -0
  108. package/lib/prompt-loop.d.ts +73 -0
  109. package/lib/prompt-loop.js +147 -0
  110. package/lib/prompt-loop.js.map +1 -0
  111. package/lib/prompt-push.d.ts +47 -0
  112. package/lib/prompt-push.js +115 -0
  113. package/lib/prompt-push.js.map +1 -0
  114. package/lib/prompt-runner.d.ts +48 -0
  115. package/lib/prompt-runner.js +90 -0
  116. package/lib/prompt-runner.js.map +1 -0
  117. package/lib/prompt-state.d.ts +142 -0
  118. package/lib/prompt-state.js +101 -0
  119. package/lib/prompt-state.js.map +1 -0
  120. package/lib/prompt.d.ts +270 -0
  121. package/lib/prompt.js +408 -0
  122. package/lib/prompt.js.map +1 -0
  123. package/lib/provider.d.ts +44 -0
  124. package/lib/provider.js +66 -0
  125. package/lib/provider.js.map +1 -0
  126. package/lib/redact.d.ts +24 -0
  127. package/lib/redact.js +85 -0
  128. package/lib/redact.js.map +1 -0
  129. package/lib/registry.d.ts +187 -0
  130. package/lib/registry.js +235 -0
  131. package/lib/registry.js.map +1 -0
  132. package/lib/removal.d.ts +159 -0
  133. package/lib/removal.js +556 -0
  134. package/lib/removal.js.map +1 -0
  135. package/lib/render.d.ts +97 -0
  136. package/lib/render.js +530 -0
  137. package/lib/render.js.map +1 -0
  138. package/lib/repair-run.d.ts +76 -0
  139. package/lib/repair-run.js +506 -0
  140. package/lib/repair-run.js.map +1 -0
  141. package/lib/repair.d.ts +208 -0
  142. package/lib/repair.js +357 -0
  143. package/lib/repair.js.map +1 -0
  144. package/lib/repo.d.ts +78 -0
  145. package/lib/repo.js +184 -0
  146. package/lib/repo.js.map +1 -0
  147. package/lib/rules-commands.d.ts +46 -0
  148. package/lib/rules-commands.js +379 -0
  149. package/lib/rules-commands.js.map +1 -0
  150. package/lib/rules.d.ts +267 -0
  151. package/lib/rules.js +478 -0
  152. package/lib/rules.js.map +1 -0
  153. package/lib/save-core.d.ts +72 -0
  154. package/lib/save-core.js +220 -0
  155. package/lib/save-core.js.map +1 -0
  156. package/lib/search.d.ts +112 -0
  157. package/lib/search.js +282 -0
  158. package/lib/search.js.map +1 -0
  159. package/lib/session-rules.d.ts +222 -0
  160. package/lib/session-rules.js +454 -0
  161. package/lib/session-rules.js.map +1 -0
  162. package/lib/signature.d.ts +45 -0
  163. package/lib/signature.js +199 -0
  164. package/lib/signature.js.map +1 -0
  165. package/lib/store.d.ts +156 -0
  166. package/lib/store.js +368 -0
  167. package/lib/store.js.map +1 -0
  168. package/lib/sync.d.ts +220 -0
  169. package/lib/sync.js +810 -0
  170. package/lib/sync.js.map +1 -0
  171. package/lib/tools.d.ts +113 -0
  172. package/lib/tools.js +1090 -0
  173. package/lib/tools.js.map +1 -0
  174. package/lib/treedx/client.d.ts +57 -0
  175. package/lib/treedx/client.js +82 -0
  176. package/lib/treedx/client.js.map +1 -0
  177. package/lib/treedx/provider.d.ts +39 -0
  178. package/lib/treedx/provider.js +479 -0
  179. package/lib/treedx/provider.js.map +1 -0
  180. package/lib/treedx/state.d.ts +27 -0
  181. package/lib/treedx/state.js +65 -0
  182. package/lib/treedx/state.js.map +1 -0
  183. package/lib/types.d.ts +210 -0
  184. package/lib/types.js +31 -0
  185. package/lib/types.js.map +1 -0
  186. package/lib/version.d.ts +9 -0
  187. package/lib/version.js +10 -0
  188. package/lib/version.js.map +1 -0
  189. package/lib/vocabulary.d.ts +41 -0
  190. package/lib/vocabulary.js +118 -0
  191. package/lib/vocabulary.js.map +1 -0
  192. package/lib/warnings.d.ts +10 -0
  193. package/lib/warnings.js +18 -0
  194. package/lib/warnings.js.map +1 -0
  195. package/package.json +151 -3
@@ -0,0 +1,235 @@
1
+ import { noSuggestion } from "./prompt-state.js";
2
+ export const freshSession = (sessionId) => ({
3
+ rev: 0,
4
+ parentSession: null,
5
+ rootSession: sessionId,
6
+ nextChapterNumber: 1,
7
+ nextPlanNumber: 1,
8
+ collections: [],
9
+ chapters: [],
10
+ reservations: {},
11
+ promptSuggestion: noSuggestion,
12
+ plans: {},
13
+ finalized: {},
14
+ });
15
+ /** Persist a summarize-time plan so finalization survives a restart. */
16
+ export function rememberPlan(state, compactionId, plan) {
17
+ return { ...state, plans: { ...state.plans, [compactionId]: plan } };
18
+ }
19
+ /** Record a post-commit finalization for one compaction. Same write twice is a no-op. */
20
+ export function markFinalized(state, compactionId, numbers) {
21
+ if (state.finalized[compactionId] !== undefined)
22
+ return state;
23
+ return { ...state, finalized: { ...state.finalized, [compactionId]: [...numbers] } };
24
+ }
25
+ /** True when a compaction's chapters are already committed (finalizer + scan use this). */
26
+ export const isFinalized = (state, compactionId) => state.finalized[compactionId] !== undefined;
27
+ /** Immutably updated copy with `n` numbers reserved for `attemptId`. */
28
+ export function reserve(state, attemptId, count) {
29
+ if (count <= 0 || !Number.isInteger(count)) {
30
+ throw new Error(`reserve: count must be a positive integer, got ${count}`);
31
+ }
32
+ const existing = state.reservations[attemptId];
33
+ if (existing !== undefined) {
34
+ if (existing.length !== count) {
35
+ // Same attempt, different size: the caller's chapter set changed under a
36
+ // half-finished attempt. Reusing a shorter list would silently drop
37
+ // chapters; growing would collide. Refuse — never guess.
38
+ throw new Error(`reserve: attempt "${attemptId}" already holds ${existing.length} numbers, requested ${count}`);
39
+ }
40
+ return { state, numbers: [...existing] };
41
+ }
42
+ // Never hand out a number the row already carries. A committed number with no matching
43
+ // reservation is a real state, not a bug: an orphan row written before this field existed, or a
44
+ // chapter whose record a lost update erased while its file survived (#59). Allocating into it would
45
+ // put two `0NN-*.md` in one tree, so allocation SKIPS it and spends the next free number instead.
46
+ const taken = new Set(state.chapters.map((c) => c.number));
47
+ const numbers = [];
48
+ let cursor = state.nextChapterNumber;
49
+ while (numbers.length < count) {
50
+ if (!taken.has(cursor))
51
+ numbers.push(cursor);
52
+ cursor += 1;
53
+ }
54
+ return {
55
+ state: {
56
+ ...state,
57
+ nextChapterNumber: cursor,
58
+ reservations: { ...state.reservations, [attemptId]: numbers },
59
+ },
60
+ numbers,
61
+ };
62
+ }
63
+ /** The same allocation rule for plan documents, on its own counter. */
64
+ export function reservePlan(state, attemptId, count) {
65
+ if (count <= 0 || !Number.isInteger(count)) {
66
+ throw new Error(`reservePlan: count must be a positive integer, got ${count}`);
67
+ }
68
+ const key = `plan:${attemptId}`;
69
+ const existing = state.reservations[key];
70
+ if (existing !== undefined) {
71
+ if (existing.length !== count) {
72
+ throw new Error(`reservePlan: attempt "${key}" already holds ${existing.length} numbers, requested ${count}`);
73
+ }
74
+ return { state, numbers: [...existing] };
75
+ }
76
+ // Same skip rule as reserve(): plan documents are numbered independently but the collision
77
+ // hazard is identical (a lost update rewinding nextPlanNumber re-issues `plan-003` over a twin).
78
+ const taken = new Set(state.chapters.flatMap((c) => c.planDocs ?? [])
79
+ .map((d) => /plan-(\d+)-/.exec(d.path)?.[1]).filter((n) => n !== undefined).map(Number));
80
+ const numbers = [];
81
+ let cursor = state.nextPlanNumber;
82
+ while (numbers.length < count) {
83
+ if (!taken.has(cursor))
84
+ numbers.push(cursor);
85
+ cursor += 1;
86
+ }
87
+ return {
88
+ state: {
89
+ ...state,
90
+ nextPlanNumber: cursor,
91
+ reservations: { ...state.reservations, [key]: numbers },
92
+ },
93
+ numbers,
94
+ };
95
+ }
96
+ /**
97
+ * Append verified chapter records.
98
+ *
99
+ * Identity is `(number, sha256)`, never `number` alone (#59). An identical record is a no-op — that is
100
+ * what makes a retry after a crash safe. A record reusing a number for DIFFERENT bytes is not a retry,
101
+ * it is a collision: one door's reservation was rewound by a lost update and another door has now been
102
+ * handed a number whose file already exists. Measured in the field: chapter 15 was spent twice in
103
+ * `session-aac0254e`, and number-only dedupe silently DROPPED the second record while its 960 KB file
104
+ * stayed on disk — a chapter no TOC cites and no tool can find. Refusing is the only honest option.
105
+ */
106
+ /**
107
+ * The ONE test for "may this chapter be shown as knowledge?" (#60, widened by #255). Both tombstone
108
+ * statuses fail it: a `superseded` husk left by repair, and a `removed` stub whose bytes the operator
109
+ * deleted. Every derived layer — the TOC a child is seeded with, the flat index, the enrichment queue,
110
+ * anything else that reads `state.chapters` — goes through this predicate, because a second hand-rolled
111
+ * comparison is how a widened meaning silently fails to reach one layer (#59; the gate is
112
+ * tests/unit/one-predicate.test.ts). Records written before this field existed are `undefined`, i.e.
113
+ * active: legacy archives are never silently hidden by a new filter.
114
+ */
115
+ export function isActiveChapter(c) {
116
+ return c.status !== 'superseded' && c.status !== 'removed';
117
+ }
118
+ /**
119
+ * The ONE positive arm for the #255 tombstone (chapter-only status value — plan statuses share the
120
+ * word `superseded` but not this one). For auditors that must COUNT removed stubs, not hide them:
121
+ * the knowledge gate above, the count below, one status read each, both here (#59).
122
+ */
123
+ export function isRemovedChapter(c) {
124
+ return c.status === 'removed';
125
+ }
126
+ export function appendChapters(state, records) {
127
+ const byNumber = new Map(state.chapters.map((c) => [c.number, c]));
128
+ const fresh = [];
129
+ for (const r of records) {
130
+ const prior = byNumber.get(r.number);
131
+ if (prior !== undefined) {
132
+ if (prior.sha256 !== r.sha256 || prior.path !== r.path) {
133
+ throw new Error(`appendChapters: chapter number ${r.number} is already committed with different content `
134
+ + `(${prior.path} sha ${prior.sha256.slice(0, 12)}…) — refusing to overwrite or silently drop `
135
+ + `${r.path} (sha ${r.sha256.slice(0, 12)}…). A reused number means a lost registry update (#59).`);
136
+ }
137
+ continue; // identical retry
138
+ }
139
+ fresh.push(r);
140
+ byNumber.set(r.number, r);
141
+ }
142
+ return fresh.length === 0 ? state : { ...state, chapters: [...state.chapters, ...fresh] };
143
+ }
144
+ /**
145
+ * Free an attempt's retry key WITHOUT un-spacing its numbers (#59). Reserve-before-write exists so a
146
+ * crash cannot re-issue a number whose file may already be on disk; that guarantee must not be undone
147
+ * here. What this does is stop a burned key from living forever: a door that reserved and then failed
148
+ * (a read-back that did not verify, a refusal after writing) currently leaves `reservations[k]` behind
149
+ * with no file and no record, which is the state the field showed at numbers 20 and 37.
150
+ *
151
+ * `numbersSpent` stays monotonic — only the attempt key goes.
152
+ */
153
+ export function releaseReservation(state, attemptId) {
154
+ if (state.reservations[attemptId] === undefined)
155
+ return state;
156
+ const { [attemptId]: _gone, ...rest } = state.reservations;
157
+ return { ...state, reservations: rest };
158
+ }
159
+ /**
160
+ * Commit point for a continuation: the child inherits the PARENT's root and
161
+ * gets its direct parent recorded. Set once, never rewritten — re-pointing a
162
+ * linked session would silently re-home every chapter below it. Same link
163
+ * twice is a no-op (retry safety); a DIFFERENT link is corruption or a bug,
164
+ * and it throws.
165
+ */
166
+ export function linkChild(state, parentSessionId, parentRootSession) {
167
+ if (state.parentSession !== null) {
168
+ if (state.parentSession === parentSessionId && state.rootSession === parentRootSession)
169
+ return state;
170
+ throw new Error(`linkChild: session already linked to parent ${state.parentSession} (root ${state.rootSession});`
171
+ + ` refusing rewrite to parent ${parentSessionId} (root ${parentRootSession})`);
172
+ }
173
+ return { ...state, parentSession: parentSessionId, rootSession: parentRootSession };
174
+ }
175
+ /**
176
+ * Walk parent links root-first. Throws on a cycle or a missing parent — both
177
+ * are registry corruption, and an infinite walk is how a small bad write
178
+ * becomes a hung plugin at the worst moment.
179
+ */
180
+ export function ancestorPath(get, sessionId, maxDepth = 1000) {
181
+ const path = [];
182
+ const seen = new Set();
183
+ let cursor = sessionId;
184
+ while (cursor !== undefined) {
185
+ if (seen.has(cursor))
186
+ throw new Error(`ancestorPath: cycle through ${cursor}`);
187
+ if (seen.size >= maxDepth)
188
+ throw new Error(`ancestorPath: exceeded depth ${maxDepth}`);
189
+ seen.add(cursor);
190
+ const state = get(cursor);
191
+ if (state === undefined) {
192
+ if (cursor === sessionId)
193
+ return [cursor]; // unregistered root: itself is the path
194
+ throw new Error(`ancestorPath: missing state for parent ${cursor} (registry corruption)`);
195
+ }
196
+ path.unshift(cursor);
197
+ cursor = state.parentSession ?? undefined;
198
+ }
199
+ return path;
200
+ }
201
+ /**
202
+ * The flat, chronological, numbered index the TOC renders — reading order IS
203
+ * causal order (docs/architecture.md § Ancestry). Ancestor sessions first;
204
+ * within a session, by reserved number. Siblings are excluded by construction:
205
+ * the walk never enters them.
206
+ */
207
+ export function buildIndex(path, get, status) {
208
+ const entries = [];
209
+ for (const sessionId of path) {
210
+ const state = get(sessionId);
211
+ if (state === undefined)
212
+ continue;
213
+ // #255: the flat index shows ONLY knowledge — tombstones (superseded, removed) fail the one
214
+ // predicate. This is what lets `/chapters-remove list` be the same view a child is seeded from.
215
+ for (const chapter of [...state.chapters].filter(isActiveChapter).sort((a, b) => a.number - b.number)) {
216
+ entries.push({
217
+ authorSession: sessionId,
218
+ number: chapter.number,
219
+ path: chapter.path,
220
+ title: chapter.title,
221
+ summary: chapter.summary,
222
+ status: status(chapter),
223
+ });
224
+ }
225
+ }
226
+ return entries;
227
+ }
228
+ /** Append one turn's signature. Idempotent per turn (same seqs → no-op), like appendChapters. */
229
+ export function appendCollection(state, sig) {
230
+ const last = state.collections[state.collections.length - 1];
231
+ if (last !== undefined && last.seqs.length === sig.seqs.length && last.seqs.every((q, i) => q === sig.seqs[i]))
232
+ return state;
233
+ return { ...state, collections: [...state.collections, sig] };
234
+ }
235
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ import { type ChapterRecord } from './archive.ts';
2
+ import { type SessionState } from './registry.ts';
3
+ import type { RegistryStore } from './store.ts';
4
+ import { type RemovedGuard } from './sync.ts';
5
+ export type RemovalRows = readonly (readonly [string, SessionState])[];
6
+ export interface RemovalTarget {
7
+ sessionId: string;
8
+ rootSession: string;
9
+ record: ChapterRecord;
10
+ }
11
+ export type ResolveOutcome = {
12
+ target: RemovalTarget;
13
+ } | {
14
+ refusal: string;
15
+ };
16
+ export interface RemovalFile {
17
+ abs: string;
18
+ /** The stored-path dialect of the registry record (what `sameStoredPath` compares). */
19
+ stored: string;
20
+ bytes: number;
21
+ }
22
+ export interface DanglingRef {
23
+ /** The surviving chapter (or artifact of the search) that still names the removed one. */
24
+ referrer: string;
25
+ /** Which spelling of the removed chapter the referrer cites. */
26
+ needle: string;
27
+ how: 'cited-in-body' | 'cited-in-toc';
28
+ }
29
+ export interface RemovalPlan {
30
+ targets: RemovalTarget[];
31
+ chapterFiles: RemovalFile[];
32
+ /** Orphan blobs + plan documents: each named ONLY by the targets. */
33
+ blobFiles: RemovalFile[];
34
+ dangling: DanglingRef[];
35
+ /** Files the plan expected on disk and did not find — stated, never silent (a stub can outrun a lost tree). */
36
+ missing: string[];
37
+ }
38
+ export interface ApplyPorts {
39
+ store: RegistryStore;
40
+ cwd: string;
41
+ rows: RemovalRows;
42
+ now: () => string;
43
+ /** The resolved store ROOT (the directory holding session trees). Defaults to `<cwd>/.dsh-chapters`. */
44
+ storeDir?: string | undefined;
45
+ /** Linked project: the removal also unlinks the mirror copies, so the sync commit carries the
46
+ * deletion (`publishIntoClone` copies — it can never propagate a deletion by itself). Absent ⇒
47
+ * store-only removal (an unlinked project), which is the normal state, not an error. */
48
+ cloneDir?: string | undefined;
49
+ projectKey?: string | undefined;
50
+ }
51
+ export interface ApplyOutcome {
52
+ journalId: string;
53
+ /** Stored paths actually unlinked (or already absent). */
54
+ removed: string[];
55
+ /** session:number of every record flipped to the removed stub. */
56
+ stubbed: string[];
57
+ problems: string[];
58
+ dangling: DanglingRef[];
59
+ bytes: number;
60
+ }
61
+ export interface Journal {
62
+ journalId: string;
63
+ at: string;
64
+ cwd: string;
65
+ /** 'planned' is written BEFORE the first unlink; 'applied'/'partial' rewrite it after. */
66
+ status: 'planned' | 'applied' | 'partial';
67
+ entries: {
68
+ sessionId: string;
69
+ number: number;
70
+ title: string;
71
+ startSeq: number;
72
+ endSeq: number;
73
+ path: string;
74
+ sha256: string;
75
+ removedFiles: string[];
76
+ appliedAt?: string;
77
+ }[];
78
+ problems?: string[];
79
+ }
80
+ /**
81
+ * Resolve one operator-supplied id to exactly one ACTIVE chapter record. Accepted dialects:
82
+ * registry path (the `list`/journal output), pool path from a search hit
83
+ * (`chapters/<key>/<session>/<file>.md` — matched by its `<session>/<file>` tail),
84
+ * `<sessionId>:<number>`, and a bare TOC display ordinal (needs the caller's session, because the
85
+ * ordinal is a position in the caller's ancestor-path flat index, not a registry number).
86
+ * Zero matches, several matches, or a tombstone each refuse with specifics — removal is destructive
87
+ * and never guesses.
88
+ */
89
+ export declare function resolveRemovalId(raw: string, rows: RemovalRows, cwd: string, callerSessionId?: string): ResolveOutcome;
90
+ /**
91
+ * The exact deletion set + the dangling report, from registry rows and the filesystem — written
92
+ * for the dry-run and consumed unchanged by apply. Reads only; deletes nothing.
93
+ */
94
+ export declare function planRemoval(opts: {
95
+ targets: readonly RemovalTarget[];
96
+ rows: RemovalRows;
97
+ cwd: string;
98
+ }): RemovalPlan;
99
+ /**
100
+ * Every SURVIVING chapter body that still names a removed chapter — by any dialect: the stored path,
101
+ * the filename, or the absolute path a TOC copy would carry. This is the operator's "should the
102
+ * referrer stay?" list, and the seed for missing-access.log.
103
+ */
104
+ export declare function scanDangling(rows: RemovalRows, targets: readonly RemovalTarget[], cwd: string, targetKeys?: Set<string>): DanglingRef[];
105
+ /**
106
+ * Execute a plan. Safety order, pinned by tests: journal (status 'planned') BEFORE the first unlink;
107
+ * unlinks; stub the record through `store.mutate` (never a snapshot put, #59) ONLY for targets whose
108
+ * chapter file is gone; append the dangling report to the missing-access log; rewrite the journal as
109
+ * applied (or partial, naming every problem). Idempotence: a second apply finds the records already
110
+ * tombstoned by the caller's resolve step — resolve refuses tombstones, so there is nothing to redo.
111
+ */
112
+ export declare function applyRemoval(ports: ApplyPorts, plan: RemovalPlan): Promise<ApplyOutcome>;
113
+ export declare const MISSING_ACCESS_FILE: string;
114
+ export declare function appendMissingAccess(storeDir: string, lines: readonly string[], problems?: string[]): void;
115
+ export declare function readMissingAccess(storeDir: string, maxLines: number): {
116
+ lines: string[];
117
+ total: number;
118
+ withheld: number;
119
+ file: string;
120
+ };
121
+ /**
122
+ * The rehydrate guard (#255): every name a `removed` stub owns, in the mirror walk's identity
123
+ * (`<sessionDir>#<file>` for chapters and plan documents; blob basename for content-addressed
124
+ * artifacts). Rehydration that skips these cannot resurrect a decision; the count lands in the
125
+ * sync steps. Keys are session-id-scoped and content-addressed-scoped, so they cannot collide
126
+ * across projects on one machine.
127
+ */
128
+ export declare function removedGuard(rows: Iterable<readonly [string, SessionState]>): RemovedGuard;
129
+ import { type SaveSessionConfig } from './save-core.ts';
130
+ import type { SessionEventLike } from './types.ts';
131
+ export interface RestorePorts {
132
+ store: RegistryStore;
133
+ cwd: string;
134
+ storeDir: string;
135
+ /** The payload-bearing durable-log read (#58's contract) — never a metadata-only record list. */
136
+ readLog(sessionId: string): Promise<SessionEventLike[] | null>;
137
+ config: SaveSessionConfig;
138
+ now: () => string;
139
+ }
140
+ export interface RestoreOutcome {
141
+ journalId: string;
142
+ /** `sid:number → ch.N` per entry whose span re-rendered and committed under a NEW number. */
143
+ restored: string[];
144
+ /** Legitimate no-ops, stated: already superseded, still-active, span already covered. */
145
+ skipped: string[];
146
+ /** Failures with names: no journal, no log, rendered nothing, vanished record. */
147
+ problems: string[];
148
+ }
149
+ /**
150
+ * Reversal is RE-DERIVATION (#255): the journaled span is re-rendered from the session's durable
151
+ * log through the SAME save core every door calls — a repaired chapter is byte-for-byte a normal
152
+ * chapter, and so is a restored one. The number is NEW (a spent number stays spent; identity is
153
+ * `(number, sha256)`); the removed stub flips to `superseded` with `repairs[].class =
154
+ * 'operator-restore'`, which is how an audit tells a restore from a repair. The log is never
155
+ * touched. A span that renders nothing REFUSES (never commit an empty archive); an unreadable log
156
+ * is UNRECOVERABLE and said so. One journal id can span trees: apply wrote one per affected tree,
157
+ * so restore collects entries from every `<tree>/removals/<id>.json`.
158
+ */
159
+ export declare function restoreRemoval(ports: RestorePorts, journalId: string): Promise<RestoreOutcome>;