@jwilleke/ngdpbase-types 5.40.0 → 5.41.1
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.
- package/lib/packages/media-date/index.d.ts +34 -0
- package/lib/packages/ngdpbase-types/index.d.ts +1 -1
- package/lib/src/ice/fileHandling.d.ts +6 -1
- package/lib/src/ice/runExport.d.ts +41 -0
- package/lib/src/ice/runImport.d.ts +16 -7
- package/lib/src/ice/shims/csv.d.ts +24 -0
- package/lib/src/ice/shims/export.d.ts +23 -0
- package/lib/src/ice/shims/index.d.ts +3 -1
- package/lib/src/ice/shims/svg.d.ts +27 -0
- package/lib/src/ice/shims/takeout.d.ts +83 -0
- package/lib/src/ice/types.d.ts +251 -5
- package/lib/src/ice/vet.d.ts +19 -0
- package/lib/src/managers/AttachmentManager.d.ts +86 -36
- package/lib/src/managers/AuthManager.d.ts +117 -7
- package/lib/src/managers/AuthManager.js +341 -37
- package/lib/src/managers/ConfigurationManager.d.ts +6 -0
- package/lib/src/managers/ExportManager.d.ts +16 -0
- package/lib/src/managers/ExportManager.js +25 -0
- package/lib/src/managers/FileManager.d.ts +182 -0
- package/lib/src/managers/ImportManager.d.ts +264 -16
- package/lib/src/managers/MediaManager.d.ts +11 -0
- package/lib/src/managers/SharedFolderManager.d.ts +320 -0
- package/lib/src/providers/BaseMediaProvider.d.ts +4 -0
- package/lib/src/providers/FileSystemMediaProvider.d.ts +25 -0
- package/lib/src/security/PolicyDecisionPoint.d.ts +3 -2
- package/lib/src/security/permissions.generated.d.ts +3 -3
- package/lib/src/types/File.d.ts +90 -0
- package/lib/src/types/express.d.ts +3 -1
- package/lib/src/types/linkedom.d.ts +2 -0
- package/lib/src/utils/auditEventNames.generated.d.ts +2 -0
- package/lib/src/utils/auditEventNames.generated.js +2 -0
- package/lib/src/utils/auditEvents.d.ts +3 -0
- package/lib/src/utils/auditEvents.js +3 -0
- package/lib/src/utils/fileMigrationPlan.d.ts +81 -0
- package/lib/src/utils/mediaFolderOverlap.d.ts +50 -0
- package/lib/src/utils/privateStoreAccess.d.ts +21 -0
- package/lib/src/utils/privateStoreAccess.js +11 -0
- package/lib/src/utils/privateStoreImport.d.ts +0 -10
- package/lib/src/utils/privateStorePath.d.ts +12 -0
- package/lib/src/utils/privateStorePath.js +18 -0
- package/lib/src/utils/recoveryWordsFile.d.ts +54 -0
- package/lib/src/utils/sessionEnd.d.ts +1 -0
- package/lib/src/utils/sessionEnd.js +4 -0
- package/lib/src/utils/sessionIdle.d.ts +4 -0
- package/lib/src/utils/sessionIdle.js +6 -0
- package/package.json +1 -1
- package/lib/src/providers/mediaFilenameDate.d.ts +0 -22
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileManager — the one door for files (#1852, epic #1851).
|
|
3
|
+
*
|
|
4
|
+
* It reads today's attachments as file records (src/types/File.ts): the
|
|
5
|
+
* shared pool as the public place, and every vault the requester may read.
|
|
6
|
+
* It writes through one door, `put` (#2148): a file goes into the place the
|
|
7
|
+
* caller chose, after the permission for that place, with AttachmentManager
|
|
8
|
+
* as the storage underneath; `placesWritableBy` lists the places a user may
|
|
9
|
+
* write to. Rename, move, delete and per-place indexes are later steps
|
|
10
|
+
* (docs/guides/storage-developer-guide.md).
|
|
11
|
+
*
|
|
12
|
+
* Read access is the existing rules, asked at this door:
|
|
13
|
+
* - the public place needs `asset-read`, from the PDP;
|
|
14
|
+
* - a vault is read through AttachmentManager's own vault reads, so the
|
|
15
|
+
* container rule (owner or delegate, never a role, admin included) decides
|
|
16
|
+
* exactly as it does for a vault page's files.
|
|
17
|
+
*
|
|
18
|
+
* The operator inventory (the migration dry run, #1859) is not on the
|
|
19
|
+
* engine's door: only an instance opened by {@link FileManager.openForOperator},
|
|
20
|
+
* from the command line, answers it.
|
|
21
|
+
*/
|
|
22
|
+
import BaseManager from './BaseManager.js';
|
|
23
|
+
import { type AttachmentMetadata, type PutFile, type PutOptions, type UploadOptions } from './AttachmentManager.js';
|
|
24
|
+
import { type ActorContext } from '../context/ActorContext.js';
|
|
25
|
+
import { type AttachmentDetail } from '../utils/auditEvents.js';
|
|
26
|
+
import { type AssetRecord } from '../types/Asset.js';
|
|
27
|
+
import type { FileInventory, FilePlace, FileRecord } from '../types/File.js';
|
|
28
|
+
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
29
|
+
import { type FileMigrationReport } from '../utils/fileMigrationPlan.js';
|
|
30
|
+
/**
|
|
31
|
+
* What `put` hands back (#2149): the stored file's metadata, and, when
|
|
32
|
+
* vetting cleaned it, what was removed (one note per kind), so the uploader
|
|
33
|
+
* can be told.
|
|
34
|
+
*/
|
|
35
|
+
export type PutResult = AttachmentMetadata & {
|
|
36
|
+
cleaned?: string[];
|
|
37
|
+
};
|
|
38
|
+
/** A place the Upload file dialog offers (#2149): its key in the form, the words the user reads, and the place. */
|
|
39
|
+
export interface UploadPlace {
|
|
40
|
+
key: string;
|
|
41
|
+
label: string;
|
|
42
|
+
place: FilePlace;
|
|
43
|
+
}
|
|
44
|
+
/** Where the Upload file dialog was opened (#2149): the page's path, and the page it shows, when there is one. */
|
|
45
|
+
export interface UploadWhere {
|
|
46
|
+
path?: string;
|
|
47
|
+
pageName?: string;
|
|
48
|
+
}
|
|
49
|
+
declare class FileManager extends BaseManager {
|
|
50
|
+
/** Set only by {@link openForOperator}: this instance answers the inventory. */
|
|
51
|
+
private operatorInstance;
|
|
52
|
+
constructor(engine: WikiEngine);
|
|
53
|
+
/**
|
|
54
|
+
* Open the door read-only for an operator command (#1859): the site's
|
|
55
|
+
* configuration and attachment index, with nothing else started and
|
|
56
|
+
* nothing written. The site may be running; this process changes no file.
|
|
57
|
+
*/
|
|
58
|
+
static openForOperator(): Promise<FileManager>;
|
|
59
|
+
/** Every file `ctx` may read, newest first; `place` narrows it to one place. */
|
|
60
|
+
list(ctx: ActorContext, filter?: {
|
|
61
|
+
place?: FilePlace;
|
|
62
|
+
}): Promise<FileRecord[]>;
|
|
63
|
+
/** The file with this id, when `ctx` may read it. */
|
|
64
|
+
get(id: string, ctx: ActorContext): Promise<FileRecord | null>;
|
|
65
|
+
/**
|
|
66
|
+
* The files named `filename` that `ctx` may read: one per place at most,
|
|
67
|
+
* since a name is unique within a place (today's pool can hold more than
|
|
68
|
+
* one, which is what the migration's suffixes are for). `place` narrows it.
|
|
69
|
+
*/
|
|
70
|
+
findByName(filename: string, ctx: ActorContext, place?: FilePlace): Promise<FileRecord[]>;
|
|
71
|
+
/**
|
|
72
|
+
* The username a public record's uploader names (#2097): the uploader is
|
|
73
|
+
* stored as an identity @id, resolved through IdentityManager; an orphan,
|
|
74
|
+
* an @id with no account, `Unknown` or an email names no owner.
|
|
75
|
+
*/
|
|
76
|
+
private ownerOf;
|
|
77
|
+
private readable;
|
|
78
|
+
/** `asset-read` for the public place, from the PDP. No PDP, no answer: closed. */
|
|
79
|
+
private mayReadPublic;
|
|
80
|
+
/**
|
|
81
|
+
* The places `ctx` may put a file in (#2148, operator 2026-10-30): the
|
|
82
|
+
* public place when the PDP grants `asset-upload`; each of the user's own
|
|
83
|
+
* vaults the vault rule lets them upload to (the default vault, and every
|
|
84
|
+
* vault they already have); each shared folder where they hold
|
|
85
|
+
* `shared-folder-file`. A place they may not write to is never listed.
|
|
86
|
+
*
|
|
87
|
+
* Each place is asked exactly as {@link put} asks it, so what is offered is
|
|
88
|
+
* what the door allows. An affordance: nothing is recorded.
|
|
89
|
+
*/
|
|
90
|
+
placesWritableBy(ctx: ActorContext): Promise<FilePlace[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Put a file in `place` (#2148): the one write door. The permission for
|
|
93
|
+
* that place is asked first, and a refusal stores nothing. Then ICE vets
|
|
94
|
+
* the file (#2149): its type from its bytes, and its format's shim cleans
|
|
95
|
+
* it; the cleaned copy is the one kept. AttachmentManager stores it as the
|
|
96
|
+
* place requires (sealed in a sealed vault or shared folder), and this door
|
|
97
|
+
* records the upload with what vetting removed (a sealed place's file by
|
|
98
|
+
* id only, #2048). No page decides the place: `options.pageName` is only
|
|
99
|
+
* the page that shows it.
|
|
100
|
+
*
|
|
101
|
+
* @throws Error 'Permission denied: …' when `ctx` may not write there, or
|
|
102
|
+
* is nobody (#1926); a route answers it 401 or 403.
|
|
103
|
+
* @throws IceVetRefused when the file could not be checked; nothing is stored
|
|
104
|
+
*/
|
|
105
|
+
put(ctx: ActorContext, place: FilePlace, file: PutFile, options?: PutOptions): Promise<PutResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Record a file stored or deleted (#2149: moved here from AttachmentManager,
|
|
108
|
+
* which stores and records nothing itself). An upload is `continue`: a
|
|
109
|
+
* failed record is logged, never fatal. A delete is critical: it rethrows,
|
|
110
|
+
* tagged `AUDIT_WRITE_FAILED`, so the delete does not happen unrecorded and
|
|
111
|
+
* a caller can tell an unwritable record from a failed delete.
|
|
112
|
+
*
|
|
113
|
+
* A sealed store's file is recorded by id and store only (#2048), its
|
|
114
|
+
* owner by identity @id with the username beside it (#2041).
|
|
115
|
+
* AttachmentManager's delete door calls this until deleting is FileManager's.
|
|
116
|
+
*/
|
|
117
|
+
recordFileEvent(op: 'upload' | 'delete', context: ActorContext, detail: AttachmentDetail, wikiContext?: {
|
|
118
|
+
request?: {
|
|
119
|
+
ip?: string;
|
|
120
|
+
} | null;
|
|
121
|
+
}): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* What the Upload file dialog offers `ctx` (#2149): every place
|
|
124
|
+
* {@link placesWritableBy} lists, and nothing else, each with the words the
|
|
125
|
+
* user reads; and the one selected at first: where they are working
|
|
126
|
+
* ({@link workingPlaceKey}), else the public place, else the first. Never
|
|
127
|
+
* inferred from whether a page exists. An affordance: nothing is recorded.
|
|
128
|
+
*/
|
|
129
|
+
uploadPlaces(ctx: ActorContext, where?: UploadWhere): Promise<{
|
|
130
|
+
places: UploadPlace[];
|
|
131
|
+
defaultKey: string | null;
|
|
132
|
+
}>;
|
|
133
|
+
/** The place a dialog key names, when it is one `ctx` may write to (#2149); null otherwise. Never a place it was not offered. */
|
|
134
|
+
placeForKey(ctx: ActorContext, key: string): Promise<FilePlace | null>;
|
|
135
|
+
/** A place's key in the dialog: `public`, `vault:<id>` (always the user's own) or `folder:<name>`. */
|
|
136
|
+
static placeKey(place: FilePlace): string;
|
|
137
|
+
/** The words the user reads for a place. */
|
|
138
|
+
private placeLabel;
|
|
139
|
+
/**
|
|
140
|
+
* Where the user is working, as a dialog key (#2149): a shared folder's
|
|
141
|
+
* list (`/shared/<folder>`) or one of its pages is that folder; a page in
|
|
142
|
+
* one of their own vaults is that vault. Read from the location, never
|
|
143
|
+
* from whether a page by that name exists; anywhere else is no place.
|
|
144
|
+
*/
|
|
145
|
+
private workingPlaceKey;
|
|
146
|
+
/**
|
|
147
|
+
* The place today's page-tied upload goes to (#1396, #1398, #2125), until
|
|
148
|
+
* #2149/#2150 move its callers to a place the user chose: an upload onto a
|
|
149
|
+
* shared folder's page goes into that folder, saved or not; onto a vault
|
|
150
|
+
* page into that page's vault, by someone who may edit the page; with the
|
|
151
|
+
* Private tick into the uploader's own vault (`store`, or their default);
|
|
152
|
+
* otherwise the public place.
|
|
153
|
+
*/
|
|
154
|
+
placeForPageUpload(ctx: ActorContext, options: Pick<UploadOptions, 'pageName' | 'private' | 'store'>): Promise<FilePlace>;
|
|
155
|
+
/** May `ctx` put a file in `place`? The existing rule for each kind, asked of its owner. */
|
|
156
|
+
private mayPut;
|
|
157
|
+
/** The user's own vaults a file may go in: their default vault, and every vault they already have. */
|
|
158
|
+
private ownVaultIds;
|
|
159
|
+
private storeLayout;
|
|
160
|
+
/** A well-formed place: a vault is a user's (never the shared folders' container), with valid ids. */
|
|
161
|
+
private static isPlace;
|
|
162
|
+
/** A place in a log line. Ids only, never a file's name. */
|
|
163
|
+
private static describePlace;
|
|
164
|
+
/**
|
|
165
|
+
* Everything kept as files, for the migration dry run: the public files in
|
|
166
|
+
* full and each vault as counts (files, bytes, clash renames), never its
|
|
167
|
+
* names. Only on an instance opened by {@link openForOperator}, for an
|
|
168
|
+
* operator's context.
|
|
169
|
+
*/
|
|
170
|
+
inventory(ctx: ActorContext): Promise<FileInventory>;
|
|
171
|
+
/** The dry run: the inventory, planned with the configured values. Writes nothing. */
|
|
172
|
+
migrationDryRun(ctx: ActorContext): Promise<FileMigrationReport>;
|
|
173
|
+
/** An asset record as a file record: the same fields, plus place, owner and system-category. */
|
|
174
|
+
static toFileRecord(asset: AssetRecord, place: FilePlace, owner: string | null, systemCategory: string | null): FileRecord;
|
|
175
|
+
private configManager;
|
|
176
|
+
/** The public place's category today: the default entry of `ngdpbase.system-category`. */
|
|
177
|
+
private defaultCategory;
|
|
178
|
+
/** The category that declares this vault, or null when none does. */
|
|
179
|
+
private vaultCategory;
|
|
180
|
+
private suffixDigits;
|
|
181
|
+
}
|
|
182
|
+
export default FileManager;
|
|
@@ -33,7 +33,7 @@ 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
35
|
import { type IceNameForm } from '../ice/givenName.js';
|
|
36
|
-
import type { IceLedger, IceOriginalChoices, IceReceiver, IceRunReport, IceRunRequest, IceShim, RecentImport, RegisteredShim } from '../ice/types.js';
|
|
36
|
+
import type { IceExportFormat, IceExportReport, IceExportRequest, IceExportSource, IceLedger, IceOriginalChoices, IceReceiver, IceRunReport, IceRunRequest, IceShim, IceTarget, IceUpload, IceVetResult, RecentImport, RegisteredShim } from '../ice/types.js';
|
|
37
37
|
/**
|
|
38
38
|
* Options for import operations
|
|
39
39
|
*/
|
|
@@ -218,6 +218,31 @@ export declare class ImportUploadRefused extends Error {
|
|
|
218
218
|
readonly reason: string;
|
|
219
219
|
constructor(file: string, reason: string);
|
|
220
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
|
+
}
|
|
221
246
|
/** One page's outcome in a takeout import (#1472). */
|
|
222
247
|
export type TakeoutPageOutcome = {
|
|
223
248
|
title: string;
|
|
@@ -272,13 +297,11 @@ export interface UsableReceiver {
|
|
|
272
297
|
originals: Required<Omit<IceOriginalChoices, 'types'>>;
|
|
273
298
|
/** What a name the user gives becomes (#2130), when the receiver takes one: the screen offers the field only then. */
|
|
274
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[];
|
|
275
302
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Import Manager class
|
|
278
|
-
*
|
|
279
|
-
* Manages content import with extensible format support via converter registry.
|
|
280
|
-
*/
|
|
281
303
|
declare class ImportManager extends BaseManager {
|
|
304
|
+
#private;
|
|
282
305
|
/** Registry of format converters */
|
|
283
306
|
private converterRegistry;
|
|
284
307
|
/** ICE (#1979): shims by id (`<owner>.<name>`), in registration order. */
|
|
@@ -287,13 +310,19 @@ declare class ImportManager extends BaseManager {
|
|
|
287
310
|
private readonly receiverRegistry;
|
|
288
311
|
/** ICE: runs this process knows about, oldest first. */
|
|
289
312
|
private readonly iceRuns;
|
|
290
|
-
/** ICE:
|
|
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). */
|
|
291
316
|
private readonly iceQueue;
|
|
292
317
|
/** ICE: true while a background job is draining the queue. */
|
|
293
318
|
private iceDraining;
|
|
294
319
|
/** ICE: the last background run id the queue was handed to. */
|
|
295
320
|
private iceLastJobRunId;
|
|
296
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;
|
|
297
326
|
constructor(engine: WikiEngine);
|
|
298
327
|
/**
|
|
299
328
|
* Initialize the manager
|
|
@@ -319,7 +348,9 @@ declare class ImportManager extends BaseManager {
|
|
|
319
348
|
/**
|
|
320
349
|
* Register a receiver (D4): a door that already exists. Core's are
|
|
321
350
|
* `core.pages` and `core.files`; an add-on registers its own manager's door
|
|
322
|
-
* 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).
|
|
323
354
|
*
|
|
324
355
|
* @returns The receiver's id, `<owner>.<name>`
|
|
325
356
|
*/
|
|
@@ -350,12 +381,35 @@ declare class ImportManager extends BaseManager {
|
|
|
350
381
|
* `ngdpbase.import.engine.default-original` when allowed, else the first.
|
|
351
382
|
*/
|
|
352
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;
|
|
353
392
|
/**
|
|
354
393
|
* The request as the engine runs it: the receiver's default original when
|
|
355
394
|
* none was chosen, and a refusal for a choice the receiver does not offer
|
|
356
395
|
* (D11, #2021). An unknown receiver is left for the run to report.
|
|
357
396
|
*/
|
|
358
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>;
|
|
359
413
|
/**
|
|
360
414
|
* Run one import now and wait for it. For code already off the request
|
|
361
415
|
* path: an add-on's own background job, a test. A route uses
|
|
@@ -383,6 +437,11 @@ declare class ImportManager extends BaseManager {
|
|
|
383
437
|
* the store's sealed index entry written. Each is recorded in the ledger
|
|
384
438
|
* as the keep-once record (#2011), so the run reuses it and needs no key.
|
|
385
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
|
+
*
|
|
386
445
|
* @throws ImportUploadRefused when an original could not be kept; nothing is queued
|
|
387
446
|
*/
|
|
388
447
|
enqueueUpload(request: IceRunRequest, ctx: ActorContext): Promise<string>;
|
|
@@ -407,14 +466,105 @@ declare class ImportManager extends BaseManager {
|
|
|
407
466
|
/**
|
|
408
467
|
* Is an import into this receiver confidential (#2110)? Only when its
|
|
409
468
|
* destination is encrypted: a receiver that keeps into a sealed store
|
|
410
|
-
* (`keepsEncrypted`)
|
|
469
|
+
* (`keepsEncrypted`), or whose own door writes into one
|
|
470
|
+
* (`writesEncrypted`, #2118). No receiver can declare itself so otherwise. A
|
|
411
471
|
* confidential import's file names are shown to the uploader and kept in
|
|
412
|
-
* the audit log, and never written to application logs, notices
|
|
413
|
-
* 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.
|
|
414
474
|
*/
|
|
415
475
|
isConfidential(receiverId: string): boolean;
|
|
416
476
|
/** Use this ledger instead of the file one: a test's, or a database table's. */
|
|
417
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;
|
|
418
568
|
/**
|
|
419
569
|
* The ledger scope a run uses (#2013): bound to the receiver and to the user
|
|
420
570
|
* `ctx` names, by their identity `@id`; their username only when the
|
|
@@ -441,7 +591,7 @@ declare class ImportManager extends BaseManager {
|
|
|
441
591
|
private enabledShims;
|
|
442
592
|
/**
|
|
443
593
|
* Hand the queue to the background job. One job run drains every queued
|
|
444
|
-
* 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
|
|
445
595
|
* but is not yet marked done answers `enqueue` with its old run id; then
|
|
446
596
|
* try again shortly, so nothing queued is stranded.
|
|
447
597
|
*/
|
|
@@ -471,14 +621,96 @@ declare class ImportManager extends BaseManager {
|
|
|
471
621
|
*/
|
|
472
622
|
private pagesReceiver;
|
|
473
623
|
/**
|
|
474
|
-
* `
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
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)
|
|
478
647
|
* is the stored file's name; when the shared pool has a file of that name
|
|
479
648
|
* already, the item is refused, never stored beside or over it.
|
|
480
649
|
*/
|
|
481
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;
|
|
482
714
|
/**
|
|
483
715
|
* Get available format IDs
|
|
484
716
|
*
|
|
@@ -671,6 +903,22 @@ declare class ImportManager extends BaseManager {
|
|
|
671
903
|
/** The uploaded file's name, for the report page. */
|
|
672
904
|
sourceName?: string;
|
|
673
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;
|
|
674
922
|
/** The import report page's markdown. Private links are `[Title|store/Title]` (#1457). */
|
|
675
923
|
private takeoutReportMarkdown;
|
|
676
924
|
/**
|
|
@@ -37,6 +37,8 @@ declare class MediaManager extends BaseManager implements CatalogSource {
|
|
|
37
37
|
readonly currentSchemaVersion = 1;
|
|
38
38
|
/** Active media provider (null when not yet initialized) */
|
|
39
39
|
private _provider;
|
|
40
|
+
/** Why configured folders were refused for overlapping private storage (#2195). */
|
|
41
|
+
private overlapReasons;
|
|
40
42
|
/** The configured media folders, for the degraded-state reason (#1167). */
|
|
41
43
|
private mediaFolders;
|
|
42
44
|
/** Return the active media provider for use by AssetManager. */
|
|
@@ -105,6 +107,15 @@ declare class MediaManager extends BaseManager implements CatalogSource {
|
|
|
105
107
|
* what degraded means (#1155) — configured, wanted, not working.
|
|
106
108
|
*/
|
|
107
109
|
private recordFolderState;
|
|
110
|
+
/** Log and notify admins of each folder refused for overlapping private storage (#2195). */
|
|
111
|
+
private surfaceOverlaps;
|
|
112
|
+
/**
|
|
113
|
+
* Why each of `folders` may not be a media folder, empty when all may (#2195).
|
|
114
|
+
* The configuration screen asks before saving `ngdpbase.media.folders`.
|
|
115
|
+
*/
|
|
116
|
+
checkFolders(folders: string[]): string[];
|
|
117
|
+
/** One path per line, then "and N more" when the provider capped the list (#2191). */
|
|
118
|
+
private formatFileList;
|
|
108
119
|
/**
|
|
109
120
|
* Surface scan/rebuild anomalies to admins via /admin/notifications and the
|
|
110
121
|
* server log (#807). Missing folders, capture-date gaps, and processing
|