@jwilleke/ngdpbase-types 5.42.0 → 5.44.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 (74) hide show
  1. package/lib/src/context/ApiContext.d.ts +10 -3
  2. package/lib/src/context/ApiContext.js +37 -4
  3. package/lib/src/context/BaseContext.d.ts +20 -20
  4. package/lib/src/context/BaseContext.js +41 -24
  5. package/lib/src/context/JobContext.d.ts +31 -0
  6. package/lib/src/context/JobContext.js +34 -1
  7. package/lib/src/ice/runExport.d.ts +8 -3
  8. package/lib/src/ice/searchExportSource.d.ts +44 -0
  9. package/lib/src/ice/types.d.ts +6 -0
  10. package/lib/src/managers/AddonsManager.d.ts +0 -28
  11. package/lib/src/managers/AgentTokenManager.d.ts +8 -0
  12. package/lib/src/managers/AttachmentManager.d.ts +11 -0
  13. package/lib/src/managers/AuditManager.d.ts +0 -1
  14. package/lib/src/managers/AuthManager.d.ts +38 -0
  15. package/lib/src/managers/AuthManager.js +84 -12
  16. package/lib/src/managers/BackupManager.d.ts +9 -2
  17. package/lib/src/managers/BackupManager.js +42 -19
  18. package/lib/src/managers/BaseManager.d.ts +17 -3
  19. package/lib/src/managers/BaseManager.js +12 -8
  20. package/lib/src/managers/ConfigurationManager.d.ts +28 -0
  21. package/lib/src/managers/ConsentManager.d.ts +1 -1
  22. package/lib/src/managers/DatabaseManager.js +17 -8
  23. package/lib/src/managers/EmailManager.d.ts +1 -1
  24. package/lib/src/managers/ExportManager.d.ts +1 -44
  25. package/lib/src/managers/ExportManager.js +3 -115
  26. package/lib/src/managers/ImportManager.d.ts +7 -0
  27. package/lib/src/managers/PageManager.d.ts +194 -23
  28. package/lib/src/managers/PolicyEvaluator.d.ts +182 -24
  29. package/lib/src/managers/ShareManager.d.ts +3 -2
  30. package/lib/src/managers/ThemeManager.d.ts +20 -1
  31. package/lib/src/managers/UserManager.d.ts +19 -5
  32. package/lib/src/managers/ValidationManager.d.ts +3 -1
  33. package/lib/src/providers/AgentTokenAuthProvider.js +1 -1
  34. package/lib/src/providers/BaseAuthProvider.d.ts +2 -0
  35. package/lib/src/providers/FileLoggingProvider.d.ts +12 -1
  36. package/lib/src/providers/FileLoggingProvider.js +37 -4
  37. package/lib/src/security/PolicyDecisionPoint.d.ts +107 -4
  38. package/lib/src/security/PolicyInformationPoint.d.ts +113 -7
  39. package/lib/src/security/ownedRecords.d.ts +69 -0
  40. package/lib/src/security/permissions.generated.d.ts +8 -0
  41. package/lib/src/security/signedOutWrites.d.ts +69 -0
  42. package/lib/src/security/signedOutWrites.js +146 -0
  43. package/lib/src/types/Page.d.ts +0 -7
  44. package/lib/src/types/Policy.d.ts +65 -8
  45. package/lib/src/types/Policy.js +6 -0
  46. package/lib/src/types/Provider.d.ts +12 -0
  47. package/lib/src/types/User.d.ts +6 -0
  48. package/lib/src/utils/addonConfigLayer.d.ts +6 -3
  49. package/lib/src/utils/addonConfigLayer.js +61 -3
  50. package/lib/src/utils/addonHost.d.ts +25 -0
  51. package/lib/src/utils/addonHost.js +29 -1
  52. package/lib/src/utils/addonPageSync.d.ts +0 -12
  53. package/lib/src/utils/auditEventNames.generated.d.ts +2 -0
  54. package/lib/src/utils/auditEventNames.generated.js +2 -0
  55. package/lib/src/utils/auditEvents.d.ts +2 -0
  56. package/lib/src/utils/auditEvents.js +3 -1
  57. package/lib/src/utils/availability.d.ts +15 -0
  58. package/lib/src/utils/availability.js +88 -0
  59. package/lib/src/utils/frontmatterAccess.d.ts +15 -24
  60. package/lib/src/utils/legacyStoreKeys.d.ts +16 -0
  61. package/lib/src/utils/pageTitleConflict.d.ts +32 -0
  62. package/lib/src/utils/pageWritePolicies.d.ts +74 -0
  63. package/lib/src/utils/permissionCatalog.d.ts +2 -0
  64. package/lib/src/utils/permissionCatalog.js +19 -0
  65. package/lib/src/utils/plainAttention.d.ts +19 -0
  66. package/lib/src/utils/plainAttention.js +18 -0
  67. package/lib/src/utils/policyAttributes.d.ts +232 -0
  68. package/lib/src/utils/policyAttributes.js +404 -0
  69. package/lib/src/utils/privateStoreAccess.d.ts +24 -4
  70. package/lib/src/utils/privateStoreAccess.js +19 -4
  71. package/lib/src/utils/roleGrants.d.ts +1 -1
  72. package/lib/src/utils/username.d.ts +13 -2
  73. package/lib/src/utils/username.js +18 -3
  74. package/package.json +7 -7
@@ -10,17 +10,7 @@ export type { PdfOptions, PdfMargin, PdfPageSize } from '../export/pdfEngine.js'
10
10
  * when something asked for a PDF and could not have one; cleared by the next
11
11
  * render that works.
12
12
  */
13
- export declare const PDF_UNAVAILABLE_REASON = "PDF export unavailable: choose a PDF provider (local-chrome or chrome-service) in ngdpbase.export.pdf.provider, or fix the one chosen (the server log says why)";
14
- /**
15
- * Export file metadata interface
16
- */
17
- export interface ExportFileInfo {
18
- filename: string;
19
- path: string;
20
- size: number;
21
- created: Date;
22
- modified: Date;
23
- }
13
+ export declare const PDF_UNAVAILABLE_REASON = "PDFs can't be made right now. Choose how PDFs are made in the settings, or fix the one already chosen.";
24
14
  /**
25
15
  * Export configuration interface
26
16
  */
@@ -146,13 +136,6 @@ declare class ExportManager extends BaseManager {
146
136
  * @returns {Promise<void>}
147
137
  */
148
138
  initialize(config?: ExportConfig): Promise<void>;
149
- /**
150
- * Export a single page to HTML
151
- * @param {string} pageName - Page name to export
152
- * @param {ExportUser|null} user - User object for locale-aware formatting
153
- * @returns {Promise<string>} HTML content
154
- */
155
- exportPageToHtml(pageName: string, ctx: ActorContext, user?: ExportUser | null): Promise<string>;
156
139
  /**
157
140
  * One page as a complete HTML document: its content rendered, with the
158
141
  * export's styling and metadata footer. It reads nothing: the caller has
@@ -171,32 +154,6 @@ declare class ExportManager extends BaseManager {
171
154
  * @returns {Promise<string>} Combined HTML content
172
155
  */
173
156
  exportPagesToHtml(pageNames: string[], ctx: ActorContext, user?: ExportUser | null): Promise<string>;
174
- /**
175
- * Export page(s) to markdown
176
- * @param {string|string[]} pageNames - Single page name or array of page names
177
- * @param {ExportUser|null} user - User object for locale-aware formatting
178
- * @returns {Promise<string>} Markdown content
179
- */
180
- exportToMarkdown(pageNames: string | string[], ctx: ActorContext, user?: ExportUser | null): Promise<string>;
181
- /**
182
- * Save export to file
183
- * @param {string} content - Content to save
184
- * @param {string} filename - Filename
185
- * @param {string} format - File format
186
- * @returns {Promise<string>} File path
187
- */
188
- saveExport(content: string, filename: string, format: string): Promise<string>;
189
- /**
190
- * Get list of available exports
191
- * @returns {Promise<ExportFileInfo[]>} List of export files
192
- */
193
- getExports(): Promise<ExportFileInfo[]>;
194
- /**
195
- * Delete an export file
196
- * @param {string} filename - Export filename
197
- * @returns {Promise<void>}
198
- */
199
- deleteExport(filename: string): Promise<void>;
200
157
  /**
201
158
  * Get formatted timestamp using user's locale
202
159
  * @param {ExportUser|null} user - User object (optional)
@@ -1,6 +1,5 @@
1
1
  import BaseManager from './BaseManager.js';
2
2
  import fs from 'fs/promises';
3
- import path from 'path';
4
3
  import LocaleUtils from '../utils/LocaleUtils.js';
5
4
  import logger from '../utils/logger.js';
6
5
  import { actorOf } from '../context/ActorContext.js';
@@ -15,7 +14,7 @@ export { PdfUnavailableError };
15
14
  * when something asked for a PDF and could not have one; cleared by the next
16
15
  * render that works.
17
16
  */
18
- export const PDF_UNAVAILABLE_REASON = 'PDF export unavailable: choose a PDF provider (local-chrome or chrome-service) in ngdpbase.export.pdf.provider, or fix the one chosen (the server log says why)';
17
+ export const PDF_UNAVAILABLE_REASON = "PDFs can't be made right now. Choose how PDFs are made in the settings, or fix the one already chosen.";
19
18
  /** Shipped defaults of the `ngdpbase.export.pdf.*` keys. */
20
19
  const PDF_DEFAULTS = { provider: 'none', pageSize: 'Letter', timeoutMs: 30_000, maxConcurrent: 2, serviceMaxBytes: 52_428_800 };
21
20
  /**
@@ -34,7 +33,7 @@ const PDF_DEFAULTS = { provider: 'none', pageSize: 'Letter', timeoutMs: 30_000,
34
33
  *
35
34
  * @example
36
35
  * const exportManager = engine.getManager('ExportManager');
37
- * const html = await exportManager.exportPageToHtml('Main');
36
+ * const csv = exportManager.toCsv(['name'], [['Main']]);
38
37
  */
39
38
  /** A text cell starting with one of these runs as a spreadsheet formula. */
40
39
  const CSV_FORMULA_START = /^[=+\-@\t\r]/;
@@ -229,7 +228,7 @@ class ExportManager extends BaseManager {
229
228
  this.exportDirectory = config.exportDirectory
230
229
  || this.engine.getManager('ConfigurationManager')?.getResolvedDataPath('ngdpbase.directories.exports', '${FAST_STORAGE}/exports')
231
230
  || './exports';
232
- const preflight = this.preflightConfiguredPath('exportDirectory', this.exportDirectory);
231
+ const preflight = this.preflightConfiguredPath('ngdpbase.directories.exports', this.exportDirectory);
233
232
  if (!preflight.ok) {
234
233
  this.exportDirectory = '';
235
234
  logger.info('ExportManager initialized (degraded — exports disabled)');
@@ -240,24 +239,6 @@ class ExportManager extends BaseManager {
240
239
  const pdfProviderName = this.engine.getManager('ConfigurationManager')?.getProperty('ngdpbase.export.pdf.provider', PDF_DEFAULTS.provider);
241
240
  logger.info(`ExportManager initialized (PDF provider: ${pdfProviderName ?? PDF_DEFAULTS.provider})`);
242
241
  }
243
- /**
244
- * Export a single page to HTML
245
- * @param {string} pageName - Page name to export
246
- * @param {ExportUser|null} user - User object for locale-aware formatting
247
- * @returns {Promise<string>} HTML content
248
- */
249
- async exportPageToHtml(pageName, ctx, user = null) {
250
- const pageManager = this.engine.getManager('PageManager');
251
- const renderingManager = this.engine.getManager('RenderingManager');
252
- if (!pageManager || !renderingManager) {
253
- throw new Error('Required managers not available');
254
- }
255
- const page = (await pageManager.getPage(pageName, ctx));
256
- if (!page) {
257
- throw new Error(`Page '${pageName}' not found`);
258
- }
259
- return this.renderPageDocument(pageName, page, user);
260
- }
261
242
  /**
262
243
  * One page as a complete HTML document: its content rendered, with the
263
244
  * export's styling and metadata footer. It reads nothing: the caller has
@@ -480,99 +461,6 @@ class ExportManager extends BaseManager {
480
461
  </html>`;
481
462
  return html;
482
463
  }
483
- /**
484
- * Export page(s) to markdown
485
- * @param {string|string[]} pageNames - Single page name or array of page names
486
- * @param {ExportUser|null} user - User object for locale-aware formatting
487
- * @returns {Promise<string>} Markdown content
488
- */
489
- async exportToMarkdown(pageNames, ctx, user = null) {
490
- const pageManager = this.engine.getManager('PageManager');
491
- if (!pageManager) {
492
- throw new Error('PageManager not available');
493
- }
494
- const names = Array.isArray(pageNames) ? pageNames : [pageNames];
495
- let markdown = '';
496
- if (names.length > 1) {
497
- markdown += '# ngdpbase Export\n\n';
498
- markdown += `*Exported on ${this.getFormattedTimestamp(user)}*\n\n`;
499
- markdown += '## Table of Contents\n\n';
500
- for (const pageName of names) {
501
- markdown += `- [${pageName}](#${pageName.toLowerCase().replace(/[^a-z0-9]/g, '-')})\n`;
502
- }
503
- markdown += '\n---\n\n';
504
- }
505
- for (const pageName of names) {
506
- const page = (await pageManager.getPage(pageName, ctx));
507
- if (page) {
508
- if (names.length > 1) {
509
- markdown += `# ${pageName}\n\n`;
510
- }
511
- markdown += page.content;
512
- if (names.length > 1) {
513
- markdown += '\n\n---\n\n';
514
- }
515
- }
516
- }
517
- return markdown;
518
- }
519
- /**
520
- * Save export to file
521
- * @param {string} content - Content to save
522
- * @param {string} filename - Filename
523
- * @param {string} format - File format
524
- * @returns {Promise<string>} File path
525
- */
526
- async saveExport(content, filename, format) {
527
- const sanitizedFilename = filename.replace(/[^a-zA-Z0-9\-_]/g, '-');
528
- const timestamp = new Date().toISOString().slice(0, 10);
529
- const fullFilename = `${sanitizedFilename}_${timestamp}.${format}`;
530
- const filePath = path.join(this.exportDirectory, fullFilename);
531
- await fs.writeFile(filePath, content, 'utf8');
532
- logger.info(`Exported to: ${fullFilename}`);
533
- return filePath;
534
- }
535
- /**
536
- * Get list of available exports
537
- * @returns {Promise<ExportFileInfo[]>} List of export files
538
- */
539
- async getExports() {
540
- try {
541
- const files = await fs.readdir(this.exportDirectory);
542
- const exports = [];
543
- for (const file of files) {
544
- const filePath = path.join(this.exportDirectory, file);
545
- const stats = await fs.stat(filePath);
546
- exports.push({
547
- filename: file,
548
- path: filePath,
549
- size: stats.size,
550
- created: stats.birthtime,
551
- modified: stats.mtime
552
- });
553
- }
554
- return exports.sort((a, b) => b.created.getTime() - a.created.getTime());
555
- }
556
- catch (err) {
557
- logger.error('Error getting exports:', err);
558
- return [];
559
- }
560
- }
561
- /**
562
- * Delete an export file
563
- * @param {string} filename - Export filename
564
- * @returns {Promise<void>}
565
- */
566
- async deleteExport(filename) {
567
- // Only a plain file name inside the export folder: never a path that
568
- // reaches another folder.
569
- if (!filename || filename !== path.basename(filename) || filename === '.' || filename === '..') {
570
- throw new Error(`Not an export file name: ${filename}`);
571
- }
572
- const filePath = path.join(this.exportDirectory, filename);
573
- await fs.unlink(filePath);
574
- logger.info(`Deleted export: ${filename}`);
575
- }
576
464
  /**
577
465
  * Get formatted timestamp using user's locale
578
466
  * @param {ExportUser|null} user - User object (optional)
@@ -633,6 +633,13 @@ declare class ImportManager extends BaseManager {
633
633
  * is permitted, and a refusal to someone who holds it is step-up's.
634
634
  */
635
635
  private mayExportPage;
636
+ /**
637
+ * `core.search` as an export source: a search's pages and files as one zip
638
+ * ({@link searchExportSource}). Like a page, a search is offered to a user
639
+ * who holds `page-export`, and read as the user who asked, each page and
640
+ * file by its own read check. Recorded as one `page-export` of the search.
641
+ */
642
+ private searchExportSource;
636
643
  /**
637
644
  * `core.pages` as an export source (#2120, D5: pages export checks
638
645
  * `page-export`). A page is offered for export when its reader may see it
@@ -97,6 +97,26 @@ export interface PageSaveOptions {
97
97
  * doc says so.
98
98
  */
99
99
  preserveEditor?: boolean;
100
+ /**
101
+ * The user a NEW page is made for, by a system job (#1815): they become its
102
+ * author and editor, where a create otherwise takes both from the acting
103
+ * context. For the pages the system makes on a user's behalf — the profile
104
+ * page at sign-up, the new profile page of a rename — so the job's
105
+ * `page-create`, granted in policy, decides the write, and the page is
106
+ * still the user's (author-lock leaves it to them). Refused from any context
107
+ * but the system principal's job, and refused for a page that already
108
+ * exists (`PageWriteRefusedError`, `page-create`): the job never edits
109
+ * that page instead.
110
+ */
111
+ createdFor?: string;
112
+ /**
113
+ * The author of a page an add-on pushes (#2255): `addon:<slug>`. A new copy
114
+ * is authored by the add-on, and a copy seeded before #2255, authored by
115
+ * the system principal, is re-stamped with it. Taken only from the system
116
+ * principal's job and only in the `addon:<slug>` form; ignored otherwise.
117
+ * Any other author is kept, as on every edit.
118
+ */
119
+ shippedAuthor?: string;
100
120
  /**
101
121
  * Request-level detail the manager cannot see, for the audit record (#1121).
102
122
  *
@@ -199,11 +219,25 @@ export declare class PageAudienceRefusedError extends Error {
199
219
  readonly pageName: string;
200
220
  constructor(pageName: string);
201
221
  }
222
+ /** The permission a page write is decided on (#1815). */
223
+ export type PageWriteAction = 'page-create' | 'page-edit' | 'page-delete' | 'page-rename';
224
+ /**
225
+ * A write refused by the write door (#1815): the context may not create,
226
+ * edit or delete this page, decided by policy, the page's own rules and the
227
+ * vault container rule. Nothing was written. Routes answer it with 403. The
228
+ * message names no page, so a private title never reaches a log (#2178).
229
+ */
230
+ export declare class PageWriteRefusedError extends Error {
231
+ readonly pageName: string;
232
+ readonly action: PageWriteAction;
233
+ constructor(pageName: string, action: PageWriteAction);
234
+ }
202
235
  export declare class PageContentValidationError extends Error {
203
236
  readonly validationErrors: FilterValidationError[];
204
237
  constructor(pageName: string, validationErrors: FilterValidationError[]);
205
238
  }
206
239
  import { type PageAction } from '../utils/pageUrl.js';
240
+ import { type VaultSealState } from '../utils/privateStoreMeta.js';
207
241
  /** One vault page with data outside its vault (#1842), as the repair actions offer it. */
208
242
  export interface VaultRepairItem {
209
243
  owner: string;
@@ -261,6 +295,9 @@ export type VaultRepairResult = ({
261
295
  /** The move of existing captures into the capture vault (#1505), recorded per vault it moved them out of. */
262
296
  /** The refusal when someone without `admin-system` names a system-category (#1503, operator 2026-10-29). */
263
297
  export declare const SYSTEM_CATEGORY_ADMIN_ONLY = "Only administrators can set a page's system category";
298
+ /** The system-category of a page in a shared folder (#2255): given when it is created there, by nothing else. */
299
+ export declare const SHARED_CATEGORY = "shared";
300
+ export declare const SHARED_CATEGORY_REFUSAL = "A page is in the shared system category only when it is created in a shared folder";
264
301
  /** What a write may give a page as its system-category ({@link PageManager.systemCategoryForWrite}). */
265
302
  export type SystemCategoryForWrite = {
266
303
  ok: true;
@@ -327,6 +364,11 @@ export interface ShippedPageSource {
327
364
  exclude?: (data: Record<string, unknown>) => string | undefined;
328
365
  /** Metadata the source adds to every page it writes (e.g. `addon`, a default category) */
329
366
  extraMetadata?: (data: Record<string, unknown>) => Record<string, unknown>;
367
+ /**
368
+ * The author of the pages it writes, for an add-on's (#2255): `addon:<slug>`
369
+ * (`SavePageOptions.shippedAuthor`). Absent, they are the system principal's.
370
+ */
371
+ author?: string;
330
372
  /**
331
373
  * Called by the boot seed for a source page this site already holds, with the
332
374
  * live page and the parsed source — the addon seed's metadata tidy-ups and
@@ -622,8 +664,10 @@ declare class PageManager extends BaseManager implements CatalogSource {
622
664
  private readShippedSystemCategories;
623
665
  /**
624
666
  * Which system-category a write by `ctx` gives a page (#1503, operator
625
- * 2026-10-29): only an administrator (`admin-system`, asked of the PDP) sets
626
- * or changes one. Anyone else's new page takes `fallback` (else the default
667
+ * 2026-10-29): only a holder of `page-categorize` (#2255; shipped to admin)
668
+ * sets or changes one. `shared` is set by nobody: a page created in a shared
669
+ * folder gets it at the write door (`savePage`), and a request naming it for
670
+ * any other page is refused, `page-categorize` or not. Anyone else's new page takes `fallback` (else the default
627
671
  * system-category) and an existing page keeps its own (`current`); a request
628
672
  * naming any other is refused, never rewritten. An administrator's request
629
673
  * comes back as given (`undefined` when it names none), for the caller's own
@@ -644,16 +688,10 @@ declare class PageManager extends BaseManager implements CatalogSource {
644
688
  * taken for a shipped one. Shipped pages are admin-only and never private.
645
689
  */
646
690
  isShippedPageCopy(uuid: string): Promise<boolean>;
647
- /**
648
- * One-time access backfill for a live required page (#1411): a page with no
649
- * `access` gets the default for its category, as a metadata-only save that
650
- * keeps `lastModified`. A page that has any `access` — an operator's choice —
651
- * is left alone.
652
- */
653
- private backfillShippedPageAccess;
654
691
  /**
655
692
  * The source for the pages an addon ships (#1406). Pages it writes carry
656
- * `addon` and, when the source names none, the `addon` category (#931).
693
+ * `addon`, the add-on as author (`addon:<slug>`, #2255) and, when the source
694
+ * names none, the `addon` category (#931).
657
695
  *
658
696
  * @param addonName - The addon's name
659
697
  * @param pagesDir - The addon's `pages/` folder
@@ -926,6 +964,82 @@ declare class PageManager extends BaseManager implements CatalogSource {
926
964
  * @throws when `ctx` may not file into the page's folder
927
965
  */
928
966
  private assertMayFileInSharedFolder;
967
+ /**
968
+ * May `ctx` edit or delete this existing page (#1815)? The page decision
969
+ * every route asks (`canUserAccessPage`), with the page's metadata, so the
970
+ * delegation ceilings, the vault container rule, author-lock, the page's own
971
+ * rules and policy all count, in that one evaluator. `record` false asks it
972
+ * as an affordance, which records nothing (#1668). Without a PIP nothing is
973
+ * writable.
974
+ */
975
+ private mayChangeExisting;
976
+ /**
977
+ * May `ctx` create a page at `destination` (#1815)? In a vault, the
978
+ * container rule and then `page-create` with the vault (`permitsInVault`;
979
+ * a shared folder by its own permission). A public page, `page-create`
980
+ * asked of the PDP about the page (#2255: the PDP reads its attributes from
981
+ * the PIP). The same rule as `hasPermissionOn`.
982
+ *
983
+ * @returns `permit`, and the `policyIds` of the `page-create` decision when
984
+ * one was made (#2272); a vault page is decided by the container rule and
985
+ * `permitsInVault`, which name none.
986
+ */
987
+ private decideCreateAt;
988
+ /**
989
+ * Where a save puts the page (#1815, decision 7): the private name the
990
+ * provider will store it under, or its public title. Read from the page's
991
+ * own `private` / `store` frontmatter, the rule the provider's placement
992
+ * follows: a private name stays private unless the save says
993
+ * `private: false`; a public title goes into a vault when it says
994
+ * `private: true`, the owner's and the store it names, else the vault the
995
+ * page is in now, else the default vault.
996
+ *
997
+ * @param owner - The username whose vault a public title would go into: the
998
+ * page's author (for a new page, whoever it is made by or for)
999
+ */
1000
+ private writeDestination;
1001
+ /** The container a page name is in: its vault, or the public space. */
1002
+ private static containerOf;
1003
+ /**
1004
+ * The write door's decision for a save (#1815, operator 2026-11-04). An
1005
+ * existing page is edited only by a context the page decision lets edit it
1006
+ * ({@link mayChangeExisting}); a page arriving in a container it is not in
1007
+ * now — a new page, or a move into or out of a vault — is also created
1008
+ * there ({@link decideCreateAt}). Whatever route the write came by: the route's
1009
+ * own check is an early refusal, this is the one that counts.
1010
+ *
1011
+ * @throws PageWriteRefusedError when either is refused; nothing is written
1012
+ */
1013
+ private assertMaySave;
1014
+ /**
1015
+ * Record a refused `page-create` as `authorization-deny` (#1815, security
1016
+ * review L1). The edit and delete refusals are recorded by the page
1017
+ * decision (`canUserAccessPage`); a create is decided by the PDP, which
1018
+ * records nothing, so the door records it here: core records every refusal.
1019
+ * Standard tier, `continue` on failure: the refusal stands either way.
1020
+ *
1021
+ * @param policyIds - The policies of the `page-create` decision that refused
1022
+ * (#2272). Only a refusal that decision made passes any; one no policy
1023
+ * decided (`createdFor` misuse) leaves the field off the record.
1024
+ * @param conditionNotMet - Allows of that decision that matched but whose condition was not met (#2288).
1025
+ */
1026
+ private recordCreateRefusal;
1027
+ /** `shippedAuthor` when the system principal's job gives it in the `addon:<slug>` form (#2255); otherwise nothing. */
1028
+ private shippedAuthorFor;
1029
+ /** Is `ctx` a job of the system principal (#631) — the only context that may make a page for someone else (#1815)? */
1030
+ private isSystemJob;
1031
+ /** The username a page's stored author names (#2097), or null. */
1032
+ private authorUsername;
1033
+ /**
1034
+ * Would `ctx` be allowed to edit this page — asked only to decide what to
1035
+ * offer or report, never before writing it (#1815). The same decision as
1036
+ * the write door, as an affordance: it records nothing (#1668). Saving still
1037
+ * asks the door.
1038
+ *
1039
+ * @param identifier - Page UUID, title, slug or vault name
1040
+ * @param ctx - Whose question this is — never defaulted
1041
+ */
1042
+ mayEditPage(identifier: string, ctx: ActorContext): Promise<boolean>;
929
1043
  /**
930
1044
  * Admin-override save (#689): the admin "Edit raw" textarea, split into
931
1045
  * frontmatter and body and written through the door (#1462).
@@ -975,7 +1089,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
975
1089
  * What a page stores for the user `name` (#2097): `IdentityManager.storedReferenceOf`.
976
1090
  */
977
1091
  private referenceTo;
978
- /** `audience` and each `access[action]` of a save, user entries as {@link referenceTo} stores them (#2097). */
1092
+ /** The `audience` of a save, user entries as {@link referenceTo} stores them (#2097). */
979
1093
  private convertAccessListsForSave;
980
1094
  /**
981
1095
  * The frontmatter a save writes, from the frontmatter it was given (#1462).
@@ -994,6 +1108,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
994
1108
  * @param actingUser - Who is writing this revision, from the context (#1164)
995
1109
  * @param viaToken - The agent token this write came through (#946), if any
996
1110
  * @param keepEditor - Leave the stored `editor` alone (`preserveEditor`, #1457)
1111
+ * @param shippedAuthor - An add-on page's author (`shippedAuthor`, #2255), already checked
997
1112
  * @returns The frontmatter to hand the provider
998
1113
  */
999
1114
  private normalizeSaveMetadata;
@@ -1009,7 +1124,9 @@ declare class PageManager extends BaseManager implements CatalogSource {
1009
1124
  *
1010
1125
  * `ctx` is mandatory and positional: the save acts as the caller's subject
1011
1126
  * (#1179), a private-store write reaches that session's keys through it
1012
- * (#1382), and the audit record names it. A caller forwards the context it
1127
+ * (#1382), and the audit record names it. The write is decided here, for
1128
+ * that context, before anything is written (#1815): `page-edit` on the page
1129
+ * as it is, `page-create` where it is going when that is somewhere new. A caller forwards the context it
1013
1130
  * was given — a request's subject, or the job's for boot and scheduled
1014
1131
  * work — and never invents one.
1015
1132
  *
@@ -1020,6 +1137,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
1020
1137
  * @param options - Validation and conflict-check opt-outs, `rawFrontmatter` (#689),
1021
1138
  * `preserveLastModified`, `preserveEditor` (#1457), audit enrichment
1022
1139
  * @returns Where the page landed, and what it was called before
1140
+ * @throws PageWriteRefusedError when `ctx` may not make this write
1023
1141
  *
1024
1142
  * @example
1025
1143
  * await pageManager.savePage('New Page', '# Hello World', { 'user-keywords': ['tutorial'] }, req.userContext);
@@ -1108,8 +1226,37 @@ declare class PageManager extends BaseManager implements CatalogSource {
1108
1226
  title: string;
1109
1227
  uuid: string;
1110
1228
  }>>;
1229
+ /**
1230
+ * Every public page stamped with one system-category (#2299), by the name
1231
+ * `mayShowPage` takes. Vault pages are not listed: they are their owners', not
1232
+ * an administrator's to be told about. Says nothing about who may see a page;
1233
+ * the caller asks `mayShowPage` as the reader.
1234
+ *
1235
+ * @param category - The category's key or label, any case
1236
+ */
1237
+ listCategoryPages(category: string): Promise<string[]>;
1111
1238
  /** The store ids the requester has of their own (#1387). */
1112
1239
  listOwnStoreIds(ctx: ActorContext): Promise<string[]>;
1240
+ /**
1241
+ * The requester's own vaults, each with its pages and whether it is
1242
+ * encrypted (#2245), for `/my/vaults`.
1243
+ *
1244
+ * `state` is the vault's {@link VaultSealState} (#1834): `sealed` is
1245
+ * encrypted, `plain` is not, and `unopened` or `mismatch` is a vault whose
1246
+ * kind says it must be encrypted while its copy is not. `readable` is false
1247
+ * for a vault this context cannot open, such as a sealed vault still
1248
+ * locked: its pages are then unknown, not none.
1249
+ */
1250
+ listOwnVaults(ctx: ActorContext): Promise<Array<{
1251
+ id: string;
1252
+ state: VaultSealState;
1253
+ readable: boolean;
1254
+ pages: Array<{
1255
+ name: string;
1256
+ title: string;
1257
+ uuid: string;
1258
+ }>;
1259
+ }>>;
1113
1260
  listOwnDeletedPrivatePages(ctx: ActorContext): Promise<StoreDeletedEntry[]>;
1114
1261
  /**
1115
1262
  * Bring one of the requester's private pages back from its store's trash
@@ -1233,14 +1380,25 @@ declare class PageManager extends BaseManager implements CatalogSource {
1233
1380
  * and the context names who put it there. Afterwards the page is taken out
1234
1381
  * of every shared index, here and nowhere else (#1462).
1235
1382
  *
1383
+ * The write door decides `page-delete` on the page first (#1815), and the
1384
+ * delete is recorded as `page-delete` before it happens.
1385
+ *
1236
1386
  * @param identifier - Page UUID, title, or slug
1237
1387
  * @param ctx - Who is acting (#1179)
1388
+ * @param options - `audit.skip` when the caller already recorded this
1389
+ * delete; `audit.ipAddress`, the request's address, for the record
1238
1390
  * @returns True if deleted, false if not found
1391
+ * @throws PageWriteRefusedError when `ctx` may not delete the page
1239
1392
  *
1240
1393
  * @example
1241
1394
  * const deleted = await pageManager.deletePage('Old Page', req.userContext);
1242
1395
  */
1243
- deletePage(identifier: string, ctx: ActorContext): Promise<boolean>;
1396
+ deletePage(identifier: string, ctx: ActorContext, options?: {
1397
+ audit?: {
1398
+ skip?: boolean;
1399
+ ipAddress?: string;
1400
+ };
1401
+ }): Promise<boolean>;
1244
1402
  /**
1245
1403
  * Delete the pages a test made (#2008): every shared page carrying the
1246
1404
  * `test-artifact` system keyword (#1355), and never a page without it.
@@ -1253,7 +1411,8 @@ declare class PageManager extends BaseManager implements CatalogSource {
1253
1411
  * follow. One page's failure never stops the rest; it is reported.
1254
1412
  *
1255
1413
  * The caller decides who may ask: the sweep route asks `admin-system`, and
1256
- * an account delete has already been decided as `user-delete`. Private
1414
+ * an account delete has already been decided as `user-delete` and sends a
1415
+ * system job (#1815). Each delete is still decided by `deletePage`. Private
1257
1416
  * vault pages are not in the shared index, so they are not reached here.
1258
1417
  *
1259
1418
  * @param ctx - Who is acting; named in every record
@@ -1502,7 +1661,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
1502
1661
  private warnUnmatchableAddonAudience;
1503
1662
  /**
1504
1663
  * At startup, once add-ons have loaded (#1964): warn about every page whose
1505
- * `audience` or `access` names an add-on that can match nobody — unknown,
1664
+ * `audience` names an add-on that can match nobody — unknown,
1506
1665
  * not per-person, or not loaded.
1507
1666
  *
1508
1667
  * @returns Each page and slug warned about
@@ -1617,8 +1776,10 @@ declare class PageManager extends BaseManager implements CatalogSource {
1617
1776
  *
1618
1777
  * `pages/vaults/{user}/` and every store below it is owned by that user.
1619
1778
  * Nobody else acts in it unless the owner delegated (a share the owner
1620
- * issued, once the store's Share switch exists — #1388). No role reaches in,
1621
- * admin included (security-posture P2: no `hasRole` as an allow).
1779
+ * issued, once the store's Share switch exists — #1388). No role reaches in
1780
+ * (security-posture P2: no `hasRole` as an allow); at a door, a holder of
1781
+ * `admin-system` may enter an unencrypted vault when
1782
+ * `ngdpbase.vaults.admin-access` is on (#2255), recorded each time.
1622
1783
  *
1623
1784
  * The owner is the one in the page's name (#1456), not frontmatter
1624
1785
  * `author`, so reassigning `author` cannot move ownership (#711).
@@ -1629,7 +1790,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
1629
1790
  * - `false` — a private name, and the caller is neither — whether or not
1630
1791
  * the page exists
1631
1792
  */
1632
- checkPrivatePageAccess(wikiContext: WikiContext, pageNameOrUuid: string): Promise<boolean | null>;
1793
+ checkPrivatePageAccess(wikiContext: WikiContext, pageNameOrUuid: string, adminEntry?: 'record' | 'quiet' | 'none'): Promise<boolean | null>;
1633
1794
  /**
1634
1795
  * Owner and store of a private page, or `null` when the page is not private
1635
1796
  * or does not exist (#1398). The owner owns the page and every attachment
@@ -1686,6 +1847,15 @@ declare class PageManager extends BaseManager implements CatalogSource {
1686
1847
  * Best-effort: a failure is logged and never blocks start-up. An encrypted
1687
1848
  * store is not readable here and is not touched — it migrates at its
1688
1849
  * owner's unlock, through {@link migratePrivateLinks}.
1850
+ *
1851
+ * #1815 (operator, 2026-11-04, decision 2): the public pages are the system
1852
+ * principal's work; each user's vaults are a job on behalf of that user
1853
+ * ({@link jobContextForOwner}), through the write door like any write. The
1854
+ * vault container rule admits a job carrying its owner, and the owner's
1855
+ * rights in their own vaults come from policy, resolved when each write is
1856
+ * decided. The system principal is let into no vault. A shared folder is
1857
+ * not a user's and opens to no boot job (its key never reaches one), so it
1858
+ * is not visited.
1689
1859
  */
1690
1860
  migratePrivateLinksAtBoot(): Promise<void>;
1691
1861
  /**
@@ -1799,8 +1969,9 @@ declare class PageManager extends BaseManager implements CatalogSource {
1799
1969
  * keeps its `lastModified` and `editor`, since nobody edited it. The audit
1800
1970
  * record is a rename, from the old path to the new.
1801
1971
  *
1802
- * Access is not decided here, as for every save: the caller has already
1803
- * been let into the page. A page is never moved out of an encrypted vault
1972
+ * Access is decided by the save it makes, as for every write (#1815): an
1973
+ * edit of the page where it is and a create in the vault it goes to. A
1974
+ * page is never moved out of an encrypted vault
1804
1975
  * into one that is not, since that would write it in the clear.
1805
1976
  *
1806
1977
  * @param pageName - The page's private name, `vaults/{owner}/{vault}/{title}`
@@ -1823,7 +1994,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
1823
1994
  *
1824
1995
  * @returns How many pages moved
1825
1996
  */
1826
- moveCapturesToCaptureVault(ctx: ActorContext): Promise<number>;
1997
+ moveCapturesToCaptureVault(ctx: ActorContext, owner?: string): Promise<number>;
1827
1998
  /**
1828
1999
  * Move private pages made before #1505 into their system-category's own
1829
2000
  * vault (#1507). Until then every private page went to its owner's default
@@ -1838,7 +2009,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
1838
2009
  *
1839
2010
  * @returns How many pages moved
1840
2011
  */
1841
- movePagesToCategoryVaults(ctx: ActorContext): Promise<number>;
2012
+ movePagesToCategoryVaults(ctx: ActorContext, owner?: string): Promise<number>;
1842
2013
  /**
1843
2014
  * Every private slug in the `vaults--…` form (#1507), once per vault. A
1844
2015
  * private page's slug is made fresh on every save, so each page whose slug
@@ -1848,7 +2019,7 @@ declare class PageManager extends BaseManager implements CatalogSource {
1848
2019
  *
1849
2020
  * @returns How many pages were re-saved
1850
2021
  */
1851
- migratePrivateSlugs(ctx: ActorContext): Promise<number>;
2022
+ migratePrivateSlugs(ctx: ActorContext, owner?: string): Promise<number>;
1852
2023
  /**
1853
2024
  * One pass of a vault move: every page in the vaults `ctx` can open is
1854
2025
  * offered to `pick`, which names the vault it goes to (and any frontmatter