@jwilleke/ngdpbase-types 5.39.0 → 5.41.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 (99) hide show
  1. package/lib/packages/ngdpbase-types/index.d.ts +1 -1
  2. package/lib/src/context/ApiContext.d.ts +3 -2
  3. package/lib/src/context/ApiContext.js +4 -2
  4. package/lib/src/ice/fileHandling.d.ts +6 -1
  5. package/lib/src/ice/givenName.d.ts +19 -0
  6. package/lib/src/ice/runExport.d.ts +41 -0
  7. package/lib/src/ice/runImport.d.ts +37 -8
  8. package/lib/src/ice/shims/csv.d.ts +24 -0
  9. package/lib/src/ice/shims/export.d.ts +23 -0
  10. package/lib/src/ice/shims/index.d.ts +3 -1
  11. package/lib/src/ice/shims/svg.d.ts +27 -0
  12. package/lib/src/ice/shims/takeout.d.ts +83 -0
  13. package/lib/src/ice/types.d.ts +301 -5
  14. package/lib/src/ice/vet.d.ts +19 -0
  15. package/lib/src/managers/AttachmentManager.d.ts +100 -36
  16. package/lib/src/managers/AuthManager.d.ts +152 -12
  17. package/lib/src/managers/AuthManager.js +3830 -0
  18. package/lib/src/managers/BackgroundJobManager.d.ts +25 -0
  19. package/lib/src/managers/BackupManager.d.ts +24 -0
  20. package/lib/src/managers/BackupManager.js +48 -1
  21. package/lib/src/managers/ExportManager.d.ts +16 -0
  22. package/lib/src/managers/ExportManager.js +25 -0
  23. package/lib/src/managers/FileManager.d.ts +182 -0
  24. package/lib/src/managers/ImportManager.d.ts +279 -16
  25. package/lib/src/managers/MediaManager.d.ts +2 -0
  26. package/lib/src/managers/PageManager.d.ts +53 -0
  27. package/lib/src/managers/SearchManager.d.ts +4 -0
  28. package/lib/src/managers/SharedFolderManager.d.ts +320 -0
  29. package/lib/src/managers/ValidationManager.d.ts +5 -2
  30. package/lib/src/middleware/csrf.js +99 -0
  31. package/lib/src/providers/AgentTokenAuthProvider.js +46 -0
  32. package/lib/src/providers/AuthentikBearerAuthProvider.js +176 -0
  33. package/lib/src/providers/BaseCredentialsProvider.js +21 -0
  34. package/lib/src/providers/BaseMediaProvider.d.ts +4 -0
  35. package/lib/src/providers/BasePageProvider.d.ts +8 -0
  36. package/lib/src/providers/CloudflareAccessAuthProvider.js +160 -0
  37. package/lib/src/providers/FileCredentialsProvider.js +188 -0
  38. package/lib/src/providers/FileSystemMediaProvider.d.ts +6 -0
  39. package/lib/src/providers/MagicLinkAuthProvider.js +333 -0
  40. package/lib/src/providers/OidcAuthProvider.js +350 -0
  41. package/lib/src/providers/OidcBearerAuthProvider.js +27 -0
  42. package/lib/src/providers/PasskeyAuthProvider.js +147 -0
  43. package/lib/src/providers/PasswordAuthProvider.js +44 -0
  44. package/lib/src/security/PolicyDecisionPoint.d.ts +3 -2
  45. package/lib/src/security/pageActions.js +43 -0
  46. package/lib/src/security/permissions.generated.d.ts +7 -3
  47. package/lib/src/types/File.d.ts +90 -0
  48. package/lib/src/types/Provider.d.ts +15 -0
  49. package/lib/src/types/express.d.ts +7 -1
  50. package/lib/src/types/guards.js +366 -0
  51. package/lib/src/types/linkedom.d.ts +2 -0
  52. package/lib/src/utils/atomicWrite.js +129 -0
  53. package/lib/src/utils/auditEventNames.generated.d.ts +12 -0
  54. package/lib/src/utils/auditEventNames.generated.js +12 -0
  55. package/lib/src/utils/auditEvents.d.ts +15 -0
  56. package/lib/src/utils/auditEvents.js +17 -1
  57. package/lib/src/utils/bip39English.js +2 -0
  58. package/lib/src/utils/browserDescription.js +28 -0
  59. package/lib/src/utils/delegation.js +79 -0
  60. package/lib/src/utils/deriveUsername.js +38 -0
  61. package/lib/src/utils/emailForLog.js +28 -0
  62. package/lib/src/utils/fileMigrationPlan.d.ts +81 -0
  63. package/lib/src/utils/instanceEnvBackup.d.ts +6 -0
  64. package/lib/src/utils/instanceEnvBackup.js +13 -0
  65. package/lib/src/utils/ipLocation.js +106 -0
  66. package/lib/src/utils/magicLinkDeviceState.js +97 -0
  67. package/lib/src/utils/pageUrl.d.ts +1 -0
  68. package/lib/src/utils/passwordHash.js +150 -0
  69. package/lib/src/utils/permissionCatalog.js +102 -0
  70. package/lib/src/utils/privateStoreAccess.d.ts +25 -0
  71. package/lib/src/utils/privateStoreAccess.js +22 -1
  72. package/lib/src/utils/privateStoreCatalogs.js +57 -0
  73. package/lib/src/utils/privateStoreCrypto.js +266 -0
  74. package/lib/src/utils/privateStoreDoor.d.ts +10 -0
  75. package/lib/src/utils/privateStoreDoor.js +298 -0
  76. package/lib/src/utils/privateStoreImport.d.ts +0 -10
  77. package/lib/src/utils/privateStoreMeta.js +123 -0
  78. package/lib/src/utils/privateStorePath.d.ts +37 -1
  79. package/lib/src/utils/privateStorePath.js +60 -3
  80. package/lib/src/utils/privateStoreTakeout.js +149 -0
  81. package/lib/src/utils/privateStoreUnlock.d.ts +3 -0
  82. package/lib/src/utils/privateStoreUnlock.js +499 -0
  83. package/lib/src/utils/recoveryWordsFile.d.ts +54 -0
  84. package/lib/src/utils/safeRedirect.js +55 -0
  85. package/lib/src/utils/sessionAccount.js +55 -0
  86. package/lib/src/utils/sessionEnd.d.ts +1 -0
  87. package/lib/src/utils/sessionEnd.js +42 -0
  88. package/lib/src/utils/sessionGeneration.js +29 -0
  89. package/lib/src/utils/sessionIdle.d.ts +4 -0
  90. package/lib/src/utils/sessionIdle.js +72 -0
  91. package/lib/src/utils/sessionLimits.js +72 -0
  92. package/lib/src/utils/sessionSecurity.js +122 -0
  93. package/lib/src/utils/shareLockbox.js +101 -0
  94. package/lib/src/utils/sharedFolderAccess.d.ts +57 -0
  95. package/lib/src/utils/sharedFolderAccess.js +80 -0
  96. package/lib/src/utils/userCreateError.js +82 -0
  97. package/lib/src/utils/username.d.ts +2 -0
  98. package/lib/src/utils/username.js +105 -0
  99. package/package.json +2 -1
@@ -32,7 +32,8 @@ import type { WikiEngine } from '../types/WikiEngine.js';
32
32
  import { IContentConverter } from '../converters/IContentConverter.js';
33
33
  import { type ZipReadLimits } from '../utils/zipArchive.js';
34
34
  import { type JobContext } from '../context/JobContext.js';
35
- import type { IceLedger, IceOriginalChoices, IceReceiver, IceRunReport, IceRunRequest, IceShim, RecentImport, RegisteredShim } from '../ice/types.js';
35
+ import { type IceNameForm } from '../ice/givenName.js';
36
+ import type { IceExportFormat, IceExportReport, IceExportRequest, IceExportSource, IceLedger, IceOriginalChoices, IceReceiver, IceRunReport, IceRunRequest, IceShim, IceTarget, IceUpload, IceVetResult, RecentImport, RegisteredShim } from '../ice/types.js';
36
37
  /**
37
38
  * Options for import operations
38
39
  */
@@ -217,6 +218,31 @@ export declare class ImportUploadRefused extends Error {
217
218
  readonly reason: string;
218
219
  constructor(file: string, reason: string);
219
220
  }
221
+ /**
222
+ * An export that is not started (#2120): the thing is not there, the user may
223
+ * not export it, or the format is not one offered for it. A route answers it
224
+ * as "not found", so it never tells the user which.
225
+ */
226
+ export declare class IceExportRefused extends Error {
227
+ constructor(message?: string);
228
+ }
229
+ /**
230
+ * An export not started because too much is already being made (#2120): the
231
+ * user's own queued exports are at `max-queued-per-user`, or every held run is
232
+ * still being made. A route answers it as 429; nothing was read or queued.
233
+ */
234
+ export declare class IceExportBusy extends Error {
235
+ constructor(message: string);
236
+ }
237
+ /**
238
+ * An export refused only because the user's sign-in is not fresh enough
239
+ * (#2120): they hold `permission` on the thing, and step-up asks for it. A
240
+ * route answers it with the step-up prompt, not "not found".
241
+ */
242
+ export declare class IceExportNeedsFreshSignIn extends Error {
243
+ readonly permission: string;
244
+ constructor(permission: string);
245
+ }
220
246
  /** One page's outcome in a takeout import (#1472). */
221
247
  export type TakeoutPageOutcome = {
222
248
  title: string;
@@ -269,13 +295,13 @@ export interface UsableReceiver {
269
295
  label?: string;
270
296
  /** What the screen offers; the kept types (`types`, #2061) are the upload door's business, not the screen's. */
271
297
  originals: Required<Omit<IceOriginalChoices, 'types'>>;
298
+ /** What a name the user gives becomes (#2130), when the receiver takes one: the screen offers the field only then. */
299
+ names?: IceNameForm;
300
+ /** Where within it this user may import (#2118), for a receiver with places: the screen offers a choice of them. */
301
+ targets?: IceTarget[];
272
302
  }
273
- /**
274
- * Import Manager class
275
- *
276
- * Manages content import with extensible format support via converter registry.
277
- */
278
303
  declare class ImportManager extends BaseManager {
304
+ #private;
279
305
  /** Registry of format converters */
280
306
  private converterRegistry;
281
307
  /** ICE (#1979): shims by id (`<owner>.<name>`), in registration order. */
@@ -284,13 +310,19 @@ declare class ImportManager extends BaseManager {
284
310
  private readonly receiverRegistry;
285
311
  /** ICE: runs this process knows about, oldest first. */
286
312
  private readonly iceRuns;
287
- /** ICE: runs waiting for the background job, each with its own context. */
313
+ /** ICE (#2171): a rechecking run's store, as fingerprint → id, read once per run and store; dropped when the run ends. */
314
+ private readonly storeHoldings;
315
+ /** ICE: work waiting for the background job, each with its own context: an import run or an export (#2120). */
288
316
  private readonly iceQueue;
289
317
  /** ICE: true while a background job is draining the queue. */
290
318
  private iceDraining;
291
319
  /** ICE: the last background run id the queue was handed to. */
292
320
  private iceLastJobRunId;
293
321
  private iceLedger;
322
+ /** ICE export (#2120): the owners' doors for reading what is exported, by id. */
323
+ private readonly exportSourceRegistry;
324
+ /** ICE export: runs this process holds, oldest first, each with its file once made. Never written to disk. */
325
+ private readonly exportRuns;
294
326
  constructor(engine: WikiEngine);
295
327
  /**
296
328
  * Initialize the manager
@@ -316,7 +348,9 @@ declare class ImportManager extends BaseManager {
316
348
  /**
317
349
  * Register a receiver (D4): a door that already exists. Core's are
318
350
  * `core.pages` and `core.files`; an add-on registers its own manager's door
319
- * through `host.registerReceiver`.
351
+ * through `host.registerReceiver`. Refuses `writesEncrypted` from every
352
+ * caller, whatever owner it names: core registers its own receivers
353
+ * through the private `#registerReceiver` (#2179).
320
354
  *
321
355
  * @returns The receiver's id, `<owner>.<name>`
322
356
  */
@@ -333,18 +367,49 @@ declare class ImportManager extends BaseManager {
333
367
  * (D11), for the import screen (#2021).
334
368
  */
335
369
  receiversFor(ctx: ActorContext): Promise<UsableReceiver[]>;
370
+ /**
371
+ * Why a name the user gave cannot be used for this receiver (#2130), or
372
+ * null when it can: the engine's one rule (`givenNameProblem`), with the
373
+ * site's `ngdpbase.import.engine.name-max-length`. A receiver that names
374
+ * what it creates itself (no `names`) takes no name at all. For the import
375
+ * screen, to answer at once; the engine checks again in the run.
376
+ */
377
+ givenNameProblem(receiverId: string, given: string, original: string): string | null;
336
378
  /**
337
379
  * The original-file choices a receiver offers (D11, #2021): what it
338
380
  * declares, else all three; its default when it names one it allows, else
339
381
  * `ngdpbase.import.engine.default-original` when allowed, else the first.
340
382
  */
341
383
  private originalChoices;
384
+ /**
385
+ * The request's place within the receiver, checked (#2118): one the
386
+ * receiver's `targets` gives this context, for a receiver with places,
387
+ * and none for any other. An unknown receiver is left for the run to report.
388
+ *
389
+ * @throws Error when the place is missing, not this user's, or not the receiver's
390
+ */
391
+ private checkTarget;
342
392
  /**
343
393
  * The request as the engine runs it: the receiver's default original when
344
394
  * none was chosen, and a refusal for a choice the receiver does not offer
345
395
  * (D11, #2021). An unknown receiver is left for the run to report.
346
396
  */
347
397
  private withOriginal;
398
+ /**
399
+ * Vet one file before it is kept as a file (#2149): its type from its
400
+ * bytes (#1624), then its format's shim, when that shim is switched on,
401
+ * cleans it. FileManager asks this for every file it stores, and keeps the
402
+ * bytes returned, never the ones it sent; `removed` says what was taken
403
+ * out, for the uploader and the upload's record.
404
+ *
405
+ * Runs in the caller's request: cleaning is one linear pass over a file no
406
+ * larger than the site's upload limit. It decides nothing about permission:
407
+ * the caller has asked that first.
408
+ *
409
+ * @param ctx - Who is keeping the file (P1); only named in the log line when something was cleaned
410
+ * @throws IceVetRefused when the cleaning shim fails: the file could not be checked, so it is not kept
411
+ */
412
+ vetFile(upload: IceUpload, ctx: ActorContext): Promise<IceVetResult>;
348
413
  /**
349
414
  * Run one import now and wait for it. For code already off the request
350
415
  * path: an add-on's own background job, a test. A route uses
@@ -372,6 +437,11 @@ declare class ImportManager extends BaseManager {
372
437
  * the store's sealed index entry written. Each is recorded in the ledger
373
438
  * as the keep-once record (#2011), so the run reuses it and needs no key.
374
439
  *
440
+ * A receiver whose `receive` itself may write into an encrypted store
441
+ * (`writesEncrypted`, #2118) has the whole run now, in this request, with
442
+ * `ctx` and its key, and the run's id is returned when it has ended: no
443
+ * key is ever carried into a job (#2034).
444
+ *
375
445
  * @throws ImportUploadRefused when an original could not be kept; nothing is queued
376
446
  */
377
447
  enqueueUpload(request: IceRunRequest, ctx: ActorContext): Promise<string>;
@@ -396,14 +466,105 @@ declare class ImportManager extends BaseManager {
396
466
  /**
397
467
  * Is an import into this receiver confidential (#2110)? Only when its
398
468
  * destination is encrypted: a receiver that keeps into a sealed store
399
- * (`keepsEncrypted`). No receiver can declare itself so otherwise. A
469
+ * (`keepsEncrypted`), or whose own door writes into one
470
+ * (`writesEncrypted`, #2118). No receiver can declare itself so otherwise. A
400
471
  * confidential import's file names are shown to the uploader and kept in
401
- * the audit log, and never written to application logs, notices or admin
402
- * screens; every other import logs them as before.
472
+ * the audit log, and never written to application logs, notices, admin
473
+ * screens or the import ledger (#2161); every other import logs them as before.
403
474
  */
404
475
  isConfidential(receiverId: string): boolean;
405
476
  /** Use this ledger instead of the file one: a test's, or a database table's. */
406
477
  setImportLedger(ledger: IceLedger): void;
478
+ /**
479
+ * Register an export source (#2120): the owner's door for reading what is
480
+ * exported, checking its existing read permission (D5). Core's is
481
+ * `core.pages`.
482
+ *
483
+ * @returns The source's id, `<owner>.<name>`
484
+ * @throws Error when the source is malformed or its id is taken
485
+ */
486
+ registerExportSource(owner: string, source: IceExportSource): string;
487
+ /**
488
+ * The formats the Export action offers this context for one thing (#2120):
489
+ * none when the source's owner would not let it export the thing (asked
490
+ * as an affordance, recording nothing), else each `out` shim, switched on,
491
+ * that can make a file from what the source yields.
492
+ */
493
+ exportFormatsFor(sourceId: string, ref: string, ctx: ActorContext): Promise<IceExportFormat[]>;
494
+ /**
495
+ * Start an export (#2120), from the request that asked: the source reads
496
+ * the thing now, with the request's own context, so its owner's door
497
+ * decides with everything the request carries (step-up, a vault's key)
498
+ * and records the export. The shims run off the request path, in the ICE
499
+ * background job, as the user's own job context. Returns the run id at once.
500
+ *
501
+ * Before anything is read, the user may have at most
502
+ * `max-queued-per-user` exports queued or running, and a held run must be
503
+ * free to make room under `max-kept-runs`. Once read, the items count
504
+ * toward `max-held-bytes` until their file is made.
505
+ *
506
+ * @throws IceExportRefused when the thing is not there, the user may not
507
+ * export it, or the format is not one offered for it
508
+ * @throws IceExportNeedsFreshSignIn when the user holds the permission but
509
+ * step-up asks for a fresh sign-in
510
+ * @throws IceExportBusy when the user, or the site, is making as many
511
+ * exports as it may
512
+ * @throws IceExportFailed when the thing is larger than this site exports
513
+ */
514
+ startExport(request: IceExportRequest, ctx: ActorContext): Promise<string>;
515
+ /** An export's status, to the user it runs as; null to anyone else, and once it is let go. */
516
+ getExportRun(runId: string, ctx: ActorContext): IceExportReport | null;
517
+ /** An export's file, to the user it runs as, while it is kept; null otherwise. */
518
+ exportFile(runId: string, ctx: ActorContext): {
519
+ name: string;
520
+ mediaType: string;
521
+ bytes: Buffer;
522
+ } | null;
523
+ /** The source's formats now: the enabled out shims' chains from what it yields. */
524
+ private exportChainsFor;
525
+ private executeExport;
526
+ /** The source's own record of how an export ended, if it keeps one; a record that fails is logged, never thrown. */
527
+ private recordExportOutcome;
528
+ /**
529
+ * Before a new export is read (#2120): refuse when the user already has
530
+ * `max-queued-per-user` exports queued or running, and let finished runs go
531
+ * until there is room under `max-kept-runs`. A queued or running run is
532
+ * never let go.
533
+ *
534
+ * @throws IceExportBusy when there is no room
535
+ */
536
+ private makeRoomForExport;
537
+ /**
538
+ * Hold a new run with the bytes its items take until its file is made,
539
+ * which count toward `max-held-bytes` (#2120). Finished files are let go
540
+ * to make room, the user's own first ({@link finishedExportRuns}).
541
+ *
542
+ * @throws IceExportFailed when the items alone are more than it may hold
543
+ * @throws IceExportBusy when queued and running exports alone hold too much
544
+ */
545
+ private holdExportRun;
546
+ /**
547
+ * Keep a made file in memory until `keep-minutes` have passed, never on
548
+ * disk: an export of a sealed vault's page must not rest unencrypted. Its
549
+ * items' bytes are released for the file's. Other finished files are let
550
+ * go when all would hold more than `ngdpbase.export.engine.max-held-bytes`,
551
+ * the user's own first.
552
+ */
553
+ private keepExportFile;
554
+ /** Let finished files go, `username`'s own first, until all held bytes are at most `limit`. */
555
+ private letFilesGo;
556
+ /** Every byte exports hold: made files, and the items of runs not yet made. */
557
+ private heldExportBytes;
558
+ /**
559
+ * The runs that may be let go, in the order they are: done or failed only,
560
+ * `username`'s own oldest first, then everyone else's oldest first, so one
561
+ * user's exports push out their own before anyone else's (#2120).
562
+ */
563
+ private finishedExportRuns;
564
+ private dropExportRun;
565
+ /** `ngdpbase.export.engine.*`: every limit an export applies is configuration (the chain's length is import's). */
566
+ private exportSettings;
567
+ private exportManager;
407
568
  /**
408
569
  * The ledger scope a run uses (#2013): bound to the receiver and to the user
409
570
  * `ctx` names, by their identity `@id`; their username only when the
@@ -430,7 +591,7 @@ declare class ImportManager extends BaseManager {
430
591
  private enabledShims;
431
592
  /**
432
593
  * Hand the queue to the background job. One job run drains every queued
433
- * import, each under its own context. A job run that has finished draining
594
+ * import and export, each under its own context. A job run that has finished draining
434
595
  * but is not yet marked done answers `enqueue` with its old run id; then
435
596
  * try again shortly, so nothing queued is stranded.
436
597
  */
@@ -455,15 +616,101 @@ declare class ImportManager extends BaseManager {
455
616
  * `core.pages` (D4): NCM into pages, through PageManager's save door, as the
456
617
  * folder import saves. It checks `page-create` itself (D5). A title that
457
618
  * already exists is a duplicate, as it is for the folder import by default.
619
+ * A title the user gave (#2130) replaces the one the file gives; when a
620
+ * page has it already, the item is refused, never saved over that page.
458
621
  */
459
622
  private pagesReceiver;
460
623
  /**
461
- * `core.files` (D4): a file through AttachmentManager's upload door, which
462
- * checks `asset-upload` itself. Also where a kept original goes (D11) when
463
- * the chosen receiver does not keep its own. It becomes FileManager's write
464
- * door once the files layer has one (#1851).
624
+ * `page-export` for this page (#2120). For a page in a vault, asked about
625
+ * the page as the route door asks it (`hasPermissionOn`, #1539): the
626
+ * container rule, then the capability with the page's vault, so a
627
+ * site-wide role does not reach in. For any other page, the capability.
628
+ * An affordance asks whether it is held (no step-up); the door whether it
629
+ * is permitted, and a refusal to someone who holds it is step-up's.
630
+ */
631
+ private mayExportPage;
632
+ /**
633
+ * `core.pages` as an export source (#2120, D5: pages export checks
634
+ * `page-export`). A page is offered for export when its reader may see it
635
+ * and holds `page-export` (affordances, recording nothing). Reading it
636
+ * for export asks `page-export` ({@link mayExportPage}), then reads through the page
637
+ * door (`readPage`), which decides `page-read` on the page and records a
638
+ * refusal. The export is recorded as `page-export`, with the format and
639
+ * the run, once the run has ended: a success only when the file was made.
640
+ */
641
+ private pagesExportSource;
642
+ /**
643
+ * `core.files` (D4): a file into the public place through FileManager's
644
+ * write door, `put` (#2149), which asks `asset-upload` itself and vets the
645
+ * file. Also where a kept original goes (D11) when the chosen receiver does
646
+ * not keep its own. A name the user gave (#2130)
647
+ * is the stored file's name; when the shared pool has a file of that name
648
+ * already, the item is refused, never stored beside or over it.
465
649
  */
466
650
  private filesReceiver;
651
+ /**
652
+ * `core.private-store` (#2118): a takeout (#1387) handed back into one of
653
+ * its owner's own private stores, read by the shim `core.takeout`. It
654
+ * replaces the takeout import's own door (#1472). Each page goes through
655
+ * PageManager's save door and each file through FileManager's write door
656
+ * (`put`), the doors an edit and an upload use, so a sealed store
657
+ * encrypts on write with no new crypto.
658
+ *
659
+ * - No new permission (D5): the rule the takeout import always had, the
660
+ * `account-security` permission and the user's own private container.
661
+ * - Its places (`targets`) are the user's own stores this context can
662
+ * write now: those on disk and the default one, an encrypted one only
663
+ * while it is unlocked in this session.
664
+ * - Its writes may be sealed (`writesEncrypted`): the engine runs an
665
+ * upload into it in the request, with the request's key, and an import
666
+ * into it is confidential (#2110): no file or page name is logged.
667
+ * - Importing the same takeout again leaves the store as the first import
668
+ * did (operator, 2026-09-25), except that a page or a file deleted since
669
+ * comes back (operator, 2026-10-30; #2171). The engine skips a file it
670
+ * already took (fingerprints), but a page with a uuid and a file are read
671
+ * again (`rechecks`): the uuid check below decides for a page, and for a
672
+ * file whether the store still holds its bytes (`ownStoreFingerprints`, read once a run),
673
+ * so one still held is `duplicate` and not put again (no second upload
674
+ * record). On a first import the file door returns a file the store
675
+ * already holds. A page whose uuid the store holds is `duplicate`: unchanged, or
676
+ * changed since the download, and then the page here is kept. A uuid
677
+ * used elsewhere on the site is `duplicate`, the page named only when
678
+ * the user may view it. A title held by a DIFFERENT page lands beside
679
+ * it as `Title (imported)`.
680
+ * - The original is the file itself: none is kept or cited (D11), as for `core.files`.
681
+ * - The run is recorded as one act, `store-import`, with counts only, and
682
+ * kept as a report page in the store, sealed when the store is (operator,
683
+ * 2026-09-25 and 2026-10-30): see {@link keepRunReport}.
684
+ */
685
+ private privateStoreReceiver;
686
+ /** The takeout import's rule, unchanged (#1472): `account-security`, and only into the user's own container. */
687
+ private mayImportIntoOwnStore;
688
+ /** Where the private-store receiver writes, from configuration. */
689
+ private privateStoreSetup;
690
+ /**
691
+ * The user's own stores this context can write now (#2118): each store on
692
+ * disk and the default one, which a user on a new site has before they
693
+ * wrote anything; an encrypted one only while unlocked in this session.
694
+ * Making a store is its own door (#1414): an encrypted one needs keys.
695
+ */
696
+ private ownStoresWritable;
697
+ /**
698
+ * One page or file of a takeout into the store `info.target` (#2118, as
699
+ * #1472 did). A file through FileManager's `put`; a page after the files
700
+ * it links to, its links pointed at their ids here, through PageManager's
701
+ * save door. Nothing here logs a page's or a file's name (#1461, #2110).
702
+ */
703
+ private receiveTakeoutEntry;
704
+ /** A takeout's page into the store, as #1472 decided: see {@link privateStoreReceiver}. */
705
+ private saveTakeoutPage;
706
+ /**
707
+ * The run as one act (#1472's `store-import`): who, which store, and how
708
+ * each file went, as counts. Never a page's or a file's name (#1461); each
709
+ * file's own record is its door's (`asset-upload`, a sealed store's by id).
710
+ * The counts use the old door's keys, so whatever reads the event reads
711
+ * one shape.
712
+ */
713
+ private recordStoreImport;
467
714
  /**
468
715
  * Get available format IDs
469
716
  *
@@ -656,6 +903,22 @@ declare class ImportManager extends BaseManager {
656
903
  /** The uploaded file's name, for the report page. */
657
904
  sourceName?: string;
658
905
  }): Promise<TakeoutImportReport>;
906
+ /**
907
+ * An engine run into `core.private-store` as the takeout report the old
908
+ * door keeps (code review of #2160, finding 3; operator, 2026-10-30), from
909
+ * the run's per-file status. Only a run that read a takeout keeps one.
910
+ * A file the run skipped as already imported is in neither count.
911
+ *
912
+ * @returns How many files no page links to
913
+ */
914
+ private keepRunReport;
915
+ /**
916
+ * A takeout import's report, kept (operator, 2026-09-25): which files no
917
+ * page now in the store links to, counted in the log, and a page in the
918
+ * store it is about, sealed exactly when the store is, so it can name what
919
+ * a log may not. A failure to save it costs the record, never the import.
920
+ */
921
+ private keepTakeoutReport;
659
922
  /** The import report page's markdown. Private links are `[Title|store/Title]` (#1457). */
660
923
  private takeoutReportMarkdown;
661
924
  /**
@@ -105,6 +105,8 @@ declare class MediaManager extends BaseManager implements CatalogSource {
105
105
  * what degraded means (#1155) — configured, wanted, not working.
106
106
  */
107
107
  private recordFolderState;
108
+ /** One path per line, then "and N more" when the provider capped the list (#2191). */
109
+ private formatFileList;
108
110
  /**
109
111
  * Surface scan/rebuild anomalies to admins via /admin/notifications and the
110
112
  * server log (#807). Missing folders, capture-date gaps, and processing
@@ -120,6 +120,12 @@ export interface PageSaveOptions {
120
120
  from: string;
121
121
  to: string;
122
122
  } | null;
123
+ /**
124
+ * An import's file name, when the user gave the page another title
125
+ * (#2130): recorded on a `create` beside the title. Ignored for a private
126
+ * page, whose record names it by its vault name only.
127
+ */
128
+ importedFrom?: string;
123
129
  /**
124
130
  * Suppress the event. For writes that are a side effect of an already
125
131
  * audited operation, where a second record would describe the same act
@@ -908,6 +914,18 @@ declare class PageManager extends BaseManager implements CatalogSource {
908
914
  * @throws PageAudienceRefusedError when the audience leaves `ctx` out
909
915
  */
910
916
  private assertAudienceAdmits;
917
+ /**
918
+ * The write door's shared-folder check (#2125): a folder's page is added,
919
+ * changed, moved or deleted only by a context the PDP permits
920
+ * `shared-folder-file` on its folder, whatever route it came by (the
921
+ * editor, the raw editor, the API, a restore). Reading a folder
922
+ * (`shared-folder-read`, which every admin holds on every folder) opens
923
+ * its key, so the store would take the write: this is what refuses it.
924
+ * A name that is not a folder page is not checked here.
925
+ *
926
+ * @throws when `ctx` may not file into the page's folder
927
+ */
928
+ private assertMayFileInSharedFolder;
911
929
  /**
912
930
  * Admin-override save (#689): the admin "Edit raw" textarea, split into
913
931
  * frontmatter and body and written through the door (#1462).
@@ -1081,6 +1099,9 @@ declare class PageManager extends BaseManager implements CatalogSource {
1081
1099
  * or a share link the owner issued that names this vault. Anyone else gets
1082
1100
  * an empty list, the same as a vault with nothing in it. Whether each page
1083
1101
  * may then be read is still the page door's answer.
1102
+ *
1103
+ * A shared folder (`SHARED_FOLDER_OWNER`, #2125): whoever the PDP permits
1104
+ * `shared-folder-read` on it.
1084
1105
  */
1085
1106
  listVaultPages(ctx: ActorContext, owner: string, vault: string): Promise<Array<{
1086
1107
  name: string;
@@ -1295,6 +1316,24 @@ declare class PageManager extends BaseManager implements CatalogSource {
1295
1316
  owner: string;
1296
1317
  store: string;
1297
1318
  }>>;
1319
+ /**
1320
+ * The pages of shared folders that match (#2125), from each folder's own
1321
+ * sealed search index, as a private store's are found. Only the folders
1322
+ * `ctx` may read: each is asked `shared-folder-read` here, whatever the
1323
+ * caller passed, and a folder it may not read gives nothing, not even a
1324
+ * count (decision 7). Folder pages are in no site-wide index.
1325
+ *
1326
+ * @param ctx - Who is searching (#1179)
1327
+ * @param folders - The folders to search, by name (SharedFolderManager's list)
1328
+ * @param ask - The query, as {@link matchStoreSearch} reads it
1329
+ */
1330
+ searchSharedFolderPages(ctx: ActorContext, folders: readonly string[], ask?: StoreSearchQuery): Promise<Array<StoreSearchMatch & {
1331
+ name: string;
1332
+ owner: string;
1333
+ store: string;
1334
+ }>>;
1335
+ /** Matches from `owner`'s `stores`' own search indexes, read through `ctx`; a store that will not read gives none. */
1336
+ private searchStoreIndexes;
1298
1337
  /**
1299
1338
  * Build a store's search index from its pages, replacing whatever is there.
1300
1339
  *
@@ -1361,6 +1400,20 @@ declare class PageManager extends BaseManager implements CatalogSource {
1361
1400
  * }
1362
1401
  */
1363
1402
  pageExists(identifier: string, ctx: ActorContext): boolean;
1403
+ /**
1404
+ * The uuid of the page that holds `name` as its title, or as its slug (#2140).
1405
+ *
1406
+ * Whether a name is taken — the uniqueness question a save and the shipped
1407
+ * pages screen ask. An exact match, ignoring case, by the store's own rule
1408
+ * (the provider's `uuidHoldingName`); never the plural or fuzzy matching
1409
+ * `getPage` does for links, so "Reviews" is not held by "review".
1410
+ *
1411
+ * @param field - Which name: 'title' or 'slug'
1412
+ * @param name - The title or slug
1413
+ * @param ctx - Who is asking
1414
+ * @returns The holder's uuid, or null when no page holds it
1415
+ */
1416
+ uuidHoldingName(field: 'title' | 'slug', name: string, ctx: ActorContext): string | null;
1364
1417
  /**
1365
1418
  * The private titles `ctx` can see in `owner`'s stores, by store id (#1457).
1366
1419
  *
@@ -222,6 +222,10 @@ declare class SearchManager extends BaseManager {
222
222
  * yields nothing rather than an error.
223
223
  *
224
224
  * A private row carries the page's private name, so `pageUrl` links to it.
225
+ *
226
+ * And the pages of the shared folders the requester may read (#2125), from
227
+ * each folder's own sealed index, through the same door. A folder they may
228
+ * not read gives nothing: no row, no title, no count (decision 7).
225
229
  */
226
230
  private mergeOwnPrivateResults;
227
231
  /**