@jwilleke/ngdpbase-types 5.40.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 (43) hide show
  1. package/lib/packages/ngdpbase-types/index.d.ts +1 -1
  2. package/lib/src/ice/fileHandling.d.ts +6 -1
  3. package/lib/src/ice/runExport.d.ts +41 -0
  4. package/lib/src/ice/runImport.d.ts +16 -7
  5. package/lib/src/ice/shims/csv.d.ts +24 -0
  6. package/lib/src/ice/shims/export.d.ts +23 -0
  7. package/lib/src/ice/shims/index.d.ts +3 -1
  8. package/lib/src/ice/shims/svg.d.ts +27 -0
  9. package/lib/src/ice/shims/takeout.d.ts +83 -0
  10. package/lib/src/ice/types.d.ts +251 -5
  11. package/lib/src/ice/vet.d.ts +19 -0
  12. package/lib/src/managers/AttachmentManager.d.ts +86 -36
  13. package/lib/src/managers/AuthManager.d.ts +117 -7
  14. package/lib/src/managers/AuthManager.js +341 -37
  15. package/lib/src/managers/ExportManager.d.ts +16 -0
  16. package/lib/src/managers/ExportManager.js +25 -0
  17. package/lib/src/managers/FileManager.d.ts +182 -0
  18. package/lib/src/managers/ImportManager.d.ts +264 -16
  19. package/lib/src/managers/MediaManager.d.ts +2 -0
  20. package/lib/src/managers/SharedFolderManager.d.ts +320 -0
  21. package/lib/src/providers/BaseMediaProvider.d.ts +4 -0
  22. package/lib/src/providers/FileSystemMediaProvider.d.ts +6 -0
  23. package/lib/src/security/PolicyDecisionPoint.d.ts +3 -2
  24. package/lib/src/security/permissions.generated.d.ts +3 -3
  25. package/lib/src/types/File.d.ts +90 -0
  26. package/lib/src/types/express.d.ts +3 -1
  27. package/lib/src/types/linkedom.d.ts +2 -0
  28. package/lib/src/utils/auditEventNames.generated.d.ts +2 -0
  29. package/lib/src/utils/auditEventNames.generated.js +2 -0
  30. package/lib/src/utils/auditEvents.d.ts +3 -0
  31. package/lib/src/utils/auditEvents.js +3 -0
  32. package/lib/src/utils/fileMigrationPlan.d.ts +81 -0
  33. package/lib/src/utils/privateStoreAccess.d.ts +21 -0
  34. package/lib/src/utils/privateStoreAccess.js +11 -0
  35. package/lib/src/utils/privateStoreImport.d.ts +0 -10
  36. package/lib/src/utils/privateStorePath.d.ts +12 -0
  37. package/lib/src/utils/privateStorePath.js +18 -0
  38. package/lib/src/utils/recoveryWordsFile.d.ts +54 -0
  39. package/lib/src/utils/sessionEnd.d.ts +1 -0
  40. package/lib/src/utils/sessionEnd.js +4 -0
  41. package/lib/src/utils/sessionIdle.d.ts +4 -0
  42. package/lib/src/utils/sessionIdle.js +6 -0
  43. package/package.json +1 -1
@@ -40,7 +40,7 @@ export type { PageAction } from '../../src/utils/pageUrl.js';
40
40
  export type { default as ConsentManager, ConsentScopeStatus } from '../../src/managers/ConsentManager.js';
41
41
  export type { AddonConsentRegistration, ConsentDocument, ConsentRecord, ConsentRequiredError, ConsentScope, ConsentSeen, ConsentStatus } from '../../src/types/Consent.js';
42
42
  export type { default as ImportManager } from '../../src/managers/ImportManager.js';
43
- export type { IceShim, IceReceiver, IceItem, IceKind, IceWarning, ShimDirection, ShimAccepts, ShimTools, ShimReadResult, ReceiveInfo, ReceiveOutcome, IceCitation, KeptOriginal, OriginalChoice, IceOriginalChoices, OriginalKeep, IceUpload, IceSource, IceUrlSource, IceRunRequest, IceRecordData, FileDoneInfo, IceRunReport, IceFileReport, IceFileStatus, RecentImport } from '../../src/ice/types.js';
43
+ export type { IceShim, IceReceiver, IceItem, IceKind, IceWarning, ShimDirection, ShimAccepts, ShimTools, ShimReadResult, ReceiveInfo, ReceiveOutcome, IceCitation, KeptOriginal, OriginalChoice, IceOriginalChoices, OriginalKeep, IceUpload, IceSource, IceUrlSource, IceRunRequest, IceRecordData, FileDoneInfo, IceTarget, IceArchiveView, IceRunReport, IceFileReport, IceFileStatus, RecentImport } from '../../src/ice/types.js';
44
44
  export type { default as UserManager, UserUpdateInput, UserUpdateFn } from '../../src/managers/UserManager.js';
45
45
  export type { User, UserPreferences } from '../../src/types/User.js';
46
46
  export type { default as AuthManager, InviteOutcome, InvitationView } from '../../src/managers/AuthManager.js';
@@ -41,8 +41,13 @@ export declare function isZip(upload: IceUpload): boolean;
41
41
  * with the type its request gave it. Limits are per archive (`ZipReadLimits`), so a small
42
42
  * archive that expands enormously stops at the budget. An archive that cannot
43
43
  * be read is returned in `unreadable`, with why; the rest go on.
44
+ *
45
+ * With `innerZipsAsFiles` (#2118), only the uploads themselves are unpacked:
46
+ * a zip inside one is passed on as a file, like any other member.
44
47
  */
45
- export declare function expandUploads(uploads: readonly IceUpload[], limits: ZipReadLimits, maxDepth: number, depth?: number): {
48
+ export declare function expandUploads(uploads: readonly IceUpload[], limits: ZipReadLimits, maxDepth: number, options?: {
49
+ innerZipsAsFiles?: boolean;
50
+ }, depth?: number): {
46
51
  files: IceUpload[];
47
52
  unreadable: Array<{
48
53
  name: string;
@@ -0,0 +1,41 @@
1
+ import type { ExportShimTools, IceItem, IceKind, RegisteredShim } from './types.js';
2
+ /** Core's page export source: pages, read through PageManager's door. */
3
+ export declare const PAGES_EXPORT_SOURCE = "core.pages";
4
+ /** Configuration an export reads (each a key under `ngdpbase.export.engine.`). */
5
+ export interface IceExportSettings {
6
+ /** The most bytes an export's source, or any step of its chain, may hold. */
7
+ maxBytes: number;
8
+ /** The most shims one export runs through (`ngdpbase.import.engine.max-chain-depth`, shared with import). */
9
+ maxChainDepth: number;
10
+ }
11
+ /** An export that cannot be made, with the reason a user is shown. */
12
+ export declare class IceExportFailed extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /**
16
+ * The formats that can be made from items of this kind: for each `out`
17
+ * shim that makes a file, the shortest chain of `out` shims that reaches it
18
+ * from `kind`, at most `maxChainDepth` long. Shims are tried in registration
19
+ * order, so between two chains of one length the first registered wins.
20
+ *
21
+ * @returns The file-making shim's id → the chain, ending with it
22
+ */
23
+ export declare function exportChains(shims: readonly RegisteredShim[], kind: IceKind, maxChainDepth: number): Map<string, RegisteredShim[]>;
24
+ /** The bytes items hold, their text counted as UTF-8. */
25
+ export declare function exportSize(items: readonly IceItem[]): number;
26
+ /**
27
+ * Refuse items larger than the export limit.
28
+ *
29
+ * @throws IceExportFailed when they are
30
+ */
31
+ export declare function checkExportSize(items: readonly IceItem[], maxBytes: number): void;
32
+ /**
33
+ * Run the source's items through the chain, each shim's output the next
34
+ * one's input, and return the one file the last shim made. What came before
35
+ * travels with each item while a shim keeps the count: earlier metadata
36
+ * under later, as an import's chain does.
37
+ *
38
+ * @throws IceExportFailed when a step holds more than `maxBytes`, a shim
39
+ * makes something other than what it declares, or the end is not one file
40
+ */
41
+ export declare function runExportChain(items: IceItem[], chain: readonly RegisteredShim[], tools: (shim: RegisteredShim) => ExportShimTools, settings: IceExportSettings): Promise<IceItem>;
@@ -41,10 +41,11 @@ export declare const SCOPE_FORM = "ice2";
41
41
  * The scope a run's fingerprints, per-file status and item keys live in
42
42
  * (#2013): bound to the receiver and to the user the run is for, by their
43
43
  * identity `@id` (else their username, #2041). The engine works it out from
44
- * the caller's context; a request cannot name another user's. A caller's own
45
- * `sub` (phr's source id) narrows it within that user's scope, never outside.
44
+ * the caller's context; a request cannot name another user's. What follows
45
+ * narrows it within that user's scope, never outside: the place within the
46
+ * receiver (#2118), then a caller's own sub-scope (phr's source id).
46
47
  */
47
- export declare function boundScope(receiverId: string, owner: string, sub?: string): string;
48
+ export declare function boundScope(receiverId: string, owner: string, ...subs: Array<string | undefined>): string;
48
49
  /** Does this shim list the receiver, by id or as any receiver (`'*'`, #2109)? */
49
50
  export declare function handsTo(shim: Pick<RegisteredShim, 'receivers'>, receiverId: string): boolean;
50
51
  export interface ChainLeaf {
@@ -64,9 +65,10 @@ export interface ChainSpec {
64
65
  /**
65
66
  * Run a shim, then the shims after it (D9), until each item is done. What
66
67
  * came before travels with each item: earlier metadata under later, earlier
67
- * warnings first. Partial notes are collected into `partial`.
68
+ * warnings first. Partial notes are collected into `partial`; a shim's note
69
+ * that the file holds nothing to import on its own (#2118) into `skipped`.
68
70
  */
69
- export declare function convertChain(shim: RegisteredShim, input: IceItem, chain: string[], spec: ChainSpec, partial: string[]): Promise<ChainLeaf[]>;
71
+ export declare function convertChain(shim: RegisteredShim, input: IceItem, chain: string[], spec: ChainSpec, partial: string[], skipped?: string[]): Promise<ChainLeaf[]>;
70
72
  /**
71
73
  * The name the user gave this file (#2130), as the engine applies it: none
72
74
  * when the receiver names things itself (it declares no `names`) or the user
@@ -85,6 +87,13 @@ export declare function resolveGivenName(upload: IceUpload, receiver: Registered
85
87
  export declare function receiverLink(receiver: RegisteredReceiver, ref: string | undefined, ctx: ActorContext): Promise<string | undefined>;
86
88
  /** What a file's status says when a name the user gave was not applied: the file is already here under another (#2130). */
87
89
  export declare function alreadyHereAs(name: string): string;
90
+ /**
91
+ * Is an import into this receiver confidential (#2110)? Only when its
92
+ * destination is encrypted: a receiver that keeps into a sealed store
93
+ * (`keepsEncrypted`), or whose own door writes into one (`writesEncrypted`,
94
+ * #2118). No receiver can declare itself so otherwise.
95
+ */
96
+ export declare function isConfidentialReceiver(receiver: Pick<RegisteredReceiver, 'keepsEncrypted' | 'writesEncrypted'> | undefined): boolean;
88
97
  /** Does this run keep its originals during the upload (#2034)? */
89
98
  export declare function keepsAtUpload(receiver: RegisteredReceiver | undefined, original: OriginalChoice): boolean;
90
99
  /** What keeping at upload did: how many it kept, or the first file it could not keep. */
@@ -110,10 +119,10 @@ export interface UploadKeepResult {
110
119
  * then queues nothing. Originals kept before it stay kept, and recorded, so
111
120
  * the next try reuses them.
112
121
  */
113
- export declare function keepOriginalsAtUpload(deps: IceRunDeps, request: IceRunRequest, ctx: ActorContext, scope: string): Promise<UploadKeepResult>;
122
+ export declare function keepOriginalsAtUpload(siteDeps: IceRunDeps, request: IceRunRequest, ctx: ActorContext, scope: string): Promise<UploadKeepResult>;
114
123
  /**
115
124
  * Run one import. Fills and returns `report`, which the caller may be
116
125
  * showing while it runs. Never throws for one file's failure; that file's
117
126
  * status says why.
118
127
  */
119
- export declare function runImport(deps: IceRunDeps, request: IceRunRequest, ctx: ActorContext, report: IceRunReport): Promise<IceRunReport>;
128
+ export declare function runImport(siteDeps: IceRunDeps, request: IceRunRequest, ctx: ActorContext, report: IceRunReport): Promise<IceRunReport>;
@@ -54,6 +54,30 @@ export declare function parseCsv(text: string, delimiter?: string, skipLines?: n
54
54
  * invisible one.
55
55
  */
56
56
  export declare function preambleSuspicion(columns: readonly string[]): string | null;
57
+ /**
58
+ * Clean CSV text before it is kept as a file (#2149): HTML is taken out of
59
+ * every cell, and a cell a spreadsheet would run as a formula gets a leading
60
+ * `'`, so it shows as text (the usual CSV-injection defence). Only the cells
61
+ * that change are rewritten, quoted; every other byte is kept as it was.
62
+ * Returns how many cells of each kind were cleaned.
63
+ */
64
+ export declare function cleanCsvText(text: string, delimiter?: string): {
65
+ text: string;
66
+ html: number;
67
+ formulas: number;
68
+ };
69
+ /**
70
+ * How a CSV is read and written back for cleaning (#2149), so its bytes stay
71
+ * its own: UTF-16 with a byte-order mark as UTF-16; valid UTF-8 as UTF-8;
72
+ * anything else (Latin-1, Windows-1252 and the like) one byte to one
73
+ * character and back, which round-trips every byte exactly. The characters
74
+ * cleaning looks for (`, " < > = + - @`, tab, line ends) are ASCII, the same
75
+ * byte in each of these.
76
+ */
77
+ export declare function csvEncoding(bytes: Buffer): {
78
+ text: string;
79
+ encode: (text: string) => Buffer;
80
+ };
57
81
  /** One row as a record: its own id, or one synthesized from its content. */
58
82
  export declare function csvRecord(raw: RawRecord, opts: RecordShimOptions): IceRecordData | null;
59
83
  export declare const csvShim: IceShim;
@@ -0,0 +1,23 @@
1
+ import type { ActorContext } from '../../context/ActorContext.js';
2
+ import { type IceItem, type IceShim } from '../types.js';
3
+ /** The core doors the export shims call. */
4
+ export interface ExportShimDoors {
5
+ /** A complete HTML document for one NCM item (ExportManager's page document). */
6
+ renderDocument(item: IceItem): Promise<string>;
7
+ /** HTML → PDF (ExportManager.htmlToPdf), as the run's user. */
8
+ htmlToPdf(ctx: ActorContext, html: string): Promise<Buffer>;
9
+ /** Is a PDF provider chosen? */
10
+ pdfAvailable(): boolean;
11
+ }
12
+ /** A file name from a title: the title, with what no file system takes replaced. */
13
+ export declare function exportFileName(item: IceItem, extension: string): string;
14
+ /**
15
+ * The HTML with nothing in it that a renderer would fetch (#2120): only
16
+ * `data:` URLs are left, in attributes and in CSS. The local renderer
17
+ * already blocks the network; a remote one (`chrome-service`, Gotenberg)
18
+ * fetches what the HTML names, from where it runs, so this is done before
19
+ * the HTML leaves the process. Links (`<a href>`) stay: printing never
20
+ * follows them.
21
+ */
22
+ export declare function withoutRemoteUrls(html: string): string;
23
+ export declare function exportShims(doors: ExportShimDoors): IceShim[];
@@ -12,5 +12,7 @@
12
12
  * receiver that takes records (`receivers: ['*']`).
13
13
  */
14
14
  import type { IceShim } from '../types.js';
15
- /** Every record shim, in registration order. */
15
+ /** Every record shim, in registration order. `core.csv` also cleans a CSV kept as a file (#2149). */
16
16
  export declare const RECORD_SHIMS: readonly IceShim[];
17
+ /** Shims that only vet a file kept as a file (#2149): they clean and are never picked for a run. */
18
+ export declare const VETTING_SHIMS: readonly IceShim[];
@@ -0,0 +1,27 @@
1
+ /**
2
+ * svg shim (#2149): cleans an SVG before it is kept as a file. An SVG is a
3
+ * document, not a picture: it can carry scripts, HTML inside
4
+ * `<foreignObject>`, `on…` event handlers, script links and animations that
5
+ * set a link. The file is parsed and rebuilt from an allow-list of SVG
6
+ * elements and attributes, never patched with patterns: what the parser did
7
+ * not take as an allowed element or attribute is not written back. The note
8
+ * says what kind of thing was removed and how many, never the content.
9
+ *
10
+ * The parser is the one the site already uses for author HTML
11
+ * (`src/rendering/htmlPolicy.ts`): sanitize-html on htmlparser2, here in XML
12
+ * mode so names keep their case. This file only configures it for SVG.
13
+ *
14
+ * Defence in depth, not the boundary: an uploaded file is always served
15
+ * sandboxed (`untrustedFileHeaders`, #1624), so nothing in it runs as the
16
+ * site. Cleaning keeps a file that is downloaded, embedded elsewhere or
17
+ * opened outside the site from carrying it.
18
+ *
19
+ * A vetting shim only (`clean`, no `read`): never picked for an import run.
20
+ */
21
+ import type { IceShim, IceWarning } from '../types.js';
22
+ /** The cleaned text and what was removed: the pure step behind the shim. */
23
+ export declare function cleanSvgText(text: string): {
24
+ text: string;
25
+ removed: IceWarning[];
26
+ };
27
+ export declare const svgShim: IceShim;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * `core.takeout` (#2118, ICE #1979): a download of a private store (#1387)
3
+ * handed back, into its owner's private store (`core.private-store`). It
4
+ * replaces the takeout import's own path (#1472).
5
+ *
6
+ * The engine unpacks the zip and hands each member here on its own, with the
7
+ * archive beside it (`IceItem.archive`); this shim reads the takeout's
8
+ * layout through that. What a takeout is, as #1387 writes it: one `{store}/`
9
+ * folder holding each page as `{title}.md` with full frontmatter, files under
10
+ * `attachments/`, and `files-index.json` mapping each file's id to its path
11
+ * in the archive. The reader is lenient about the one thing people change,
12
+ * the folder: someone who extracts a takeout and zips it again may add a
13
+ * level, or zip the contents with no folder at all, so the takeout's root is
14
+ * found rather than assumed.
15
+ *
16
+ * - A page (`.md` directly in the root) is one `takeout` item: its body as
17
+ * text, and in `data` its title, uuid and frontmatter, and the files of
18
+ * the takeout its body links to (`/attachments/{old id}`), so the
19
+ * receiver can store those first and point the links at their new ids.
20
+ * - A file (directly in `attachments/`) is one `takeout` item: its bytes,
21
+ * and in `data` its old id, name, media type and description from the index.
22
+ * - The file index is read through the archive by the members it names; on
23
+ * its own it holds nothing to import (`skip`). So does anything else in
24
+ * the archive: versions, the trash, footnotes and comments are not brought
25
+ * back, as before.
26
+ *
27
+ * Pure: no I/O, no access decision, no write. The receiver's door does those.
28
+ */
29
+ import type { IceShim } from '../types.js';
30
+ /** The kind this shim produces: one page or one file of a takeout. */
31
+ export declare const TAKEOUT_KIND = "takeout";
32
+ /** The receiver a takeout goes into: its owner's private store. */
33
+ export declare const PRIVATE_STORE_RECEIVER = "core.private-store";
34
+ /** A file of the takeout that a page links to, found through the archive. */
35
+ export interface TakeoutLinkedFile {
36
+ /** Its id where it came from: what the page's links name. */
37
+ oldId: string;
38
+ /** Its path in the archive. */
39
+ path: string;
40
+ name: string;
41
+ encodingFormat?: string;
42
+ description?: string;
43
+ }
44
+ /** A page of a takeout: the `data` of its item; the body is the item's `text`. */
45
+ export interface TakeoutPageData {
46
+ entry: 'page';
47
+ /** From the frontmatter, else the file's name. */
48
+ title: string;
49
+ /** From the frontmatter. Absent for a page written by hand. */
50
+ uuid?: string;
51
+ /** The frontmatter, as written. */
52
+ metadata: Record<string, unknown>;
53
+ /** The takeout's files its body links to. */
54
+ files: TakeoutLinkedFile[];
55
+ }
56
+ /** A file of a takeout: the `data` of its item; the bytes are the item's `bytes`. */
57
+ export interface TakeoutFileData {
58
+ entry: 'file';
59
+ /** Its id where it came from, when the file index names it. */
60
+ oldId?: string;
61
+ /** The name it was uploaded with. */
62
+ name: string;
63
+ encodingFormat?: string;
64
+ description?: string;
65
+ }
66
+ export type TakeoutData = TakeoutPageData | TakeoutFileData;
67
+ /**
68
+ * The folder the takeout lives in: the one holding the file index, else the
69
+ * shallowest one holding a page. `''` is the archive's own root.
70
+ */
71
+ export declare function takeoutRoot(paths: readonly string[]): string;
72
+ /** `body` with every `/attachments/{old}` link pointed at its new id. */
73
+ export declare function rewriteAttachmentLinks(body: string, ids: ReadonlyMap<string, string>): string;
74
+ /** Whether `body` links to the file `id`. */
75
+ export declare function linksToAttachment(body: string, id: string): boolean;
76
+ /**
77
+ * The first free title of `title`, `title (imported)`, `title (imported 2)` …
78
+ * (operator, 2026-09-25): a DIFFERENT page already holding the title keeps
79
+ * it, and the import lands beside it.
80
+ */
81
+ export declare function freeImportTitle(title: string, isTaken: (candidate: string) => Promise<boolean>): Promise<string>;
82
+ /** The takeout shim: every member of a zip, read as a takeout's layout says. */
83
+ export declare const takeoutShim: IceShim;
@@ -14,9 +14,10 @@
14
14
  * noise filter, fingerprints, skipping unchanged files, per-file status,
15
15
  * keeping the original, and running the work as a background job.
16
16
  *
17
- * Only importing is built in the first slice. `out` / `both` shims and the
18
- * `write` half are declared so export (#1974) and feeds (D10) fit later
19
- * without changing these shapes.
17
+ * - An __export source__ is the owner's door for reading the thing being
18
+ * exported (#2120): a page, later a search result or an add-on's list. It
19
+ * checks the owner's existing read permission (D5) and yields items that
20
+ * `out` shims chain into one file to download.
20
21
  */
21
22
  import type { ActorContext } from '../context/ActorContext.js';
22
23
  import type { GuardedFetchOptions, GuardedResponse } from '../http/guardedFetch.js';
@@ -61,6 +62,28 @@ export interface IceItem {
61
62
  * (a feed entry): an item whose key the engine has seen before is skipped.
62
63
  */
63
64
  itemKey?: string;
65
+ /**
66
+ * The archive the file was unpacked from (#2118), when it was: what lies
67
+ * beside it, for a shim whose format spans the files of an archive (a
68
+ * takeout's pages link to its files by the ids its file index names).
69
+ * The engine still hands each file to the shims on its own.
70
+ */
71
+ archive?: IceArchiveView;
72
+ }
73
+ /**
74
+ * The archive a file was unpacked from (#2118): this file's own path in it,
75
+ * and every member, read only. Paths are the archive's own, `/`-separated.
76
+ * Never part of an item's key: what an item is, is its content.
77
+ */
78
+ export interface IceArchiveView {
79
+ /** The archive's name, as the file's `name` begins: `takeout.zip`. */
80
+ name: string;
81
+ /** This file's path within the archive. */
82
+ path: string;
83
+ /** Every member's path, in the archive's order. */
84
+ paths: readonly string[];
85
+ /** A member's bytes, or undefined when there is no such member. */
86
+ read(memberPath: string): Buffer | undefined;
64
87
  }
65
88
  /**
66
89
  * The `data` of a `record` item (#2109): one row of a table or one entry of a
@@ -99,6 +122,14 @@ export interface ShimTools {
99
122
  */
100
123
  options: Record<string, unknown>;
101
124
  }
125
+ /** What an `out` shim is handed (#2120): a shim's tools, and who the export is for. */
126
+ export interface ExportShimTools extends ShimTools {
127
+ /**
128
+ * The job context of the user who asked for the export, for a core shim
129
+ * that calls a core door with it (the PDF render, P1).
130
+ */
131
+ ctx: ActorContext;
132
+ }
102
133
  /** What a shim's read returns. */
103
134
  export interface ShimReadResult {
104
135
  items: IceItem[];
@@ -107,6 +138,13 @@ export interface ShimReadResult {
107
138
  * records plus a partial note): the file's status is then `partial`, with this.
108
139
  */
109
140
  partial?: string;
141
+ /**
142
+ * Set, with no items, when the file is part of its format's layout and
143
+ * holds nothing to import on its own (#2118): a takeout's file index, read
144
+ * through the archive by the files it names. The file's status is then
145
+ * `noise`, with this, and the ledger records nothing for it.
146
+ */
147
+ skip?: string;
110
148
  }
111
149
  /**
112
150
  * A shim as its author writes it (D7). `name` is unique within its owner; the
@@ -132,8 +170,47 @@ export interface IceShim {
132
170
  duplicates?: 'file' | 'item';
133
171
  /** Inbound: read one input. Required for `in` and `both`. */
134
172
  read?(input: IceItem, tools: ShimTools): Promise<ShimReadResult>;
135
- /** Outbound (export, #1974): not run in the first slice. Required for `out` and `both`. */
136
- write?(items: IceItem[], tools: ShimTools): Promise<IceItem[]>;
173
+ /**
174
+ * Outbound (export, #2120): turn the items into this shim's `produces`.
175
+ * Required for `out` and `both`. An outbound shim matches its input by
176
+ * `accepts.kinds` alone; a shim that produces `file` makes the download,
177
+ * and is what the Export action offers.
178
+ */
179
+ write?(items: IceItem[], tools: ExportShimTools): Promise<IceItem[]>;
180
+ /**
181
+ * An outbound shim whose engine is missing says so (#2120): PDF with no
182
+ * provider chosen. The Export action does not offer it. Default: available.
183
+ */
184
+ isAvailable?(): boolean;
185
+ /**
186
+ * Vetting (#2149): clean this format's own kind of risky content out of a
187
+ * file that is about to be kept as a file (scripts and handlers in an SVG,
188
+ * HTML and formulas in CSV cells). Returns the cleaned bytes, and one note
189
+ * per kind of thing removed, saying what and how many, never the content.
190
+ * An inbound shim has `read()`, `clean()` or both; one with only `clean()`
191
+ * is never picked for a run.
192
+ */
193
+ clean?(input: IceItem): Promise<IceCleanResult>;
194
+ }
195
+ /** What a shim's `clean` returns (#2149). `removed` is empty when the file needed nothing. */
196
+ export interface IceCleanResult {
197
+ bytes: Buffer;
198
+ removed: IceWarning[];
199
+ }
200
+ /**
201
+ * A file after the engine's vetting (#2149): its type read from its bytes,
202
+ * and, when its format's shim found something to clean, the cleaned bytes
203
+ * and what was removed. The caller keeps `bytes`, never the bytes it sent.
204
+ */
205
+ export interface IceVetResult {
206
+ name: string;
207
+ bytes: Buffer;
208
+ /** From the bytes when they show it (#1624), else the declared or extension type. */
209
+ mediaType: string;
210
+ /** What cleaning removed, one note per kind; empty when nothing was. */
211
+ removed: IceWarning[];
212
+ /** The shim that cleaned it, when one did. */
213
+ cleanedBy?: string;
137
214
  }
138
215
  /** A shim the engine holds: its full id, and who registered it. */
139
216
  export interface RegisteredShim extends IceShim {
@@ -214,6 +291,18 @@ export interface ReceiveInfo {
214
291
  * taken there: never a silent replace.
215
292
  */
216
293
  givenName?: string;
294
+ /**
295
+ * The place within the receiver the run goes to (#2118), for a receiver
296
+ * with `targets`: one its `targets` gave the user the run is for, checked
297
+ * by the engine before the run. The receiver's door still checks it.
298
+ */
299
+ target?: string;
300
+ /**
301
+ * Set when the file was taken before and is read again only because the
302
+ * receiver `rechecks` this item (#2171): the receiver's own check decides
303
+ * whether it still holds it, and one it holds is `duplicate`.
304
+ */
305
+ rechecked?: boolean;
217
306
  }
218
307
  /** What a receiver's `fileDone` is told (#2109). */
219
308
  export interface FileDoneInfo extends ReceiveInfo {
@@ -236,6 +325,11 @@ export interface ReceiveOutcome {
236
325
  */
237
326
  heldAs?: string;
238
327
  }
328
+ /** A place within a receiver (#2118): a user's private store. */
329
+ export interface IceTarget {
330
+ id: string;
331
+ label?: string;
332
+ }
239
333
  /**
240
334
  * A receiver as its owner writes it (D4). Its doors are the receiver's own
241
335
  * existing ones; each checks its own permission with the context given (D5).
@@ -294,6 +388,49 @@ export interface IceReceiver {
294
388
  * before the run is queued; the run reuses what was kept and needs no key.
295
389
  */
296
390
  keepsEncrypted?: boolean;
391
+ /**
392
+ * `receive` itself may write into an encrypted store (a takeout into a
393
+ * sealed private store, #2118), which only a request holding the user's
394
+ * unlocked key can write. The engine then runs an upload into it during
395
+ * the upload request, with the request's own context, instead of as a
396
+ * background job: no key is ever carried into a job (#2034's decision).
397
+ * An import into it is confidential (#2110). Core's only: an add-on
398
+ * receiver that declares it is refused at registration, so an add-on is
399
+ * confidential only through `keepsEncrypted`.
400
+ */
401
+ writesEncrypted?: boolean;
402
+ /**
403
+ * A zip inside an uploaded zip is handed on as a file, not unpacked
404
+ * (#2118): for a receiver whose format holds zips as content (a takeout's
405
+ * files). Without it, the engine unpacks nested zips too.
406
+ */
407
+ innerZipsAsFiles?: boolean;
408
+ /**
409
+ * The items this receiver checks for itself whether it still holds
410
+ * (#2118, operator 2026-10-30): a takeout's page, by its uuid, and a
411
+ * takeout's file, by its bytes (#2171), so one deleted since an earlier
412
+ * import comes back when the same takeout is imported again. A file whose
413
+ * fingerprint the scope already took is then read again, and only these
414
+ * of its items go to `receive`, with `info.rechecked`; the rest are
415
+ * duplicates. When nothing new is stored it stays `unchanged`, unrecorded,
416
+ * as the fingerprint skip leaves it. Without this, the fingerprint decides.
417
+ */
418
+ rechecks?(item: IceItem): boolean;
419
+ /**
420
+ * The places within this receiver this context may import into (#2118):
421
+ * a user's own private stores. The receiver's own rule (D5). A receiver
422
+ * with targets takes a run only with one of them (`IceRunRequest.target`),
423
+ * which the engine checks against this before the run, binds into the
424
+ * ledger scope, and hands on in every item's `ReceiveInfo.target`.
425
+ */
426
+ targets?(ctx: ActorContext): Promise<IceTarget[]>;
427
+ /**
428
+ * Called once when a run into this receiver has read every file (#2118),
429
+ * with its report: a receiver that records the run as one act (a
430
+ * takeout's `store-import` audit event). Not called for a refused run. A
431
+ * throw is the run's failure.
432
+ */
433
+ runDone?(ctx: ActorContext, report: IceRunReport): Promise<void>;
297
434
  /** The original-file choices this receiver offers (D11). Without it: all three. */
298
435
  originals?: IceOriginalChoices;
299
436
  /**
@@ -321,6 +458,8 @@ export interface IceUpload {
321
458
  * drops it on a zip). Checked by the engine against the receiver's `names`.
322
459
  */
323
460
  givenName?: string;
461
+ /** The archive the engine unpacked this file from (#2118); set by the engine, never by a caller. */
462
+ archive?: IceArchiveView;
324
463
  }
325
464
  /**
326
465
  * A URL the engine fetches as the run's one file (D1, D10, #2109): through
@@ -378,6 +517,12 @@ export interface IceRunRequest {
378
517
  * client sent.
379
518
  */
380
519
  options?: Record<string, unknown>;
520
+ /**
521
+ * The place within the receiver (#2118), for a receiver with `targets`:
522
+ * one of those it gives the user the run is for. Required for such a
523
+ * receiver and refused for any other.
524
+ */
525
+ target?: string;
381
526
  }
382
527
  /** Per-file status (first slice). */
383
528
  export type IceFileStatus = 'imported' | 'partial' | 'unchanged' | 'duplicate' | 'noise' | 'unreadable' | 'refused' | 'failed';
@@ -410,6 +555,8 @@ export interface IceFileReport {
410
555
  export interface IceRunReport {
411
556
  runId: string;
412
557
  receiver: string;
558
+ /** The place within the receiver (#2118), when it has places. */
559
+ target?: string;
413
560
  scope: string;
414
561
  /** Who it ran as. */
415
562
  username: string;
@@ -461,6 +608,9 @@ export interface RecentImport {
461
608
  /** The receiver it went into. */
462
609
  receiver: string;
463
610
  receiverLabel?: string;
611
+ /** The place within it (#2118), when the receiver has places. */
612
+ target?: string;
613
+ targetLabel?: string;
464
614
  /** How many references the receiver gave (pages, records). */
465
615
  refs: number;
466
616
  /** Where the user can open the first of them (`linkFor`), when they may. */
@@ -482,3 +632,99 @@ export interface IceLedger {
482
632
  recordItems(scope: string, itemKeys: string[]): Promise<void>;
483
633
  files(scope: string): Promise<LedgerFileEntry[]>;
484
634
  }
635
+ /**
636
+ * The kind of the one item an export ends with: a file to download, with
637
+ * `bytes`, `mediaType` and the `name` it is saved under.
638
+ */
639
+ export declare const EXPORT_FILE_KIND = "file";
640
+ /** What an export source is told about the read it is asked for. */
641
+ export interface ExportReadInfo {
642
+ /** The shim that will make the file (`core.pdf`). */
643
+ format: string;
644
+ }
645
+ /**
646
+ * The owner's door for reading the thing being exported (#2120, D5): a page
647
+ * through PageManager, later a search result or an add-on's list. Export
648
+ * starts from the thing (operator, 2026-10-28): the source says what kind of
649
+ * items it yields, and only the `out` shims that can make a file from that
650
+ * kind are offered.
651
+ */
652
+ export interface IceExportSource {
653
+ name: string;
654
+ label?: string;
655
+ /** The kind of the items it yields, e.g. `ncm` for a page. */
656
+ yields: IceKind;
657
+ /**
658
+ * Would this context be allowed to export `ref`? Asked only to decide
659
+ * whether to offer the Export action: an affordance, which records nothing.
660
+ * The owner's existing read rule (D5).
661
+ */
662
+ mayExport(ref: string, ctx: ActorContext): Promise<boolean>;
663
+ /**
664
+ * Read `ref` for export as `ctx`: the door. It checks the owner's existing
665
+ * permission, records what its owner records, and answers null when the
666
+ * thing is not there or `ctx` may not export it, without telling the two apart.
667
+ */
668
+ read(ref: string, ctx: ActorContext, info: ExportReadInfo): Promise<IceItem[] | null>;
669
+ /**
670
+ * Record how an export of `ref` ended, as its owner records exports: once
671
+ * the run has ended, with the file made or not. Also called, as a failure,
672
+ * when what was read is not exported (too large, the site too busy).
673
+ * Optional; a record that fails is logged, never thrown.
674
+ */
675
+ recordExport?(ref: string, ctx: ActorContext, outcome: ExportOutcome): Promise<void>;
676
+ }
677
+ /** How an export ended, for its source's record (#2120). */
678
+ export interface ExportOutcome {
679
+ /** The shim that made, or was to make, the file. */
680
+ format: string;
681
+ runId: string;
682
+ /** The names of the items the source read. */
683
+ items: string[];
684
+ result: 'success' | 'failure';
685
+ /** Why it failed, as the user is told. */
686
+ reason?: string;
687
+ }
688
+ export interface RegisteredExportSource extends IceExportSource {
689
+ id: string;
690
+ owner: string;
691
+ }
692
+ /** A format the Export action offers: the shim that makes the file, and the shims before it. */
693
+ export interface IceExportFormat {
694
+ /** The shim that makes the file, `core.pdf`. */
695
+ id: string;
696
+ label: string;
697
+ /** Every shim the export runs through, in order, ending with `id`. */
698
+ chain: string[];
699
+ }
700
+ /** One export, as a caller asks for it. */
701
+ export interface IceExportRequest {
702
+ /** The export source's id, `core.pages`. */
703
+ source: string;
704
+ /** What to export, in the source's own terms: a page's name. */
705
+ ref: string;
706
+ /** The format: an id `exportFormatsFor` offered. */
707
+ format: string;
708
+ }
709
+ /** The file an export made, as its status shows it. */
710
+ export interface IceExportFile {
711
+ name: string;
712
+ mediaType: string;
713
+ size: number;
714
+ }
715
+ export interface IceExportReport {
716
+ runId: string;
717
+ source: string;
718
+ ref: string;
719
+ format: string;
720
+ chain: string[];
721
+ /** Who it ran as: the only user who sees it or downloads its file. */
722
+ username: string;
723
+ state: 'queued' | 'running' | 'done' | 'failed';
724
+ startedAt?: string;
725
+ finishedAt?: string;
726
+ error?: string;
727
+ file?: IceExportFile;
728
+ /** When the file is let go (`ngdpbase.export.engine.keep-minutes`). */
729
+ expiresAt?: string;
730
+ }
@@ -0,0 +1,19 @@
1
+ import type { IceUpload, IceVetResult, RegisteredShim } from './types.js';
2
+ /** A file whose format's shim failed while cleaning it: it is not kept, since it could not be checked. */
3
+ export declare class IceVetRefused extends Error {
4
+ readonly file: string;
5
+ readonly reason: string;
6
+ constructor(file: string, reason: string);
7
+ }
8
+ /**
9
+ * The shim that cleans this file: one whose media types include the type the
10
+ * bytes show; or, when the bytes show no type of their own (CSV, plain text),
11
+ * one whose extensions include the file's. Its "can I read this?" must agree.
12
+ */
13
+ export declare function cleanerFor(shims: readonly RegisteredShim[], name: string, mediaType: string, bytesShowType: boolean): RegisteredShim | null;
14
+ /**
15
+ * Vet one file (#2149). `shims` are the enabled ones. Throws
16
+ * {@link IceVetRefused} when the cleaning shim fails: a file that could not
17
+ * be checked is not kept.
18
+ */
19
+ export declare function vetFile(shims: readonly RegisteredShim[], upload: IceUpload): Promise<IceVetResult>;