@sparkelf/dsh-knowledge-base 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/SKILL.md +147 -0
  3. package/cordis.patch.yml +5 -0
  4. package/i18n/zh.json +10 -0
  5. package/lib/client.js +15976 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/index.js +5724 -0
  8. package/lib/types/api.d.ts +574 -0
  9. package/lib/types/client/KnowledgeAudit.d.ts +39 -0
  10. package/lib/types/client/KnowledgeContextMenu.d.ts +96 -0
  11. package/lib/types/client/KnowledgeCreateDialog.d.ts +134 -0
  12. package/lib/types/client/KnowledgeFileTree.d.ts +88 -0
  13. package/lib/types/client/KnowledgeHome.d.ts +79 -0
  14. package/lib/types/client/KnowledgeIndexing.d.ts +60 -0
  15. package/lib/types/client/KnowledgeListing.d.ts +160 -0
  16. package/lib/types/client/KnowledgeLoading.d.ts +41 -0
  17. package/lib/types/client/KnowledgeMenuIcon.d.ts +27 -0
  18. package/lib/types/client/KnowledgePanel.d.ts +58 -0
  19. package/lib/types/client/KnowledgePermissionOverview.d.ts +99 -0
  20. package/lib/types/client/KnowledgePermissions.d.ts +47 -0
  21. package/lib/types/client/KnowledgeReferencePicker.d.ts +65 -0
  22. package/lib/types/client/KnowledgeSearch.d.ts +83 -0
  23. package/lib/types/client/KnowledgeSectionHeader.d.ts +71 -0
  24. package/lib/types/client/KnowledgeSelectionBar.d.ts +45 -0
  25. package/lib/types/client/KnowledgeSettings.d.ts +96 -0
  26. package/lib/types/client/KnowledgeSettingsSection.d.ts +21 -0
  27. package/lib/types/client/KnowledgeShareDialog.d.ts +70 -0
  28. package/lib/types/client/KnowledgeTemplates.d.ts +43 -0
  29. package/lib/types/client/KnowledgeTrash.d.ts +62 -0
  30. package/lib/types/client/MenuGlyphs.d.ts +37 -0
  31. package/lib/types/client/departmentFolders.d.ts +57 -0
  32. package/lib/types/client/deployTarget.d.ts +34 -0
  33. package/lib/types/client/index.d.ts +27 -0
  34. package/lib/types/client/knowledgeMenuCopy.d.ts +23 -0
  35. package/lib/types/client/locales.d.ts +438 -0
  36. package/lib/types/client/openInSidebar.d.ts +62 -0
  37. package/lib/types/client/ownerLabel.d.ts +39 -0
  38. package/lib/types/client/permissionRows.d.ts +94 -0
  39. package/lib/types/client/roleSummary.d.ts +47 -0
  40. package/lib/types/client/saveAudit.d.ts +87 -0
  41. package/lib/types/client/useFavorites.d.ts +49 -0
  42. package/lib/types/client/usePinnedPaths.d.ts +39 -0
  43. package/lib/types/client/useTreeState.d.ts +46 -0
  44. package/lib/types/client/useViewMode.d.ts +39 -0
  45. package/lib/types/deploy.d.ts +209 -0
  46. package/lib/types/extract.d.ts +144 -0
  47. package/lib/types/index.d.ts +846 -0
  48. package/lib/types/permissions.d.ts +237 -0
  49. package/lib/types/protocol.d.ts +229 -0
  50. package/lib/types/remote.d.ts +105 -0
  51. package/lib/types/trash.d.ts +142 -0
  52. package/lib/types/viewMetadata.d.ts +20 -0
  53. package/lib/types/views.d.ts +923 -0
  54. package/package.json +109 -4
  55. package/skills/kb-organize/SKILL.md +365 -0
  56. package/skills/kb-organize/i18n/zh.json +10 -0
  57. package/README.md +0 -3
@@ -0,0 +1,923 @@
1
+ /**
2
+ * Materialize each user's visible knowledge tree as a hardlink view.
3
+ *
4
+ * A grant in the DataOps database is a statement about what a principal MAY read;
5
+ * it is not a filesystem fact. A workspace process that mounts the shared export
6
+ * sees every file in it, so the grant has to be turned into an actual directory
7
+ * tree before it can mean anything: one directory per user, holding hardlinks to
8
+ * exactly the files that user may read. Nothing crosses into a view whose grant
9
+ * was not resolved by DataOps, so this module never decides who may see what --
10
+ * it only asks, and links what the answer allows.
11
+ *
12
+ * Hardlinks rather than copies: the link shares the inode, so a revoke unlinks
13
+ * the view's entry and the source is untouched, and a re-grant costs one link
14
+ * rather than a copy of the file. That is what makes permission changes
15
+ * incremental, and it is why the NFS export has to be one filesystem (a hardlink
16
+ * cannot span mounts).
17
+ *
18
+ * @module @sparkelf/dsh-knowledge-base/views
19
+ */
20
+ /** One path a user may read, as DataOps resolved it. */
21
+ export interface ResolvedPath {
22
+ /** Path relative to the authoritative root. */
23
+ relPath: string;
24
+ }
25
+ /** Which layout the listing uses for this caller. */
26
+ export type ViewMode = 'list' | 'grid' | 'explorer';
27
+ /** What one user's view currently holds. */
28
+ export interface ViewDelta {
29
+ /** Paths linked into the view during this run. */
30
+ linked: string[];
31
+ /** Paths unlinked from the view during this run. */
32
+ unlinked: string[];
33
+ /** Paths left alone because the view already matched. */
34
+ unchanged: number;
35
+ /**
36
+ * Directories removed because a revoke emptied them.
37
+ *
38
+ * Kept apart from `unlinked` because a leftover empty directory is not a stale
39
+ * file: it still discloses a folder name the user has lost access to.
40
+ */
41
+ pruned: string[];
42
+ /**
43
+ * Set when the prune pass was deliberately skipped, with the reason.
44
+ *
45
+ * Reported rather than logged so a reader can see it: "the view is not being
46
+ * cleaned because nothing resolved this round" is a fact about the deployment,
47
+ * and the alternative -- silently keeping stale links -- would be indistinguishable
48
+ * from a view that is correct.
49
+ */
50
+ pruneSkipped?: string;
51
+ }
52
+ export interface ViewOptions {
53
+ /** Directory holding the authoritative files. */
54
+ actualRoot: string;
55
+ /** Directory holding one view per user. */
56
+ viewRoot: string;
57
+ /** Opaque user key; a directory name, so it must not contain a separator. */
58
+ userId: string;
59
+ /**
60
+ * Department code used to select the `department/` subtree, when the caller has one.
61
+ *
62
+ * Layout only: it decides which department bucket is linked, never whether a path
63
+ * may be read. Without it the view simply has no department bucket.
64
+ */
65
+ departmentCode?: string;
66
+ /**
67
+ * The caller's organization path, root first, as DataOps reported it.
68
+ *
69
+ * This is what turns the department bucket into nested folders: the view mirrors
70
+ * `department/<ancestor>/.../<own>/file` so a reader opening 「部门」 walks their
71
+ * own reporting line instead of one flat pile of every department's documents.
72
+ *
73
+ * Absent from an older backend that reports only `departmentCode`. The view then
74
+ * falls back to the single-level layout, which is exactly what it used to be, so
75
+ * an un-upgraded backend degrades to the old shape rather than to an empty bucket.
76
+ */
77
+ departmentPathCodes?: readonly string[];
78
+ /**
79
+ * Paths this caller has removed from their view, in view coordinates.
80
+ *
81
+ * A filter, not a permission: a hidden path keeps its grant, its inode and every
82
+ * other caller's link. It is checked while building the view because a view is
83
+ * hardlinks into one tree, so "remove" cannot mean "unlink" without also undoing
84
+ * a grant on the next sync.
85
+ */
86
+ hidden?: readonly string[];
87
+ }
88
+ /** A resolved set linked into a user's view. */
89
+ export interface ViewSyncOptions extends ViewOptions {
90
+ /**
91
+ * Whether `resolved` is the complete answer for this caller.
92
+ *
93
+ * True when the caller enumerated the whole tree (the routine and full syncs), and
94
+ * false when it asked about specific paths (a write verifying its own result).
95
+ * Only a full scan may apply the prune-refusal rule below: a targeted sync that
96
+ * resolved nothing knows nothing about the rest of the tree, so it must never prune
97
+ * either -- but it also must not be mistaken for evidence that everything failed.
98
+ */
99
+ fullScan?: boolean;
100
+ /**
101
+ * Whether an empty `resolved` may be read as "nothing is readable".
102
+ *
103
+ * Default true: an empty set prunes, which is what a real revoke looks like and what
104
+ * every existing caller means. A caller that enumerated the tree and saw every
105
+ * resolve fail passes false, because an empty answer there is a failure rather than
106
+ * a permission decision, and must not delete the reader's links.
107
+ */
108
+ trustResolvedSet?: boolean;
109
+ }
110
+ /**
111
+ * The three top-level buckets every view exposes, in display order.
112
+ *
113
+ * A curated shared store needs one visible answer to "where does this document
114
+ * live" before it can have a delete policy: what everyone may read, what the
115
+ * caller's department may read, and what only the caller may read are three
116
+ * different audiences, and mixing them puts a department document beside a
117
+ * company-wide one with no way to tell them apart.
118
+ */
119
+ /**
120
+ * File name of a caller's trash record inside their view directory.
121
+ *
122
+ * Named here rather than imported from `trash.ts` so the view builder does not
123
+ * depend on the trash module: the builder only needs to know not to prune it.
124
+ */
125
+ export declare const TRASH_RECORD_NAME = ".kb-trash.json";
126
+ /**
127
+ * Every bookkeeping file this plugin writes into a caller's view directory.
128
+ *
129
+ * The view directory is the directory the reader browses, so a state file left in it
130
+ * is a file the explorer will offer to open, rename or delete. The NAMES live in
131
+ * `viewMetadata.ts` because the client needs them too and cannot import this module
132
+ * (`node:fs/promises` is not available in a browser bundle).
133
+ *
134
+ * The list is cross-checked against the `*PathFor` helpers below at module load: a
135
+ * renamed state file then fails loudly here instead of quietly appearing in every
136
+ * reader's tree, which is how `.kb-favorites.json` reached it once.
137
+ */
138
+ export declare const VIEW_METADATA_NAMES: readonly string[];
139
+ export declare const VIEW_BUCKETS: readonly ["global", "department", "personal"];
140
+ /** One bucket a view can hold. */
141
+ export type ViewBucket = typeof VIEW_BUCKETS[number];
142
+ /** Display name of each bucket, keyed for the client's locale files. */
143
+ export declare const VIEW_BUCKET_LABELS: Readonly<Record<ViewBucket, string>>;
144
+ /**
145
+ * The caller's organization path, cleaned and made safe to use as folder names.
146
+ *
147
+ * Three inputs can produce an empty answer, and all three mean "no hierarchy to
148
+ * render": no path at all (an older backend), an empty path, or a path whose codes
149
+ * are not ordinary path segments. The last one matters because the codes come from
150
+ * another system: a code carrying `/` or `..` would otherwise place a directory
151
+ * outside the view, which is the one thing this module must not allow. Rejecting
152
+ * the whole path rather than the offending segment keeps the layout all-or-nothing,
153
+ * so a caller never sees half a hierarchy that reads as a real one.
154
+ *
155
+ * @param codes - `departmentPathCodes` as DataOps reported it.
156
+ * @returns The usable codes, root first, or an empty array.
157
+ */
158
+ export declare function usableDepartmentPath(codes: readonly string[] | undefined): string[];
159
+ /**
160
+ * Which bucket a resolved path belongs to, and where it lands inside that bucket.
161
+ *
162
+ * The authoritative root carries the same three names as ordinary directories, so
163
+ * ownership is a fact of where the file lives rather than a per-file label that
164
+ * could disagree with it. Legacy flat files (written before the split) answer
165
+ * `global`, which keeps a view populated while a migration is still in progress.
166
+ *
167
+ * `global` owns no second segment, so only its own name is dropped. `department`
168
+ * and `personal` name an OWNER and drop it: a caller opening 「部门」 expects their
169
+ * department's documents inside it, not nested under a second copy of the code.
170
+ *
171
+ * The department bucket's DEPTH depends on the caller, so this function takes the
172
+ * caller's organization path. Everything below the caller's own department keeps
173
+ * its sub-directories, which is what makes the bucket read as a reporting line
174
+ * rather than a flat pile.
175
+ *
176
+ * @param relPath - path relative to the authoritative root.
177
+ * @param departmentPathCodes - the caller's usable organization path, root first.
178
+ * Absent or empty keeps the legacy single-level department layout.
179
+ * @returns The bucket and the path to link inside it, or undefined when the path
180
+ * cannot be placed safely.
181
+ */
182
+ export declare function bucketOf(relPath: string, departmentPathCodes?: readonly string[]): {
183
+ bucket: ViewBucket;
184
+ inner: string;
185
+ } | undefined;
186
+ /**
187
+ * Whether a `department/…` path belongs to the caller's own department.
188
+ *
189
+ * The department segment is compared exactly, so `OPS` does not match `OPS2`. A
190
+ * caller with no department matches nothing: their view gets no department bucket
191
+ * rather than every department a stray grant happens to mention.
192
+ *
193
+ * With an organization path there are two shapes a document of the caller's own
194
+ * department can have, and both are accepted because both exist on a live share:
195
+ *
196
+ * - **Nested**, `department/<ancestor>/.../<own>/<something>`: the whole chain must
197
+ * match in order. Comparing the chain rather than the leaf alone is what keeps a
198
+ * peer out, since a peer that merely shares the root (`A/B` vs `A/C`) would pass a
199
+ * leaf-only test.
200
+ * - **Legacy flat**, `department/<own>/<something>`: written before the hierarchy
201
+ * existed. The caller's own code is at the first position instead of the last, so it
202
+ * is checked separately; refusing it would make every pre-upgrade document vanish.
203
+ *
204
+ * Both shapes need at least one segment BELOW the department, because the department
205
+ * folder itself is a container, not a document.
206
+ *
207
+ * @param relPath - authoritative path, already known to start with `department/`.
208
+ * @param departmentCode - the caller's department code, when DataOps reported one.
209
+ * @param departmentPathCodes - the caller's organization path, root first, when the
210
+ * backend reported one. Falls back to {@link departmentCode} when absent.
211
+ * @returns true when the path sits under the caller's own department.
212
+ */
213
+ export declare function departmentPrefixMatches(relPath: string, departmentCode: string | undefined, departmentPathCodes?: readonly string[]): boolean;
214
+ /**
215
+ * Whether a user key is safe to use as one directory name.
216
+ *
217
+ * The key comes from the identity provider rather than from this plugin, so it is
218
+ * validated before it reaches the filesystem: a value carrying a separator or a
219
+ * parent reference would place the view outside the view root, which is the one
220
+ * thing this module must not allow.
221
+ * @param userId - the candidate key.
222
+ * @returns true when it is a single ordinary path segment.
223
+ */ export declare function isSafeUserId(userId: string): boolean;
224
+ /**
225
+ * The directory one user's view lives in.
226
+ * @param options - the roots and the user key.
227
+ * @returns The absolute view directory.
228
+ */
229
+ /**
230
+ * Whether a path is a mount point.
231
+ *
232
+ * Read from `/proc/self/mountinfo` rather than inferred from `stat`: comparing a
233
+ * directory's device against its parent's reports a bind mount of the SAME
234
+ * filesystem as "not a mount point", and the whole point of this check is to catch
235
+ * the deployment where the share IS the path being retargeted. The file is the
236
+ * kernel's own list, so a path present in it is mounted by definition.
237
+ *
238
+ * Field 5 of each line is the mount point, escaped (`\040` for a space). A path
239
+ * that is not absolute, or a kernel without the file, answers false: the remaining
240
+ * guards in {@link pointMountAtView} still refuse a non-empty directory, so this
241
+ * check failing open cannot on its own authorize a destructive replace.
242
+ * @param path - absolute path to test.
243
+ * @returns true when the kernel lists it as a mount point.
244
+ */
245
+ export declare function isMountPoint(path: string): Promise<boolean>;
246
+ /**
247
+ * Point the browsable mount at one user's view.
248
+ *
249
+ * The view is built beside the authoritative tree, but the page browses
250
+ * `mountPoint`. Without this step every user would browse the same directory and a
251
+ * grant would change nothing they can see: the view would be correct and
252
+ * unreachable at the same time.
253
+ *
254
+ * A symlink is the mechanism because the browsable path is a fixed path the caller
255
+ * configured, while the view is per user.
256
+ *
257
+ * This function REPLACES whatever sits at `mountPoint`, so it refuses two shapes
258
+ * outright rather than removing them:
259
+ *
260
+ * - a **mount point**, because removing it would delete the mounted filesystem's
261
+ * contents on the server. Measured on 2026-10-17: a deployment whose
262
+ * `mountPoint` was the NFS root had `actual/` and every user's `views/` deleted
263
+ * by the old unconditional `rm -rf`, and the share root replaced by a symlink.
264
+ * - a **non-empty real directory**, because its contents are someone else's data
265
+ * and a retarget is not a reason to delete them.
266
+ *
267
+ * What remains is exactly the set of shapes a pointer may legitimately occupy: an
268
+ * absent path (first sync), a symlink (a repeat sync), an empty directory (a
269
+ * leftover), or an already-correct link. A refusal throws rather than returning
270
+ * false, because false means "the view was not ready" while this means "the
271
+ * configuration is wrong" -- two different things for the caller to report.
272
+ * @param options - the roots the paths are resolved against.
273
+ * @param mountPoint - the browsable path to retarget.
274
+ * @returns True when the link now points at the view.
275
+ */
276
+ export declare function pointMountAtView(options: ViewOptions, mountPoint: string): Promise<boolean>;
277
+ export declare function viewPathFor(options: ViewOptions): string;
278
+ /**
279
+ * The view-relative path one authoritative entry is expected to appear at.
280
+ *
281
+ * A write knows the path it just created; this answers "where should the reader see
282
+ * it". Used to VERIFY a refresh rather than trust its status: `syncCallerView` reports
283
+ * success as soon as it has walked the tree, and its per-path permission question
284
+ * fails closed, so a resolve that failed for every path still answers 200 with
285
+ * `resolved: 0`. Comparing the expected view path against the disk is what turns
286
+ * "the sync ran" into "the reader can see it".
287
+ *
288
+ * @param options - the roots and the user key, for the department layout.
289
+ * @param relPath - authoritative path of the entry.
290
+ * @returns The view-relative path, or undefined when the caller's layout excludes it.
291
+ */
292
+ export declare function expectedViewPathFor(options: ViewOptions, relPath: string): string | undefined;
293
+ /**
294
+ * Reject a relative path that would escape the root it is joined to.
295
+ *
296
+ * The resolved paths come from the authorization service, but they name files in
297
+ * a shared export. A `..` segment would let a grant for one path reach another, so
298
+ * it is refused here rather than trusted: the service decides visibility, this
299
+ * module decides that a visible path still lands inside the tree.
300
+ * @param relPath - the candidate path.
301
+ * @returns true when the path is relative and stays under its root.
302
+ */
303
+ export declare function isContainedRelPath(relPath: string): boolean;
304
+ /**
305
+ * Bring one user's view in line with the paths DataOps resolved for them.
306
+ *
307
+ * Only the difference is touched: a path already linked is left alone, a missing
308
+ * one is linked, and one that is no longer resolved is unlinked. A run over an
309
+ * unchanged grant set therefore performs no filesystem writes at all, which is
310
+ * what makes this safe to repeat after every permission change.
311
+ *
312
+ * A link whose source has gone stale is reported as unlinked rather than failing
313
+ * the run, so one deleted file cannot leave a user's whole view half-applied.
314
+ * @param options - the roots and the user key.
315
+ * @param resolved - the paths DataOps says this user may read.
316
+ * @returns What changed, so a caller can report or log the delta.
317
+ */
318
+ export declare function syncView(options: ViewSyncOptions, resolved: readonly ResolvedPath[]): Promise<ViewDelta>;
319
+ /**
320
+ * Remove one user's whole view.
321
+ *
322
+ * Used when a user is removed rather than merely re-scoped: their grants are gone
323
+ * from DataOps, so the view directory has no reason to exist. The authoritative
324
+ * files are untouched, because every entry in the view is a link to them.
325
+ * @param options - the roots and the user key.
326
+ * @returns true when a directory was removed, false when there was none.
327
+ */
328
+ export declare function dropView(options: ViewOptions): Promise<boolean>;
329
+ /**
330
+ * The dotfile holding one user's list/grid preference.
331
+ *
332
+ * Beside `.pinned.json` and for the same reasons: the view directory is already
333
+ * per-user, already on the share, and the choice should follow the reader rather
334
+ * than the browser profile. Written through {@link writeViewMode} and skipped by
335
+ * the sync walk, which is what keeps a preference from being deleted as if it
336
+ * were a stale grant.
337
+ * @param viewDir - the user's view directory.
338
+ * @returns The absolute path of the view-mode file.
339
+ */
340
+ export declare function viewModePathFor(viewDir: string): string;
341
+ /**
342
+ * The dotfile holding this knowledge base's review settings.
343
+ *
344
+ * A third sibling of `.pinned.json` and `.view-mode.json`, for the same reasons: the
345
+ * view directory already exists per user, already lives on the share, and the setting
346
+ * belongs to the knowledge base rather than to one browser profile. It MUST be named in
347
+ * the sync walk's metadata set, or the next sync deletes it as if it were a stale grant.
348
+ * @param viewDir - the user's view directory.
349
+ * @returns The absolute path of the settings file.
350
+ */
351
+ export declare function settingsPathFor(viewDir: string): string;
352
+ /**
353
+ * The append-only audit log.
354
+ *
355
+ * A `.jsonl` file rather than a JSON array: an audit entry is appended with a single
356
+ * `appendFile`, so recording one action never rewrites the history before it. A JSON
357
+ * array would have to be read, extended and rewritten in full, which both costs more and
358
+ * creates a window where a crash truncates the whole trail.
359
+ * @param viewDir - the user's view directory.
360
+ * @returns The absolute path of the audit log.
361
+ */
362
+ export declare function auditLogPathFor(viewDir: string): string;
363
+ /**
364
+ * Where content waits while review is required.
365
+ *
366
+ * Deliberately NOT under a view: the sync walk unlinks every file in a view that the
367
+ * caller's resolved grants do not name, so pending content parked there would be
368
+ * deleted by the next sync, which is the same failure that made writing new documents
369
+ * into a view wrong. It lives in the authoritative root instead, in a directory whose
370
+ * name starts with a dot so the tree listing already hides it.
371
+ * @param actualRoot - the authoritative root.
372
+ * @returns The absolute path of the pending directory.
373
+ */
374
+ export declare function pendingDirFor(actualRoot: string): string;
375
+ /**
376
+ * Build the stable filename one pending write is parked under.
377
+ *
378
+ * The caller's id is part of the name so the trail of who submitted what survives even
379
+ * when the same path is submitted twice by different people; the timestamp keeps the
380
+ * two submissions from overwriting each other.
381
+ * @param userId - the submitting caller.
382
+ * @param relPath - the path the caller wanted to write.
383
+ * @param at - when it was submitted.
384
+ * @returns A filename that is safe as one path segment.
385
+ */
386
+ export declare function pendingFileNameFor(userId: string, relPath: string, at: string): string;
387
+ /**
388
+ * Park one write for review instead of applying it.
389
+ *
390
+ * The payload is a small JSON envelope beside a copy of the bytes, both inside the
391
+ * pending directory. Keeping the content rather than only the intent means an approver
392
+ * can read exactly what was submitted, which is the whole point of reviewing it.
393
+ * @param actualRoot - the authoritative root.
394
+ * @param submission - what was submitted.
395
+ * @returns The directory the submission was parked in.
396
+ */
397
+ export declare function parkPendingWrite(actualRoot: string, submission: {
398
+ userId: string;
399
+ relPath: string;
400
+ content: string;
401
+ at: string;
402
+ }): Promise<string>;
403
+ /** One submission waiting for review. */
404
+ export interface PendingWrite {
405
+ /** The parking name, which is also the handle a reviewer acts on. */
406
+ name: string;
407
+ /** Who submitted it. */
408
+ userId: string;
409
+ /** The path it wants to occupy in the authoritative tree. */
410
+ relPath: string;
411
+ /** When it was submitted. */
412
+ at: string;
413
+ /** Size of the submitted bytes. */
414
+ bytes: number;
415
+ }
416
+ /**
417
+ * Decide whether a value is one parking name and not a path.
418
+ *
419
+ * A reviewer acts on a name that came back from {@link listPendingWrites}, but the
420
+ * request is still browser input. Refusing separators and dot segments here means a
421
+ * crafted name cannot reach outside the pending directory, whatever the caller sends.
422
+ * @param value - the candidate name.
423
+ * @returns Whether it is usable as one path segment.
424
+ */
425
+ export declare function isPendingName(value: unknown): value is string;
426
+ /**
427
+ * Read every submission waiting for review.
428
+ *
429
+ * The envelope is the source of truth rather than the filename, because the name escapes
430
+ * characters out of `relPath` and that escaping is lossy: a reviewer must see the path
431
+ * the submitter actually asked for. A submission whose envelope cannot be read is
432
+ * skipped rather than aborting the queue.
433
+ * @param actualRoot - the authoritative root.
434
+ * @returns The pending submissions, oldest first.
435
+ */
436
+ export declare function listPendingWrites(actualRoot: string): Promise<PendingWrite[]>;
437
+ /**
438
+ * Read one submission's bytes.
439
+ *
440
+ * The name is checked again rather than trusted: this is the function that returns
441
+ * content to be written into the shared tree, so it is the last place a traversal could
442
+ * turn into a write outside the pending directory.
443
+ * @param actualRoot - the authoritative root.
444
+ * @param name - the parking name.
445
+ * @returns The submitted content, or null when it is not there.
446
+ */
447
+ export declare function readPendingContent(actualRoot: string, name: string): Promise<string | null>;
448
+ /**
449
+ * Forget one submission.
450
+ *
451
+ * Used by both the approve and the reject path: approving means the bytes now live in the
452
+ * authoritative tree, and rejecting means nobody wants them. Either way the parked copy
453
+ * must not linger, or the queue would re-offer a decision already made.
454
+ * @param actualRoot - the authoritative root.
455
+ * @param name - the parking name.
456
+ * @returns Whether anything was removed.
457
+ */
458
+ export declare function discardPendingWrite(actualRoot: string, name: string): Promise<boolean>;
459
+ /**
460
+ * Storage choices the operator may change from the panel.
461
+ *
462
+ * A strict subset of the plugin's configuration, and every field here is one the panel
463
+ * can meaningfully write: which shape to run, which transport a remote deployment uses,
464
+ * and where that transport reaches. Everything omitted -- `mountPoint`, `actualRoot`,
465
+ * `viewRoot`, `permitted` -- is left to the deployment because those paths are bound into
466
+ * the container when it is created, so writing them here would only point the plugin at
467
+ * paths nothing serves.
468
+ *
469
+ * There is deliberately no private-key field. `sshKeyRef` names a credential; the secret
470
+ * lives in the credential store, which is the only place a key may live.
471
+ */
472
+ export interface KnowledgeStorageSettings {
473
+ /** Which storage shape to run, when the operator has chosen one. */
474
+ storageMode?: 'local' | 'remote';
475
+ /** Transport a remote deployment reaches its share by. */
476
+ storageProtocol?: 'nfs' | 'sshfs';
477
+ /** NFS export host. */
478
+ nfsServer?: string;
479
+ /** SSHFS: remote host. */
480
+ sshHost?: string;
481
+ /** SSHFS: login user. */
482
+ sshUser?: string;
483
+ /** SSHFS: SSH daemon port. */
484
+ sshPort?: number;
485
+ /** SSHFS: absolute path of the shared directory on the remote host. */
486
+ sshRemotePath?: string;
487
+ /** SSHFS: extra `-o` options. */
488
+ sshMountOptions?: string;
489
+ /** SSHFS: credential reference holding the private key -- a name, never the key. */
490
+ sshKeyRef?: string;
491
+ }
492
+ /** Whether a write must be reviewed before it counts as published. */
493
+ export interface KnowledgeSettings extends KnowledgeStorageSettings {
494
+ /**
495
+ * When true, content a caller adds is recorded as pending instead of taking effect.
496
+ *
497
+ * Defaults to false: the product ships with review OFF and an audit trail only, which
498
+ * is the behaviour the operator asked for. Turning it on is a deliberate act.
499
+ */
500
+ reviewRequired: boolean;
501
+ /**
502
+ * MinerU's full base address, or `''` to derive it from the port.
503
+ *
504
+ * The port alone cannot express anything but this machine: a service reached through
505
+ * a tunnel, a gateway or another host has no port to put here. An empty value means
506
+ * "derive it", which keeps every existing deployment working without a migration.
507
+ */
508
+ mineruBaseUrl: string;
509
+ }
510
+ /** The settings a caller who has never opened the page gets. */
511
+ export declare const DEFAULT_KNOWLEDGE_SETTINGS: KnowledgeSettings;
512
+ /**
513
+ * Normalise a MinerU base address.
514
+ *
515
+ * Only an absolute http(s) address is storable. A bare host, a `file:` URL or a typo
516
+ * would be handed to a parser that can only fail on it, and the failure would surface
517
+ * as "extraction broke" rather than "this setting is wrong", so the value is checked
518
+ * where it is written instead.
519
+ * @param raw - the candidate value.
520
+ * @returns The trimmed address without a trailing slash, or `''`.
521
+ */
522
+ export declare function normalizeMineruBaseUrl(raw: unknown): string;
523
+ /**
524
+ * Copy the storage fields out of a parsed settings object.
525
+ *
526
+ * Values are validated here rather than at the render site so a hand-edited or foreign file
527
+ * cannot put an unknown mode into the effective configuration: an unrecognized value is
528
+ * dropped and the deployment default stands.
529
+ * @param raw - the parsed settings object.
530
+ * @returns The recognized storage fields, omitting anything invalid.
531
+ */
532
+ export declare function storageSettingsOf(raw: Record<string, unknown>): KnowledgeStorageSettings;
533
+ /**
534
+ * Read this knowledge base's settings.
535
+ *
536
+ * Anything unrecognized answers the defaults rather than an error: a corrupt settings
537
+ * file should cost the reader their preferences, not the page. `reviewRequired` is
538
+ * coerced to a real boolean so a hand-edited `"false"` string cannot switch review on.
539
+ * @param viewDir - the user's view directory.
540
+ * @returns The stored settings, or the defaults.
541
+ */
542
+ export declare function readSettings(viewDir: string): Promise<KnowledgeSettings>;
543
+ /**
544
+ * Replace this knowledge base's settings.
545
+ *
546
+ * Atomic by rename for the same reason the pins and the view mode are: a reader must
547
+ * never observe a half-written settings file. The view directory is created because a
548
+ * setting can be recorded before the first sync has built one.
549
+ * @param viewDir - the user's view directory, created if absent.
550
+ * @param settings - the settings to store.
551
+ * @returns The settings actually written.
552
+ */
553
+ export declare function writeSettings(viewDir: string, settings: KnowledgeSettings): Promise<KnowledgeSettings>;
554
+ /**
555
+ * The deployment-level settings file, addressed from the mounted knowledge tree.
556
+ *
557
+ * A separate accessor from {@link settingsPathFor} because the two files answer different
558
+ * questions and are read at different moments. The per-user file is only reachable once a
559
+ * caller is known, while this one must be readable on the very first `config` call -- before
560
+ * any identity has been resolved -- because the storage shape is what tells the page whether
561
+ * a caller is needed at all. `mountPoint` is the one path the panel knows without asking
562
+ * anyone, so the file hangs off it.
563
+ *
564
+ * Named `.kb-settings.json` so it lands in the same metadata set the sync walk already
565
+ * skips; a sync that deleted it would silently revert the operator's choice.
566
+ * @param mountPoint - the mounted knowledge tree.
567
+ * @returns The absolute path of the settings file.
568
+ */
569
+ export declare function storageSettingsPathFor(mountPoint: string): string;
570
+ /**
571
+ * Read the stored storage settings, tolerating every ordinary absence.
572
+ *
573
+ * A missing mount point, a missing file, and a file written by an older build are all the
574
+ * same answer here: nothing has been chosen yet, so the deployment defaults stand. Only a
575
+ * value that IS present and unrecognized is dropped, and it is dropped on its own rather
576
+ * than invalidating its neighbours.
577
+ * @param mountPoint - the mounted knowledge tree.
578
+ * @returns The stored storage fields, empty when nothing usable is on disk.
579
+ */
580
+ export declare function readStorageSettings(mountPoint: string): Promise<KnowledgeStorageSettings>;
581
+ /**
582
+ * Merge a patch into the stored storage settings and write the file atomically.
583
+ *
584
+ * A merge rather than a replace: the panel sends only the fields the operator touched, and a
585
+ * save that blanked the others would make editing one field erase the rest. Written by
586
+ * `rename` so a reader never sees a half-written file, for the same reason the per-user
587
+ * settings are.
588
+ * @param mountPoint - the mounted knowledge tree, created if absent.
589
+ * @param patch - the fields to change.
590
+ * @returns The settings now on disk.
591
+ */
592
+ export declare function writeStorageSettings(mountPoint: string, patch: KnowledgeStorageSettings): Promise<KnowledgeStorageSettings>;
593
+ /**
594
+ * Read one user's list/grid preference.
595
+ *
596
+ * Anything unrecognized answers `list`, which is the shape the page was built
597
+ * around: a corrupt or hand-edited preference should cost the reader a layout
598
+ * choice, not the page. A missing file is that same ordinary first read.
599
+ * @param viewDir - the user's view directory.
600
+ * @returns The stored mode.
601
+ */
602
+ export declare function readViewMode(viewDir: string): Promise<ViewMode>;
603
+ /**
604
+ * Replace one user's list/grid preference.
605
+ *
606
+ * Atomic by rename for the same reason the pins are: a reader must never observe
607
+ * a half-written file. The view directory is created because the preference can
608
+ * be recorded before the first sync has built one.
609
+ * @param viewDir - the user's view directory, created if absent.
610
+ * @param mode - the mode to store.
611
+ * @returns The mode actually written.
612
+ */
613
+ export declare function writeViewMode(viewDir: string, mode: ViewMode): Promise<ViewMode>;
614
+ /**
615
+ * The dotfile holding one user's expanded folders in the explorer.
616
+ *
617
+ * A sibling of `.pinned.json` and `.view-mode.json`, for the same reasons: the view
618
+ * directory is already per-user and already on the share, so "the same person sees
619
+ * the same tree" needs no new storage of its own. It MUST be named in the sync
620
+ * walk's metadata set, or the next sync deletes it as if it were a stale grant --
621
+ * which reads as a preference that will not stick rather than as a bug in that walk.
622
+ * @param viewDir - the user's view directory.
623
+ * @returns The absolute path of the tree-state file.
624
+ */
625
+ export declare function treeStatePathFor(viewDir: string): string;
626
+ /**
627
+ * Read one user's expanded folders.
628
+ *
629
+ * Every failure answers an empty list, which is also the correct default: the tree
630
+ * starts fully collapsed, so a missing file, an unreadable file, a truncated file,
631
+ * and a file holding something other than an array all resolve to the state a
632
+ * first visit has. Never throws -- the tree must render even when this file is
633
+ * missing or corrupt, and a preference is never worth failing a page for.
634
+ * @param viewDir - the user's view directory.
635
+ * @returns The stored expanded paths, deduplicated, junk entries dropped.
636
+ */
637
+ export declare function readTreeState(viewDir: string): Promise<string[]>;
638
+ /**
639
+ * Replace one user's expanded folders.
640
+ *
641
+ * Atomic by rename for the same reason the pins and the view mode are: a reader
642
+ * must never observe a half-written file, and the file lives on a share where a
643
+ * crash mid-write would otherwise leave a truncated document that reads as "all
644
+ * collapsed". The view directory is created because the preference can be
645
+ * recorded before the first sync has built one.
646
+ *
647
+ * Junk is dropped rather than refused: unlike a pin, which names a document the
648
+ * reader explicitly asked to keep, this is a volume knob whose worst failure mode
649
+ * is a folder that comes back closed. Answering 400 here would make a broken
650
+ * client's save look like a page error for no reader-visible gain.
651
+ * @param viewDir - the user's view directory, created if absent.
652
+ * @param expanded - the absolute paths to store.
653
+ * @returns The paths actually written.
654
+ */
655
+ export declare function writeTreeState(viewDir: string, expanded: readonly string[]): Promise<string[]>;
656
+ /**
657
+ * The dotfile holding one user's favourite paths.
658
+ *
659
+ * A fourth sibling of `.pinned.json`, `.view-mode.json` and `.kb-tree-state.json`,
660
+ * and for the same reason: a favourite is a view preference about the reader, not a
661
+ * property of the document, so the caller's own view directory is already the right
662
+ * owner and no backend table is involved. It MUST stay listed in the sync walk's
663
+ * metadata set or the next sync unlinks it, which shows up as a star that will not
664
+ * stick rather than as a bug in that walk.
665
+ *
666
+ * Kept separate from `pins` on purpose. A pin promotes a path into the rail's band
667
+ * and is addressed by the reader looking for a shortcut; a favourite only marks the
668
+ * row where it already sits, and the two are expected to differ -- pinning a folder
669
+ * you work in every day while still starring the report you are drafting.
670
+ * @param viewDir - the user's view directory.
671
+ * @returns The absolute path of the favourites file.
672
+ */
673
+ export declare function favoritesPathFor(viewDir: string): string;
674
+ /**
675
+ * Read one user's favourites.
676
+ *
677
+ * A missing or unparsable file is the empty list, exactly as {@link readPins}
678
+ * reports it: no favourites is the ordinary first state, and a corrupt preference
679
+ * should cost the reader their stars rather than the page.
680
+ * @param viewDir - the user's view directory.
681
+ * @returns The favourited paths, in the order they were written.
682
+ */
683
+ export declare function readFavorites(viewDir: string): Promise<string[]>;
684
+ /**
685
+ * Replace one user's favourites.
686
+ *
687
+ * Normalised and written by atomic rename, like every other preference file here:
688
+ * a reader must never observe a half-written list, and the temporary is a sibling
689
+ * because a rename cannot cross a filesystem.
690
+ * @param viewDir - the user's view directory, created if absent.
691
+ * @param relPaths - the paths to store.
692
+ * @returns The paths actually written, after normalisation.
693
+ */
694
+ export declare function writeFavorites(viewDir: string, relPaths: readonly string[]): Promise<string[]>;
695
+ /**
696
+ * Move or rename one entry inside the authoritative tree.
697
+ *
698
+ * `rename(2)` rather than a copy followed by an unlink, and that choice is the
699
+ * whole point of this function: the authoritative tree is an NFS export, so a
700
+ * copy-and-delete would read and rewrite the entire file across the network for
701
+ * what the kernel can do as a single atomic directory operation. It also keeps
702
+ * the inode, which matters because every user's view holds HARDLINKS to these
703
+ * files: a copy would give the moved file a new inode and leave every hardlink in
704
+ * every view pointing at the old one. Callers must resynchronize the affected
705
+ * views afterwards ({@link syncView} relinks the new path and drops the old), and
706
+ * that is not optional -- without it the reader sees a dead entry.
707
+ *
708
+ * Both paths are required to be contained relative paths; the caller is
709
+ * responsible for having asked the authority whether the caller may write them.
710
+ * The destination's parent is created when it does not exist, and a destination
711
+ * that already holds an entry is refused rather than overwritten, because a move
712
+ * that silently replaces a document is data loss and no prompt can undo it.
713
+ * @param options - the roots; only `actualRoot` is used.
714
+ * @param fromRelPath - the entry's current path, relative to the root.
715
+ * @param toRelPath - the entry's new path, relative to the root.
716
+ * @returns The moved entry's new relative path.
717
+ */
718
+ export declare function moveEntry(options: ViewOptions, fromRelPath: string, toRelPath: string): Promise<{
719
+ from: string;
720
+ to: string;
721
+ }>;
722
+ /**
723
+ * Copy one entry, with its subtree, to a new path inside the authoritative tree.
724
+ *
725
+ * Three rules that the caller cannot waive:
726
+ *
727
+ * - **No overwrite.** An existing destination is refused by name rather than replaced
728
+ * or auto-renamed: a silent overwrite destroys something the reader did not name, and
729
+ * an auto-rename produces a file under a name they never typed while the surface says
730
+ * the copy succeeded.
731
+ * - **Symlinks are copied, not followed.** See {@link planCopy}.
732
+ * - **All-or-nothing.** Everything is resolved and size-checked first; if the write
733
+ * phase still fails, the paths written so far are removed, so the tree never keeps a
734
+ * half-copied directory.
735
+ *
736
+ * `copyFile` + `mkdir` rather than a recursive helper: the plan already knows every
737
+ * path, and doing them one at a time is what makes rollback a list of paths to unlink
738
+ * instead of a second tree walk over an unknown half-written state.
739
+ * @param options - the caller's view options (only `actualRoot` is used).
740
+ * @param fromRelPath - the source, relative to the authoritative root.
741
+ * @param toRelPath - the destination, relative to the authoritative root.
742
+ * @returns The source and destination paths, on success.
743
+ */
744
+ export declare function copyEntry(options: ViewOptions, fromRelPath: string, toRelPath: string): Promise<{
745
+ from: string;
746
+ to: string;
747
+ entries: number;
748
+ bytes: number;
749
+ }>;
750
+ /**
751
+ * The dotfile holding one user's pins.
752
+ *
753
+ * A dotfile inside the caller's own view directory, rather than a backend table:
754
+ * the view directory is already per-user, already on the shared export, and
755
+ * already rebuilt in place by {@link syncView}. Pins are therefore durable across
756
+ * a restart and need no new authority to store. The name is dotted to keep it out
757
+ * of the way of a reader scanning the tree, and the walk skips this exact path so
758
+ * a sync cannot delete it.
759
+ * @param viewDir - the user's view directory.
760
+ * @returns The absolute path of the pin file.
761
+ */
762
+ export declare function pinsPathFor(viewDir: string): string;
763
+ /**
764
+ * Read one user's pins.
765
+ *
766
+ * A missing file is the empty list rather than an error: every user has no pins
767
+ * until they make one, so that is the ordinary first read. A file that exists but
768
+ * cannot be parsed is also reported empty, because a corrupt preference should
769
+ * cost the reader their pins rather than the whole page.
770
+ * @param viewDir - the user's view directory.
771
+ * @returns The pinned paths, in the order they were written.
772
+ */
773
+ export declare function readPins(viewDir: string): Promise<string[]>;
774
+ /**
775
+ * Replace one user's pins.
776
+ *
777
+ * The list is normalized before it is written: every entry must be a contained
778
+ * relative path, duplicates collapse, and the order is the caller's. Writing is
779
+ * atomic by rename, so a reader never observes a half-written file, and the
780
+ * temporary name is a sibling because a rename cannot cross a filesystem.
781
+ * @param viewDir - the user's view directory, created if absent.
782
+ * @param relPaths - the paths to store, already filtered for readability.
783
+ * @returns The paths actually written, after normalization.
784
+ */
785
+ export declare function writePins(viewDir: string, relPaths: readonly string[]): Promise<string[]>;
786
+ /** One recorded change to the knowledge base. */
787
+ export interface KnowledgeAuditEntry {
788
+ /** ISO timestamp of the action. */
789
+ at: string;
790
+ /** Who performed it: the DataOps user id the Host resolved. */
791
+ actor: string;
792
+ /**
793
+ * That user's display name at the time of the action, when the Host resolved one.
794
+ *
795
+ * Kept beside the id rather than replacing it: the id is the identity an audit is
796
+ * checked against, while this is only what the trail is read as. Optional because
797
+ * logs written before it existed carry no label, and a reader must show such an
798
+ * entry by its id rather than drop it.
799
+ */
800
+ actorLabel?: string;
801
+ /** What they did. */
802
+ /**
803
+ * What they did.
804
+ *
805
+ * `edit` is a save of an existing document's bytes, kept distinct from `create`
806
+ * because the two answer different questions in a review: "who first put this
807
+ * here" and "who changed it last". A save through the host's own file editor
808
+ * reports here through a side channel (see the `audit.appended` route), which is
809
+ * what keeps the host's write path untouched.
810
+ */
811
+ action: 'create' | 'upload' | 'move' | 'approve' | 'reject' | 'edit';
812
+ /** The path the action concerned, relative to the knowledge root. */
813
+ relPath: string;
814
+ /** For a move, the destination path. */
815
+ toRelPath?: string;
816
+ /**
817
+ * What became of it.
818
+ *
819
+ * `pending` means it was parked for review, `applied` means it took effect,
820
+ * `rejected` means a reviewer refused it. The three are kept distinct because
821
+ * "nothing happened yet" and "somebody said no" are different facts, and an audit
822
+ * trail that collapsed them would misreport why a document is missing.
823
+ */
824
+ outcome: 'applied' | 'pending' | 'rejected';
825
+ }
826
+ /**
827
+ * Append one audit entry.
828
+ *
829
+ * Deliberately append-only: no update and no delete path exists in this module, because
830
+ * an audit trail a caller can rewrite is not an audit trail. The file is opened with
831
+ * `'a'`, so the write is a single append at the end and can never overwrite an earlier
832
+ * entry, even with two writers racing.
833
+ *
834
+ * Failure to record is NOT allowed to fail the action that was recorded. The caller has
835
+ * already made its change, so throwing here would report a failure that did not happen;
836
+ * the caller is expected to treat this as best-effort and the reason is logged by the
837
+ * host. That trade is deliberate: an audit gap is bad, but a reported-but-not-applied
838
+ * write is worse.
839
+ * @param viewDir - the user's view directory, created if absent.
840
+ * @param entry - the entry to record.
841
+ */
842
+ export declare function appendAuditEntry(viewDir: string, entry: KnowledgeAuditEntry): Promise<void>;
843
+ /**
844
+ * Read the newest audit entries, newest first.
845
+ *
846
+ * The whole file is read because it is a per-user, append-only log that grows by one
847
+ * short line per write; there is no rotation yet and pretending otherwise would add a
848
+ * seek that the format does not support. Unparseable lines are skipped rather than
849
+ * thrown: a half-written last line from a crash must not make the whole trail unreadable.
850
+ * @param viewDir - the user's view directory.
851
+ * @param limit - maximum entries to return, newest first.
852
+ * @returns The entries, newest first.
853
+ */
854
+ export declare function readAuditEntries(viewDir: string, limit?: number): Promise<KnowledgeAuditEntry[]>;
855
+ /** One stored template, as the index records it. */
856
+ export interface TemplateRecord {
857
+ /** Derived from the stored document's name. */
858
+ id: string;
859
+ title: string;
860
+ description: string;
861
+ category: string;
862
+ tags: string[];
863
+ /** The document's stored file name, including its extension. */
864
+ fileName: string;
865
+ /** Last-modified instant, ISO-8601. */
866
+ updatedAt: string;
867
+ }
868
+ /**
869
+ * The template directory's absolute path.
870
+ * @param actualRoot - the authoritative tree's root.
871
+ * @returns The absolute directory.
872
+ */
873
+ export declare function templateDirFor(actualRoot: string): string;
874
+ /**
875
+ * Read the template index.
876
+ *
877
+ * Every failure answers an empty library, which is also the correct first-run state:
878
+ * a missing directory, a truncated index, and an index holding the wrong shape all
879
+ * resolve to "no templates yet". Never throws -- the library must render.
880
+ * @param actualRoot - the authoritative tree's root.
881
+ * @returns The template records, newest first.
882
+ */
883
+ export declare function readTemplates(actualRoot: string): Promise<TemplateRecord[]>;
884
+ /**
885
+ * Store one template: its document and its metadata.
886
+ *
887
+ * Replacing is keyed on the FILE NAME, not on a separate id, because the file name is
888
+ * the identity. A save that changes the name therefore writes a new document and
889
+ * removes the old one, which is what "rename this template" means; a save that keeps
890
+ * the name overwrites in place.
891
+ * @param actualRoot - the authoritative tree's root.
892
+ * @param input - the document text and metadata.
893
+ * @returns The stored record.
894
+ */
895
+ export declare function saveTemplate(actualRoot: string, input: {
896
+ title: string;
897
+ description: string;
898
+ category: string;
899
+ tags: readonly string[];
900
+ fileName: string;
901
+ content?: string;
902
+ /** The template being edited, so its previous document is removed on a rename. */
903
+ id?: string;
904
+ }): Promise<TemplateRecord>;
905
+ /**
906
+ * Remove one template: its document and its record.
907
+ *
908
+ * Both halves go together, because a record without a document is a card that cannot
909
+ * be used and a document without a record is an invisible file. A missing document is
910
+ * not an error -- the record is what says the template exists, and removing it is the
911
+ * operation asked for.
912
+ * @param actualRoot - the authoritative tree's root.
913
+ * @param id - the template to remove.
914
+ * @returns Whether a template was removed.
915
+ */
916
+ export declare function removeTemplate(actualRoot: string, id: string): Promise<boolean>;
917
+ /**
918
+ * Stat one stored template's document, so the card can report its real size.
919
+ * @param actualRoot - the authoritative tree's root.
920
+ * @param fileName - the record's stored name.
921
+ * @returns Byte size, or 0 when the document is gone.
922
+ */
923
+ export declare function templateBytes(actualRoot: string, fileName: string): Promise<number>;