diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 02f7e8b..caddaaa 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -28,8 +28,8 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: repository: PicPeak/picpeak - # Includes the English-only catalogs from PicPeak/picpeak#1330. - ref: 9396d019c6ab7cea81662193410c11ddfd77175c + # usage.v6 client (PicPeak/picpeak#1743): repin to its merge commit once it lands. + ref: 234a17c5d7c53656d76060d0931510c1606eb382 path: picpeak - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: diff --git a/README.md b/README.md index e29fa0a..a346a06 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,12 @@ Every build includes /source.tar.gz containing a fixed allowlist of application ## Contract and verification Old and partial reports remain readable after upgrades. The collector supports -v1/v2/v3/v4/v5 and treats omitted or null measurements as unknown. Freshly signed +v1/v2/v3/v4/v5/v6 and treats omitted or null measurements as unknown. Freshly signed delayed reports keep their original reporting date. Separate reception schemas -at `/schema/ingress/usage.v1.json` (also v2/v3/v4/v5) describe this compatibility; +at `/schema/ingress/usage.v1.json` (also v2/v3/v4/v5/v6) describe this compatibility; the original complete sender schemas, consent and privacy boundaries stay fixed. -The [protocol reference](docs/PROTOCOL.md) defines every field and operation. JSON Schemas are served at `/schema/usage.v1.json`, `/schema/usage.v2.json`, `/schema/usage.v3.json` `/schema/usage.v4.json` and `/schema/usage.v5.json`, with the current EN/DE catalog at `/schema/features.v5.json`. PicPeak carries byte-identical protocol/catalog files in `backend/src/usage/`. +The [protocol reference](docs/PROTOCOL.md) defines every field and operation. JSON Schemas are served at `/schema/usage.v1.json`, `/schema/usage.v2.json`, `/schema/usage.v3.json`, `/schema/usage.v4.json`, `/schema/usage.v5.json` and `/schema/usage.v6.json`, with the current EN/DE catalog at `/schema/features.v6.json`. PicPeak carries byte-identical protocol/catalog files in `backend/src/usage/`. The [full coverage matrix](docs/FEATURE_COVERAGE.md) covers 87 capabilities: 64 configured/used pairs and 23 configuration-only signals. ML face recognition is included without biometric results. v3 adds 13 specific capabilities, including invoice import, plus exactly two installation inventory totals: stored galleries and non-video photo records, including drafts and retained archived records. No contents, per-gallery breakdowns, visitor behavior or identities. diff --git a/docs/FEATURE_COVERAGE.md b/docs/FEATURE_COVERAGE.md index 85f9f6b..4f48cfa 100644 --- a/docs/FEATURE_COVERAGE.md +++ b/docs/FEATURE_COVERAGE.md @@ -1,4 +1,4 @@ -# Product-usage coverage: usage.v5 +# Product-usage coverage: usage.v6 ## What the numbers mean @@ -6,6 +6,9 @@ The 86 v4 capabilities have all been reviewed for the distinction between availability, a present configuration, and observed use. v5 asks 87 questions: six broad admin-management questions are replaced by precisely defined edit signals, and one question measures real template-mail transport acceptance. +v6 asks the same 87 questions; its one change is that `webhooks` use counts +any successful delivery (HTTP 2xx), automatic or manual, instead of only an +explicit admin test/replay (issue 1740). Still one bit, no delivery data. There are 64 configured/used pairs and 23 configuration-only signals. Historical views retain all 94 keys separately. No old value is renamed, backfilled, reinterpreted, or combined with its replacement. @@ -52,8 +55,8 @@ Reports retain the existing stable installation fingerprint, PicPeak version, daily date/generation time and gallery-layout enum. They are pseudonymous, not fully anonymous. Existing retention, access controls and deletion apply. -Every v1–v4 wire schema/catalog remains immutable. New evidence is retained only -under active, confirmed usage-consent.v5. The signed consent upgrade preserves +Every v1–v5 wire schema/catalog remains immutable. New evidence is retained only +under active, confirmed usage-consent.v6. The signed consent upgrade preserves identity and raw history, finishes pending old packets unchanged and resets markers only after the matching receipt. Opt-out wins over late receipts. A marker write failure must not retry a successfully sent email. @@ -61,9 +64,9 @@ Deploy the collector before the PicPeak client; existing clients keep working. ## Full audit -The table is the decision for every active v5 capability. The route/flag/settings -inventory is `usage-coverage.v5.json`; historical inventories remain unchanged. -Exact definitions are served at `/schema/features.v5.json` and disclosed in the +The table is the decision for every active v6 capability. The route/flag/settings +inventory is `usage-coverage.v6.json`; historical inventories remain unchanged. +Exact definitions are served at `/schema/features.v6.json` and disclosed in the PicPeak EN/DE consent catalog. A retired broad management signal remains under its original definition in the collector's earlier-measurements view/history. @@ -118,7 +121,7 @@ its original definition in the collector's earlier-measurements view/history. | `seo_editing` — SEO customization | Built in; display a label, not a percentage. | An admin saved a real change to an allowlisted SEO setting since consent. Defaults, reading and unchanged saves do not count; no rules, paths or search-engine activity are collected. | | `admin_management` — Admin and role management | Enabled switch/capability; may be a default. | A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts. | | `api_integration` — HTTP API integration | Technical configuration exists; may be a default, not activity. | Successful authenticated HTTP API capability call; only this bit, never URLs, request values, token/owner IDs or call counts. Does not trigger a report. | -| `webhooks` — Outbound webhooks | Technical configuration exists; may be a default, not activity. | Successful explicit admin webhook test/replay; no automatic or visitor-triggered deliveries. | +| `webhooks` — Outbound webhooks | Technical configuration exists; may be a default, not activity. | Successful delivery (HTTP 2xx) of any outbound webhook, whether triggered automatically by an event or by an explicit admin test/replay; no destinations, event types, payloads, timestamps or counts. | | `restore` — Restore | Built in; display a label, not a percentage. | A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts. | | `portable_backup` — Portable PicPeak export/import | Built in; display a label, not a percentage. | A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts. | | `database_backup` — Database backups | Technical configuration exists; may be a default, not activity. | A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts. | diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index cc2c68f..1ef74cf 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -1,4 +1,4 @@ -# Versioned usage protocol (usage.v1 / usage.v2 / usage.v3 / usage.v4 / usage.v5) +# Versioned usage protocol (usage.v1 / usage.v2 / usage.v3 / usage.v4 / usage.v5 / usage.v6) Implements the backend-signs/backend-sends decision in [#1110's transport follow-up](https://github.com/PicPeak/picpeak/issues/1110#issuecomment-5367220785). @@ -7,16 +7,16 @@ Browser transport is not implemented. CORS is not authentication. ## Exact envelope `POST /api/envelopes` accepts at most 16 KiB of uncompressed JSON. The complete -closed sender JSON Schemas are at `/schema/usage.v1.json`, `/schema/usage.v2.json`, `/schema/usage.v3.json`, `/schema/usage.v4.json`, and `/schema/usage.v5.json`. -The current field catalog is at `/schema/features.v5.json`; the v2/v3 catalogs retain their original definitions. Unknown fields are rejected at +closed sender JSON Schemas are at `/schema/usage.v1.json`, `/schema/usage.v2.json`, `/schema/usage.v3.json`, `/schema/usage.v4.json`, `/schema/usage.v5.json`, and `/schema/usage.v6.json`. +The current field catalog is at `/schema/features.v6.json`; the v2/v3 catalogs retain their original definitions. Unknown fields are rejected at every level. No arbitrary attributes or free-form telemetry are supported. | Field | Meaning | | ------------------------ | ------------------------------------------------------------- | -| `packet.schema_version` | Literal `usage.v1`, `usage.v2`, `usage.v3`, `usage.v4` or `usage.v5`; never inferred from payload | +| `packet.schema_version` | Literal `usage.v1`, `usage.v2`, `usage.v3`, `usage.v4`, `usage.v5` or `usage.v6`; never inferred from payload | | `packet.installation_id` | SHA-256 of Ed25519 SPKI public-key DER, lowercase hex | | `packet.packet_id` | UUIDv4 identifying an immutable operation | -| `packet.action` | `register`, `report`, `delete`, `feedback`, `vote`, `session`; v2/v3/v4/v5 also `consent` | +| `packet.action` | `register`, `report`, `delete`, `feedback`, `vote`, `session`; v2/v3/v4/v5/v6 also `consent` | | `packet.sequence` | 0 at registration; increments per accepted operation | | `packet.payload` | Action-specific closed schema, below | | `public_key` | Ed25519 SPKI DER, unpadded base64url | @@ -53,7 +53,7 @@ The separate, closed reception schemas are published at `/schema/ingress/usage.v1.json`, `/schema/ingress/usage.v2.json` and `/schema/ingress/usage.v3.json` and `/schema/ingress/usage.v4.json`. For `report` only, they accept omitted or `null` measurements: `picpeak_version`, `features`, individual capabilities and their -known `configured`/`used` members, `gallery_layouts`, and v3/v4/v5 `inventory` with +known `configured`/`used` members, `gallery_layouts`, and v3/v4/v5/v6 `inventory` with either or both totals. Other supplied values retain their original types, bounds and per-version allowlists. A missing value is unknown, never false or zero; an explicit empty layout list is known and means no layouts were reported. @@ -104,7 +104,7 @@ receipt was lost. No migration or rewriting of stored reports is required. ## Actions -- `register`: the matching `usage-consent.v1`, `usage-consent.v2`, `usage-consent.v3`, `usage-consent.v4` or `usage-consent.v5`, +- `register`: the matching `usage-consent.v1`, `usage-consent.v2`, `usage-consent.v3`, `usage-consent.v4`, `usage-consent.v5` or `usage-consent.v6`, with sequence zero. A second different registration for the same identity conflicts. - `report`: `picpeak_version`, `report_date`, `generated_at`, `features`, and @@ -336,3 +336,20 @@ unknown; v1–v4 envelopes retain their exact allowlists and validation. Confirm unchanged, local markers reset only on the matching consent receipt, and late responses cannot reverse opt-out. The existing two inventory totals and stable pseudonymous installation identity remain unchanged. Deploy collector first. + +## usage.v6 webhook delivery evidence + +v6 keeps all 87 v5 keys, the 64 configured/used pairs, the 23 configuration-only +signals and both inventory totals unchanged. The single change is the definition +of `webhooks` use: any successful delivery (HTTP 2xx) of an outbound webhook, +automatic event-triggered or explicit admin test/replay, instead of only an +admin test/replay enqueue. The PicPeak client records the bit from its delivery +worker on a 2xx, and only under confirmed `usage-consent.v6`; earlier consents +keep the enqueue meaning, and route enqueue evidence stops being recorded at v6. +No destination, event type, payload, timestamp or count enters the usage +subsystem; it stays one installation-wide boolean. + +The v6 receiver schema is `/schema/ingress/usage.v6.json`. v1–v5 envelopes keep +their exact allowlists and validation. Confirmed `usage-consent.v6` is required +before a v6 report is accepted; pending packets finish unchanged and local +markers reset only on the matching consent receipt. Deploy collector first. diff --git a/docs/usage-coverage.v5.json b/docs/usage-coverage.v5.json index 66216ef..5831d9f 100644 --- a/docs/usage-coverage.v5.json +++ b/docs/usage-coverage.v5.json @@ -1,42 +1,47 @@ { "settings_tabs": { - "usage": { - "signals": [], - "reason": "Explicit consent/report inspection/feedback is not itself adoption telemetry." + "accounting": { + "signals": [ + "accounting", + "accounting_incoming_invoices", + "accounting_expenses", + "accounting_tax_report", + "accounting_ledger" + ], + "reason": "Only module capabilities, no tax codes, rates, balances or business identity." }, - "features": { - "signals": [], - "reason": "Only the allowlisted effective feature booleans; no settings visit/save marker." + "analytics": { + "signals": [ + "analytics_dashboard" + ], + "reason": "Analytics capability and admin aggregate-view use only; no embedded analytics results/tracker IDs or visitors." }, - "general": { + "apiTokens": { "signals": [ - "video_uploads", - "camera_raw_uploads", - "public_site", - "custom_css", - "download_original_filenames" + "api_integration" ], - "reason": "General technical upload/public-site/CSS configuration only; no title, URLs, limits, times, HTML or identity. v3 also reports two installation inventory totals separately, with explicit consent." + "reason": "Valid credential presence and one successful scoped API capability bit; no tokens/scopes/owner metadata." }, - "events": { + "backup": { "signals": [ - "galleries", - "gallery_guest_uploads", - "gallery_downloads_restricted", - "gallery_client_access", - "gallery_watermarks", - "gallery_image_protection", - "gallery_reveal", - "gallery_expiration", - "gallery_capture_date_sort" + "backup", + "database_backup", + "portable_backup", + "restore", + "s3_backups" ], - "reason": "Gallery operations and disclosed configuration only; no event/customer values or visitor use. v3 also reports two installation inventory totals separately, with explicit consent." + "reason": "Schedule presence/manual capability initiation only, no histories, sizes, paths or files." }, - "eventTypes": { + "branding": { "signals": [ - "event_type_editing" + "branding_editing", + "gallery_watermarks" ], - "reason": "General admin event-type capability; no names or preset contents." + "reason": "Branding operation and watermark configuration only; no branding text, logos or colors." + }, + "businessProfile": { + "signals": [], + "reason": "Excluded business identity, bank accounts and addresses." }, "categories": { "signals": [ @@ -45,23 +50,41 @@ ], "reason": "Category management capability only; no names/order/category membership." }, - "thumbnails": { + "cms": { "signals": [ - "photo_processing" + "cms_content_editing", + "public_site" ], - "reason": "Admin processing settings/regeneration initiation only; no image data or progress." + "reason": "Admin page editing capability/public-site enabled only; no HTML, slugs or traffic." }, - "downloads": { + "contracts": { "signals": [ - "download_resolution_picker" + "crm_contracts", + "document_templates" ], - "reason": "Configuration boolean only; no actual download/selection behavior or resolution values." + "reason": "Admin contract/template capability only; no legal text or signatures." }, - "styling": { + "crm": { "signals": [ - "custom_css" + "crm", + "crm_quotes", + "crm_invoices", + "crm_projects", + "crm_hours", + "customer_portal", + "crm_installments", + "crm_invoice_import", + "crm_combined_billing", + "crm_monthly_billing_manual", + "crm_document_conversion" ], - "reason": "Presence/application only plus controlled gallery-layout enums, never CSS/theme values." + "reason": "Only coarse module capabilities; no policies/amounts/customer/payment values." + }, + "downloads": { + "signals": [ + "download_resolution_picker" + ], + "reason": "Configuration boolean only; no actual download/selection behavior or resolution values." }, "email": { "signals": [ @@ -73,21 +96,43 @@ ], "reason": "Configuration and documented manual admin capability operations only; messages, recipients, automatic activity and mailbox values excluded." }, - "moderation": { + "eventTypes": { "signals": [ - "feedback_moderation" + "event_type_editing" ], - "reason": "Admin moderation/word-filter capability, never feedback content or visitor behavior." + "reason": "General admin event-type capability; no names or preset contents." }, - "security": { + "events": { + "signals": [ + "galleries", + "gallery_guest_uploads", + "gallery_downloads_restricted", + "gallery_client_access", + "gallery_watermarks", + "gallery_image_protection", + "gallery_reveal", + "gallery_expiration", + "gallery_capture_date_sort" + ], + "reason": "Gallery operations and disclosed configuration only; no event/customer values or visitor use. v3 also reports two installation inventory totals separately, with explicit consent." + }, + "features": { "signals": [], - "reason": "Excluded password/MFA/session/rate-limit/security profiles and operations." + "reason": "Only the allowlisted effective feature booleans; no settings visit/save marker." }, - "sso": { + "general": { "signals": [ - "oauth" + "video_uploads", + "camera_raw_uploads", + "public_site", + "custom_css", + "download_original_filenames" ], - "reason": "Enabled/config-present and successful admin callback only; no claims/provider details." + "reason": "General technical upload/public-site/CSS configuration only; no title, URLs, limits, times, HTML or identity. v3 also reports two installation inventory totals separately, with explicit consent." + }, + "health": { + "signals": [], + "reason": "Excluded operational health, stuck-email diagnostics, queue and key-state data — the same exclusion the Status tab and the adminSystemHealth route family already carry." }, "imageSecurity": { "signals": [ @@ -95,113 +140,78 @@ ], "reason": "Configuration presence only; no blocked-IP/security analytics or monitoring history." }, + "moderation": { + "signals": [ + "feedback_moderation" + ], + "reason": "Admin moderation/word-filter capability, never feedback content or visitor behavior." + }, + "security": { + "signals": [], + "reason": "Excluded password/MFA/session/rate-limit/security profiles and operations." + }, "seo": { "signals": [ "seo_editing" ], "reason": "Admin SEO configuration operation only; no meta tags, URLs, robots or verification tokens." }, - "apiTokens": { + "slideshow": { "signals": [ - "api_integration" + "slideshow" ], - "reason": "Valid credential presence and one successful scoped API capability bit; no tokens/scopes/owner metadata." + "reason": "Admin setup capability only; no kiosk viewers, slide progress or photos." }, - "webhooks": { + "sso": { "signals": [ - "webhooks" + "oauth" ], - "reason": "Active configuration and manual test/replay enqueue only; no delivery data." + "reason": "Enabled/config-present and successful admin callback only; no claims/provider details." }, "status": { "signals": [], "reason": "Excluded operational health, diagnostics, resource data, update and storage polling." }, - "analytics": { + "styling": { "signals": [ - "analytics_dashboard" + "custom_css" ], - "reason": "Analytics capability and admin aggregate-view use only; no embedded analytics results/tracker IDs or visitors." + "reason": "Presence/application only plus controlled gallery-layout enums, never CSS/theme values." }, - "backup": { + "thumbnails": { "signals": [ - "backup", - "database_backup", - "portable_backup", - "restore", - "s3_backups" + "photo_processing" ], - "reason": "Schedule presence/manual capability initiation only, no histories, sizes, paths or files." - }, - "businessProfile": { - "signals": [], - "reason": "Excluded business identity, bank accounts and addresses." + "reason": "Admin processing settings/regeneration initiation only; no image data or progress." }, - "crm": { + "transfers": { "signals": [ - "crm", - "crm_quotes", - "crm_invoices", - "crm_projects", - "crm_hours", - "customer_portal", - "crm_installments", - "crm_invoice_import", - "crm_combined_billing", - "crm_monthly_billing_manual", - "crm_document_conversion" + "transfers", + "transfer_upload_links" ], - "reason": "Only coarse module capabilities; no policies/amounts/customer/payment values." + "reason": "Admin-side upload policy only: which file types a request accepts and the size cap. Never the files themselves, their names, who uploaded them or any recipient address." }, - "contracts": { - "signals": [ - "crm_contracts", - "document_templates" - ], - "reason": "Admin contract/template capability only; no legal text or signatures." + "usage": { + "signals": [], + "reason": "Explicit consent/report inspection/feedback is not itself adoption telemetry." }, - "reminderTemplates": { + "users": { "signals": [ - "reminder_emails", - "email_template_editing" + "admin_management" ], - "reason": "Reminder flag configuration and admin template editing only; no automatic reminder sends/recipients/content." + "reason": "Admin account/role management capability, as the top-level Users surface reported before it became a Settings tab; no names, emails, permissions, role labels or invitation contents." }, - "accounting": { + "webhooks": { "signals": [ - "accounting", - "accounting_incoming_invoices", - "accounting_expenses", - "accounting_tax_report", - "accounting_ledger" + "webhooks" ], - "reason": "Only module capabilities, no tax codes, rates, balances or business identity." + "reason": "Active configuration and manual test/replay enqueue only; no delivery data." }, "whatsapp": { "signals": [ "whatsapp" ], "reason": "Configured integration plus manual test only; no phone numbers, tokens or automatic delivery." - }, - "slideshow": { - "signals": [ - "slideshow" - ], - "reason": "Admin setup capability only; no kiosk viewers, slide progress or photos." - }, - "branding": { - "signals": [ - "branding_editing", - "gallery_watermarks" - ], - "reason": "Branding operation and watermark configuration only; no branding text, logos or colors." - }, - "cms": { - "signals": [ - "cms_content_editing", - "public_site" - ], - "reason": "Admin page editing capability/public-site enabled only; no HTML, slugs or traffic." } }, "reviewed_picpeak_base": "a5ff9264 (3.124.1-beta.0)", @@ -285,6 +295,7 @@ "GET /picpeak/export", "POST /picpeak/import", "GET /runs/:id", + "DELETE /runs/:id", "GET /files", "DELETE /cleanup", "POST /test-connection", @@ -309,6 +320,7 @@ "reason": "Business identity/bank/tax-address configuration and VAT-code helper surface are not separate usage signals. Billing/accounting capabilities are covered without profiling the business.", "route_signatures": [ "GET /", + "GET /history", "GET /logo-diagnostic", "POST /logo", "DELETE /logo", @@ -319,6 +331,19 @@ "DELETE /bank-accounts/:id" ] }, + "adminPdfThemes.js": { + "decision": "excluded", + "signals": [], + "reason": "PDF look-and-feel configuration (colours, font, footer, page numbers, layout, uploaded fonts) and its sample preview are settings, not separate usage signals; the documents they style are covered by the billing capabilities.", + "route_signatures": [ + "GET /", + "GET /fonts", + "POST /fonts", + "POST /fonts/:id/archive", + "PUT /:scope", + "POST /:scope/preview" + ] + }, "adminCalendar.js": { "decision": "partial", "signals": [ @@ -380,17 +405,27 @@ "GET /", "POST /", "GET /:id", + "GET /:id/documents", "PUT /:id", + "GET /:id/send-preview", "POST /:id/send", "POST /:id/cancel", "POST /:id/convert-to-event", "POST /:id/convert-to-invoice", "POST /:id/resend-signed", "POST /:id/restamp-signatures", + "GET /:id/signers", + "PUT /:id/signers", + "POST /:id/signers/:signerId/resend", + "POST /:id/signers/:signerId/remind", + "GET /:id/signing-evidence", "POST /:id/countersign", "POST /:id/upload-signed-pdf", + "GET /:id/paper-signature-coverage", "GET /:id/pdf", "GET /:id/signed-pdf", + "GET /:id/certificate", + "GET /:id/history", "GET /:id/audit-trail", "GET /:id/verify-integrity", "GET /:id/preview" @@ -419,8 +454,15 @@ "crm_combined_billing", "crm_monthly_billing_manual" ], - "reason": "Successful admin CRM/hour-entry/invitation operations only. No customer/account names, IDs, rates, billed hours, payment state or portal behavior. v3 adds only: crm_combined_billing, crm_monthly_billing_manual. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "reason": "Successful admin CRM/hour-entry/invitation operations only. No customer/account names, IDs, rates, billed hours, payment state or portal behavior. v3 adds only: crm_combined_billing, crm_monthly_billing_manual. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface. Customer-document writes (v5) only count as crm: no file names, sizes, counts or review outcomes. Customer groups (#1443) are admin-side organisation: the catalogue and the assignment routes report crm only — no group names, colours, membership or counts.", "route_signatures": [ + "GET /groups", + "POST /groups", + "POST /groups/reorder", + "POST /groups/bulk-assign", + "PUT /groups/:groupId", + "DELETE /groups/:groupId", + "PUT /:id/groups", "GET /", "GET /search", "GET /invitations", @@ -428,6 +470,7 @@ "DELETE /invitations/:id", "POST /", "POST /:id/send-invite", + "GET /:id/history", "GET /:id", "PUT /:id", "POST /:id/deactivate", @@ -443,7 +486,20 @@ "POST /:id/hour-entries/bill", "POST /:id/bill-combined", "POST /:id/trigger-monthly-bill", - "GET /:id/monthly-draft" + "GET /:id/monthly-draft", + "GET /:id/activity", + "GET /:id/documents", + "POST /:id/documents", + "PATCH /:id/documents/:docId", + "POST /:id/documents/:docId/share", + "POST /:id/documents/:docId/unshare", + "POST /:id/documents/:docId/review", + "GET /:id/documents/:docId/download", + "DELETE /:id/documents/:docId", + "GET /:id/document-requests", + "POST /:id/document-requests", + "PATCH /:id/document-requests/:requestId", + "DELETE /:id/document-requests/:requestId" ] }, "adminDashboard.js": { @@ -594,6 +650,15 @@ "POST /:id/extend" ] }, + "adminEvents/downloadLimit.js": { + "decision": "excluded", + "signals": [], + "reason": "Download limit usage and reset for one gallery (issue 1560): no product-usage middleware, counters or report triggers; the usage numbers stay on the install.", + "route_signatures": [ + "GET /:id/download-limit", + "POST /:id/download-limit/reset" + ] + }, "adminEvents/downloadResolutions.js": { "decision": "configuration", "signals": [ @@ -643,6 +708,12 @@ "reason": "Router composition / helpers; decisions are recorded for each mounted family.", "route_signatures": [] }, + "adminEvents/listSort.js": { + "decision": "composition", + "signals": [], + "reason": "ORDER BY construction for the admin events list; declares no routes and reads no personal data.", + "route_signatures": [] + }, "adminEvents/logo.js": { "decision": "partial", "signals": [ @@ -654,6 +725,17 @@ "DELETE /:id/logo" ] }, + "adminEvents/passwordRecovery.js": { + "decision": "partial", + "signals": [ + "galleries" + ], + "reason": "Admin reveal of a stored gallery password (#1271); the password, the event and who looked are never reported, only that galleries exist.", + "route_signatures": [ + "GET /:id/password-status", + "GET /:id/password" + ] + }, "adminEvents/qr.js": { "decision": "partial", "signals": [ @@ -715,6 +797,7 @@ "reason": "Admin expense/inbound-invoice operations; no financial values, suppliers, mileage/location, dates, receipt files or OCR text.", "route_signatures": [ "GET /categories", + "GET /categories/:id/history", "POST /categories", "PATCH /categories/:id", "DELETE /categories/:id", @@ -725,6 +808,7 @@ "GET /inbound/by-customer/:customerAccountId", "GET /inbound/:id/file", "GET /inbound/:id/page/:n", + "GET /inbound/:id/history", "GET /inbound/:id", "PATCH /inbound/:id", "POST /inbound/:id/categorize", @@ -733,6 +817,7 @@ "GET /", "POST /", "GET /:id/proof", + "GET /:id/history", "GET /:id", "PATCH /:id", "POST /:id/invoice", @@ -856,6 +941,7 @@ "reason": "Admin invoice operations only; no amounts, VAT/customer/payment values or payment-check responses. v3 adds only: crm_invoice_import. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", "route_signatures": [ "GET /", + "GET /:id/history", "GET /:id", "POST /", "POST /import", @@ -882,10 +968,12 @@ "reason": "Admin ledger-account/VAT/mapping edits and ledger export initiation only; no account/currency/VAT identifiers or exported records.", "route_signatures": [ "GET /accounts", + "GET /accounts/:id/history", "POST /accounts", "PATCH /accounts/:id", "DELETE /accounts/:id", "GET /vat-codes", + "GET /vat-codes/:id/history", "POST /vat-codes", "PATCH /vat-codes/:id", "DELETE /vat-codes/:id", @@ -938,7 +1026,9 @@ "POST /repair-capture-dates", "GET /repair-capture-dates/status", "POST /repair-orientation", - "GET /repair-orientation/status" + "GET /repair-orientation/status", + "POST /repair-credits", + "GET /repair-credits/status" ] }, "adminPhotoExport.js": { @@ -979,6 +1069,8 @@ "PATCH /:eventId/photos/:photoId", "POST /:eventId/photos/bulk-delete", "POST /:eventId/photos/bulk-update", + "GET /:eventId/photos/credits", + "PUT /:eventId/photos/:photoId/credit", "GET /:eventId/photos/:photoId/download", "GET /:eventId/photos", "GET /:eventId/photo/:photoId", @@ -1026,13 +1118,17 @@ "reason": "Admin quote/preset operations only; no quote content, prices, customer acceptance or signatures. v3 adds only: crm_document_conversion. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", "route_signatures": [ "GET /", + "GET /:id/history", "GET /:id", "POST /", "PUT /:id", "POST /:id/send", "POST /:id/duplicate", + "POST /:id/recalculate-rates", "POST /:id/accept", + "POST /:id/add-ons", "POST /:id/decline", + "POST /:id/reissue", "POST /:id/convert", "POST /:id/convert-to-invoice", "POST /:id/convert-to-contract", @@ -1118,6 +1214,8 @@ "PUT /customer-surface", "PUT /accounting", "PUT /slideshow", + "GET /transfers", + "PUT /transfers", "GET /downloads", "PUT /downloads", "GET /sso", @@ -1218,7 +1316,7 @@ "transfers", "transfer_upload_links" ], - "reason": "Admin transfer CRUD/files/link management/download only. Public recipients, received-file data, upload and download statistics excluded. v3 adds only: transfer_upload_links. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "reason": "Admin transfer CRUD/files/link and upload-code management/email resend/download only. Public recipients, received-file data, upload and download statistics excluded. v3 adds only: transfer_upload_links. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", "route_signatures": [ "GET /", "POST /", @@ -1230,8 +1328,9 @@ "POST /:id/upload-files", "DELETE /:id/extra-files/:extraId", "GET /:id/extra-files/:extraId/download", - "POST /:id/upload-link", - "DELETE /:id/upload-link", + "POST /:id/upload-code", + "DELETE /:id/upload-code", + "POST /:id/resend", "GET /:id/download", "GET /:id/uploads/:uploadId/download" ] @@ -1239,11 +1338,12 @@ "adminUsage.js": { "decision": "excluded", "signals": [], - "reason": "Consent, inspection, export, feedback, voting, deletion and abandoning an unsignable deletion are explicit protocol operations; not product-use signals. Activity only triggers a due fixed report.", + "reason": "Consent, prompt acknowledgement, inspection, export, feedback, voting, deletion and abandoning an unsignable deletion are explicit protocol operations; not product-use signals. Activity only triggers a due fixed report.", "route_signatures": [ "POST /activity", "GET /", "POST /dismiss", + "POST /prompt-seen", "POST /enable", "POST /consent", "POST /disable", @@ -1358,7 +1458,6 @@ "POST /gallery/share-login", "POST /gallery/logout", "GET /session", - "POST /admin/change-password", "POST /password-strength", "GET /admin/sso/login", "GET /admin/sso/callback" @@ -1381,7 +1480,22 @@ "GET /quotes/:id/pdf", "GET /invoices/:id/pdf", "GET /contracts", - "GET /contracts/:id/pdf" + "POST /contracts/:id/signing-access", + "GET /contracts/:id/pdf", + "GET /contracts/:id/certificate", + "GET /contracts/:id", + "POST /contracts/:id/sign", + "POST /contracts/:id/upload-signed-pdf", + "GET /quotes/:id", + "POST /quotes/:id/respond", + "GET /dashboard", + "GET /events/:slug/overview", + "GET /documents", + "POST /documents", + "GET /document-requests", + "GET /documents/:id", + "DELETE /documents/:id", + "GET /documents/:id/download" ] }, "customerAuth.js": { @@ -1402,32 +1516,7 @@ "decision": "excluded", "signals": [], "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", - "route_signatures": [ - "GET /resolve/:identifier", - "GET /:slug/verify-token/:token", - "GET /:slug/info", - "GET /:slug/show/:token/session", - "GET /:slug/show/:token/state", - "GET /:slug/photos", - "GET /:slug/people", - "PATCH /:slug/photos/:photoId/visibility", - "PATCH /:slug/photos/visibility/bulk", - "GET /:slug/download/:photoId", - "GET /:slug/download-all", - "POST /:slug/download-selected", - "POST /:slug/download-jobs", - "GET /:slug/download-jobs/:token", - "GET /:slug/download-jobs/:token/file", - "POST /:slug/photo/:photoId/view", - "GET /:slug/photo/:photoId", - "GET /:slug/thumbnail/:photoId", - "GET /:slug/hero/:photoId", - "GET /:slug/preview/:photoId", - "GET /:slug/stats", - "POST /:eventId/upload", - "GET /:slug/uploads/status", - "GET /:slug/css-template" - ] + "route_signatures": [] }, "galleryFeedback.js": { "decision": "excluded", @@ -1454,17 +1543,6 @@ "POST /:slug/guest/redeem" ] }, - "protectedImages.js": { - "decision": "excluded", - "signals": [], - "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", - "route_signatures": [ - "GET /:slug/photo/:photoId/view", - "POST /:slug/photo/:photoId/generate-secure-token", - "POST /:slug/photo/:photoId/generate-url", - "GET /:slug/photo/:photoId/signed/:token" - ] - }, "publicCMS.js": { "decision": "excluded", "signals": [], @@ -1479,11 +1557,21 @@ "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", "route_signatures": [ "GET /:token", + "GET /:token/attachments/:attachmentId", "POST /:token/sign", "POST /:token/upload-signed-pdf", "GET /:token/pdf" ] }, + "publicDocumentVerification.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:token/verification", + "POST /:token/verification/confirm" + ] + }, "publicFonts.js": { "decision": "excluded", "signals": [], @@ -1516,6 +1604,7 @@ "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", "route_signatures": [ "GET /:token", + "GET /:token/totals", "POST /:token/respond" ] }, @@ -1555,17 +1644,6 @@ "POST /:token/:action" ] }, - "secureImages.js": { - "decision": "excluded", - "signals": [], - "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", - "route_signatures": [ - "POST /:slug/generate-token", - "GET /:slug/secure/:photoId/:token", - "GET /:slug/secure-download/:photoId/:token", - "GET /security/stats" - ] - }, "setup.js": { "decision": "excluded", "signals": [], @@ -1590,7 +1668,176 @@ "GET /events/:id", "POST /events/:id/photos", "GET /events/:id/share-link", - "GET /events/:id/photos" + "GET /events/:id/photos", + "GET /events/:id/photos/download", + "GET /events/:id/photos/:photoId/download", + "GET /events/:id/photos/:photoId/preview" + ] + }, + "gallery/downloads.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/download/:photoId", + "GET /:slug/download-all", + "POST /:slug/download-selected", + "POST /:slug/download-jobs", + "GET /:slug/download-jobs/:token", + "GET /:slug/download-jobs/:token/file" + ] + }, + "gallery/media.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:slug/photo/:photoId/view", + "GET /:slug/photo/:photoId", + "GET /:slug/thumbnail/:photoId", + "GET /:slug/hero/:photoId", + "GET /:slug/preview/:photoId" + ] + }, + "gallery/metadata.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /resolve/:identifier", + "GET /:slug/verify-token/:token", + "GET /:slug/info" + ] + }, + "gallery/photos.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/photos", + "GET /:slug/people", + "PATCH /:slug/photos/:photoId/visibility", + "PATCH /:slug/photos/visibility/bulk" + ] + }, + "gallery/slideshow.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/show/:token/session", + "GET /:slug/show/:token/state" + ] + }, + "gallery/stats.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/stats" + ] + }, + "gallery/styles.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/css-template" + ] + }, + "gallery/uploads.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:eventId/upload", + "GET /:slug/uploads/status" + ] + }, + "adminQuoteCatalog.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_quotes", + "document_templates" + ], + "reason": "Admin quote catalogue and template operations only (packages, promotions, text blocks, templates, quote-from-template). No quote content, prices, customer data or text-block wording.", + "route_signatures": [ + "GET /packages", + "POST /packages", + "PUT /packages/:id", + "DELETE /packages/:id", + "GET /promotions", + "POST /promotions", + "PUT /promotions/:id", + "DELETE /promotions/:id", + "GET /text-blocks", + "POST /text-blocks", + "PUT /text-blocks/:id", + "DELETE /text-blocks/:id", + "GET /templates", + "POST /templates", + "GET /templates/:id", + "PUT /templates/:id", + "DELETE /templates/:id", + "POST /templates/:id/publish", + "POST /templates/:id/quotes", + "POST /templates/from-quote/:quoteId" + ] + }, + "adminContractTemplates.js": { + "decision": "partial", + "signals": [ + "document_templates" + ], + "reason": "Admin contract template operations only (drafts, pre-publication check, publishing, versions, default, preview). No clause wording, customer data or contract content.", + "route_signatures": [ + "GET /", + "GET /placeholders", + "POST /", + "GET /:id", + "GET /:id/versions/:version", + "PUT /:id/draft", + "POST /:id/publish-check", + "POST /:id/publish", + "POST /:id/versions/:version/draft", + "POST /:id/duplicate", + "POST /:id/archive", + "POST /:id/restore", + "POST /:id/default", + "GET /:id/preview-content", + "POST /:id/preview" + ] + }, + "adminDocumentAttachments.js": { + "decision": "partial", + "signals": [ + "document_templates" + ], + "reason": "Admin attachment-library operations only (upload, archive, restore, download). No file names, contents or checksums.", + "route_signatures": [ + "GET /", + "POST /", + "GET /:id/download", + "POST /:id/archive", + "POST /:id/restore" + ] + }, + "publicContractSigning.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /invite/:token", + "POST /invite/:token/code", + "POST /invite/:token/verify", + "GET /session", + "GET /session/pdf", + "GET /session/attachments/:attachmentId", + "POST /session/sign", + "POST /session/details", + "POST /session/decline", + "POST /session/upload-signed-pdf" ] } }, @@ -1648,6 +1895,10 @@ ], "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." }, + "documents": { + "signals": [], + "reason": "Excluded: customer document exchange is not reported as its own capability; no flag value, file names, sizes or counts." + }, "expenses": { "signals": [ "accounting_expenses" diff --git a/docs/usage-coverage.v6.json b/docs/usage-coverage.v6.json new file mode 100644 index 0000000..6b70c87 --- /dev/null +++ b/docs/usage-coverage.v6.json @@ -0,0 +1,2411 @@ +{ + "settings_tabs": { + "accounting": { + "signals": [ + "accounting", + "accounting_incoming_invoices", + "accounting_expenses", + "accounting_tax_report", + "accounting_ledger" + ], + "reason": "Only module capabilities, no tax codes, rates, balances or business identity." + }, + "analytics": { + "signals": [ + "analytics_dashboard" + ], + "reason": "Analytics capability and admin aggregate-view use only; no embedded analytics results/tracker IDs or visitors." + }, + "apiTokens": { + "signals": [ + "api_integration" + ], + "reason": "Valid credential presence and one successful scoped API capability bit; no tokens/scopes/owner metadata." + }, + "backup": { + "signals": [ + "backup", + "database_backup", + "portable_backup", + "restore", + "s3_backups" + ], + "reason": "Schedule presence/manual capability initiation only, no histories, sizes, paths or files." + }, + "branding": { + "signals": [ + "branding_editing", + "gallery_watermarks" + ], + "reason": "Branding operation and watermark configuration only; no branding text, logos or colors." + }, + "businessProfile": { + "signals": [], + "reason": "Excluded business identity, bank accounts and addresses." + }, + "categories": { + "signals": [ + "category_editing", + "gallery_folders" + ], + "reason": "Category management capability only; no names/order/category membership." + }, + "cms": { + "signals": [ + "cms_content_editing", + "public_site" + ], + "reason": "Admin page editing capability/public-site enabled only; no HTML, slugs or traffic." + }, + "contracts": { + "signals": [ + "crm_contracts", + "document_templates" + ], + "reason": "Admin contract/template capability only; no legal text or signatures." + }, + "crm": { + "signals": [ + "crm", + "crm_quotes", + "crm_invoices", + "crm_projects", + "crm_hours", + "customer_portal", + "crm_installments", + "crm_invoice_import", + "crm_combined_billing", + "crm_monthly_billing_manual", + "crm_document_conversion" + ], + "reason": "Only coarse module capabilities; no policies/amounts/customer/payment values." + }, + "downloads": { + "signals": [ + "download_resolution_picker" + ], + "reason": "Configuration boolean only; no actual download/selection behavior or resolution values." + }, + "email": { + "signals": [ + "smtp", + "incoming_mail", + "messaging", + "email_template_editing", + "email_webhook" + ], + "reason": "Configuration and documented manual admin capability operations only; messages, recipients, automatic activity and mailbox values excluded." + }, + "eventTypes": { + "signals": [ + "event_type_editing" + ], + "reason": "General admin event-type capability; no names or preset contents." + }, + "events": { + "signals": [ + "galleries", + "gallery_guest_uploads", + "gallery_downloads_restricted", + "gallery_client_access", + "gallery_watermarks", + "gallery_image_protection", + "gallery_reveal", + "gallery_expiration", + "gallery_capture_date_sort" + ], + "reason": "Gallery operations and disclosed configuration only; no event/customer values or visitor use. v3 also reports two installation inventory totals separately, with explicit consent." + }, + "features": { + "signals": [], + "reason": "Only the allowlisted effective feature booleans; no settings visit/save marker." + }, + "general": { + "signals": [ + "video_uploads", + "camera_raw_uploads", + "public_site", + "custom_css", + "download_original_filenames" + ], + "reason": "General technical upload/public-site/CSS configuration only; no title, URLs, limits, times, HTML or identity. v3 also reports two installation inventory totals separately, with explicit consent." + }, + "health": { + "signals": [], + "reason": "Excluded operational health, stuck-email diagnostics, queue and key-state data — the same exclusion the Status tab and the adminSystemHealth route family already carry." + }, + "imageSecurity": { + "signals": [ + "gallery_image_protection" + ], + "reason": "Configuration presence only; no blocked-IP/security analytics or monitoring history." + }, + "moderation": { + "signals": [ + "feedback_moderation" + ], + "reason": "Admin moderation/word-filter capability, never feedback content or visitor behavior." + }, + "security": { + "signals": [], + "reason": "Excluded password/MFA/session/rate-limit/security profiles and operations." + }, + "seo": { + "signals": [ + "seo_editing" + ], + "reason": "Admin SEO configuration operation only; no meta tags, URLs, robots or verification tokens." + }, + "slideshow": { + "signals": [ + "slideshow" + ], + "reason": "Admin setup capability only; no kiosk viewers, slide progress or photos." + }, + "sso": { + "signals": [ + "oauth" + ], + "reason": "Enabled/config-present and successful admin callback only; no claims/provider details." + }, + "status": { + "signals": [], + "reason": "Excluded operational health, diagnostics, resource data, update and storage polling." + }, + "styling": { + "signals": [ + "custom_css" + ], + "reason": "Presence/application only plus controlled gallery-layout enums, never CSS/theme values." + }, + "thumbnails": { + "signals": [ + "photo_processing" + ], + "reason": "Admin processing settings/regeneration initiation only; no image data or progress." + }, + "transfers": { + "signals": [ + "transfers", + "transfer_upload_links" + ], + "reason": "Admin-side upload policy only: which file types a request accepts and the size cap. Never the files themselves, their names, who uploaded them or any recipient address." + }, + "usage": { + "signals": [], + "reason": "Explicit consent/report inspection/feedback is not itself adoption telemetry." + }, + "users": { + "signals": [ + "admin_management" + ], + "reason": "Admin account/role management capability, as the top-level Users surface reported before it became a Settings tab; no names, emails, permissions, role labels or invitation contents." + }, + "webhooks": { + "signals": [ + "webhooks" + ], + "reason": "Active configuration plus one bit for any successful delivery, automatic or manual test/replay; no delivery data." + }, + "whatsapp": { + "signals": [ + "whatsapp" + ], + "reason": "Configured integration plus manual test only; no phone numbers, tokens or automatic delivery." + } + }, + "reviewed_picpeak_base": "a5ff9264 (3.124.1-beta.0)", + "schema_version": "usage.v6", + "purpose": "Product capability prioritization and installation gallery/photo totals; no contents, identities, per-entity breakdowns, action frequencies or visitor observations.", + "route_families": { + "acceptInvite.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token", + "POST /:token" + ] + }, + "admin.js": { + "decision": "composition", + "signals": [], + "reason": "Router composition / helpers; decisions are recorded for each mounted family.", + "route_signatures": [] + }, + "adminApiTokens.js": { + "decision": "configuration", + "signals": [ + "api_integration" + ], + "reason": "Only existence of a valid credential; no marker from token listing/creation, no scope, owner, token, expiry date or last-used time.", + "route_signatures": [ + "GET /", + "POST /", + "DELETE /:id" + ] + }, + "adminArchives.js": { + "decision": "partial", + "signals": [ + "galleries", + "archive_management", + "photo_exports" + ], + "reason": "Admin archive/delete/restore/download initiation only; filenames, histories, storage sizes and polling excluded.", + "route_signatures": [ + "GET /", + "GET /:id", + "POST /:id/restore", + "GET /:id/download", + "DELETE /:id" + ] + }, + "adminAuth.js": { + "decision": "excluded", + "signals": [], + "reason": "Bootstrap, passwords/MFA/session/profile, per-person notifications, developer helpers and operational health/update/log polling are outside the prioritization purpose.", + "route_signatures": [ + "GET /profile", + "PUT /profile", + "POST /change-password", + "POST /logout", + "GET /mfa/status", + "POST /mfa/setup", + "POST /mfa/enable", + "POST /mfa/disable", + "POST /mfa/recovery-codes" + ] + }, + "adminBackup.js": { + "decision": "partial", + "signals": [ + "backup", + "portable_backup", + "restore", + "s3_storage", + "s3_backups" + ], + "reason": "Admin backup initiation, portable export/import and successful S3 roundtrip test. Local export never implies S3; names, schedules, sizes, contents and history excluded.", + "route_signatures": [ + "GET /config", + "PUT /config", + "GET /status", + "POST /run", + "GET /picpeak/export", + "POST /picpeak/import", + "GET /runs/:id", + "DELETE /runs/:id", + "GET /files", + "DELETE /cleanup", + "POST /test-connection", + "GET /manifest/:backupRunId", + "POST /manifest/validate", + "GET /manifest/:backupRunId/download", + "GET /manifests/:backupId", + "GET /manifests/:backupId/download", + "POST /manifests/validate", + "GET /s3/buckets", + "GET /s3/files", + "DELETE /s3/cleanup", + "POST /s3/test-upload", + "GET /download/:backupId", + "GET /checksums", + "POST /estimate" + ] + }, + "adminBusinessProfile.js": { + "decision": "excluded", + "signals": [], + "reason": "Business identity/bank/tax-address configuration and VAT-code helper surface are not separate usage signals. Billing/accounting capabilities are covered without profiling the business.", + "route_signatures": [ + "GET /", + "GET /history", + "GET /logo-diagnostic", + "POST /logo", + "DELETE /logo", + "PUT /", + "GET /bank-accounts", + "POST /bank-accounts", + "PUT /bank-accounts/:id", + "DELETE /bank-accounts/:id" + ] + }, + "adminPdfThemes.js": { + "decision": "excluded", + "signals": [], + "reason": "PDF look-and-feel configuration (colours, font, footer, page numbers, layout, uploaded fonts) and its sample preview are settings, not separate usage signals; the documents they style are covered by the billing capabilities.", + "route_signatures": [ + "GET /", + "GET /fonts", + "POST /fonts", + "POST /fonts/:id/archive", + "PUT /:scope", + "POST /:scope/preview" + ] + }, + "adminCalendar.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_calendar" + ], + "reason": "Authenticated admin calendar retrieval is capability use; no calendar entries, dates, recurrence, availability or bookings.", + "route_signatures": [ + "GET /items" + ] + }, + "adminCategories.js": { + "decision": "partial", + "signals": [ + "category_editing", + "gallery_folders" + ], + "reason": "Admin category CRUD; no names, descriptions, colors or ordering values. v3 adds only: gallery_folders. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /global", + "GET /event/:eventId", + "POST /", + "PUT /:id", + "PUT /:id/hero", + "DELETE /:id", + "POST /reorder", + "DELETE /reorder/:eventId", + "POST /reorder-global" + ] + }, + "adminCMS.js": { + "decision": "partial", + "signals": [ + "cms_content_editing" + ], + "reason": "Admin CMS page CRUD only. Public page traffic, slug, HTML, text, links and media excluded.", + "route_signatures": [ + "GET /pages", + "GET /pages/:slug", + "PUT /pages/:slug", + "POST /pages/:slug/logo", + "DELETE /pages/:slug/logo" + ] + }, + "adminContracts.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_contracts", + "document_templates", + "crm_document_conversion" + ], + "reason": "Admin contract/block operations only; no legal text, signatures, signing parties or customer signing events. v3 adds only: crm_document_conversion. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /blocks", + "POST /blocks", + "PUT /blocks/:id", + "DELETE /blocks/:id", + "GET /", + "POST /", + "GET /:id", + "GET /:id/documents", + "PUT /:id", + "GET /:id/send-preview", + "POST /:id/send", + "POST /:id/cancel", + "POST /:id/convert-to-event", + "POST /:id/convert-to-invoice", + "POST /:id/resend-signed", + "POST /:id/restamp-signatures", + "GET /:id/signers", + "PUT /:id/signers", + "POST /:id/signers/:signerId/resend", + "POST /:id/signers/:signerId/remind", + "GET /:id/signing-evidence", + "POST /:id/countersign", + "POST /:id/upload-signed-pdf", + "GET /:id/paper-signature-coverage", + "GET /:id/pdf", + "GET /:id/signed-pdf", + "GET /:id/certificate", + "GET /:id/history", + "GET /:id/audit-trail", + "GET /:id/verify-integrity", + "GET /:id/preview" + ] + }, + "adminCssTemplates.js": { + "decision": "configuration", + "signals": [ + "custom_css" + ], + "reason": "Only existence of enabled applied CSS and locally observed application, not editing/viewing templates or any CSS text.", + "route_signatures": [ + "GET /", + "GET /enabled", + "GET /:slotNumber", + "PUT /:slotNumber", + "POST /:slotNumber/reset" + ] + }, + "adminCustomers.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_hours", + "customer_portal", + "crm_combined_billing", + "crm_monthly_billing_manual" + ], + "reason": "Successful admin CRM/hour-entry/invitation operations only. No customer/account names, IDs, rates, billed hours, payment state or portal behavior. v3 adds only: crm_combined_billing, crm_monthly_billing_manual. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface. Customer-document writes (v5) only count as crm: no file names, sizes, counts or review outcomes. Customer groups (#1443) are admin-side organisation: the catalogue and the assignment routes report crm only — no group names, colours, membership or counts.", + "route_signatures": [ + "GET /groups", + "POST /groups", + "POST /groups/reorder", + "POST /groups/bulk-assign", + "PUT /groups/:groupId", + "DELETE /groups/:groupId", + "PUT /:id/groups", + "GET /", + "GET /search", + "GET /invitations", + "POST /invite", + "DELETE /invitations/:id", + "POST /", + "POST /:id/send-invite", + "GET /:id/history", + "GET /:id", + "PUT /:id", + "POST /:id/deactivate", + "POST /:id/reactivate", + "POST /:id/erase", + "POST /:id/password-reset", + "PUT /:id/events", + "GET /hour-entries/unbilled-summary", + "GET /:id/hour-entries", + "POST /:id/hour-entries", + "PUT /:id/hour-entries/:entryId", + "DELETE /:id/hour-entries/:entryId", + "POST /:id/hour-entries/bill", + "POST /:id/bill-combined", + "POST /:id/trigger-monthly-bill", + "GET /:id/monthly-draft", + "GET /:id/activity", + "GET /:id/documents", + "POST /:id/documents", + "PATCH /:id/documents/:docId", + "POST /:id/documents/:docId/share", + "POST /:id/documents/:docId/unshare", + "POST /:id/documents/:docId/review", + "GET /:id/documents/:docId/download", + "DELETE /:id/documents/:docId", + "GET /:id/document-requests", + "POST /:id/document-requests", + "PATCH /:id/document-requests/:requestId", + "DELETE /:id/document-requests/:requestId" + ] + }, + "adminDashboard.js": { + "decision": "partial", + "signals": [ + "analytics_dashboard" + ], + "reason": "Admin analytics capability endpoint only; no stats, activities, health/CRM polls, underlying visitor data or dashboard values.", + "route_signatures": [ + "GET /stats", + "GET /activity", + "GET /health", + "GET /analytics", + "GET /crm-stats" + ] + }, + "adminDatabaseBackup.js": { + "decision": "partial", + "signals": [ + "backup", + "database_backup" + ], + "reason": "Admin database-backup initiation plus schedule-enabled boolean, no file data/history.", + "route_signatures": [ + "GET /status", + "PUT /config", + "POST /backup", + "GET /progress", + "GET /history", + "DELETE /cleanup", + "POST /test", + "GET /checksums" + ] + }, + "adminDeals.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_installments" + ], + "reason": "Admin installment-plan changes only. No actual plans, invoice links, amounts, paid states or deal reporting.", + "route_signatures": [ + "GET /:uuid/documents", + "PUT /:uuid/installment-plan" + ] + }, + "adminDev.js": { + "decision": "excluded", + "signals": [], + "reason": "Bootstrap, passwords/MFA/session/profile, per-person notifications, developer helpers and operational health/update/log polling are outside the prioritization purpose.", + "route_signatures": [ + "GET /email-templates", + "POST /send-test-email" + ] + }, + "adminEmail.js": { + "decision": "partial", + "signals": [ + "messaging", + "incoming_mail", + "smtp", + "email_template_editing", + "email_webhook", + "reminder_emails", + "email_template_delivery" + ], + "reason": "v5 records real admin template content changes separately from successful real template mail transport acceptance, including background sends; no preview/test/no-op markers or message details.", + "route_signatures": [ + "GET /config", + "POST /config", + "GET /incoming-config", + "POST /incoming-config", + "POST /incoming-config/folders", + "POST /incoming-config/test", + "POST /incoming-config/roundtrip", + "POST /incoming-config/poll", + "GET /received", + "GET /received/:id", + "POST /item/:kind/:id/state", + "DELETE /item/:kind/:id", + "GET /accounts", + "GET /identities", + "POST /accounts", + "POST /accounts/test", + "POST /test", + "POST /flush-queue", + "GET /queue", + "GET /queue/:id", + "POST /send", + "GET /templates", + "GET /templates/:key", + "PUT /templates/:key", + "POST /templates", + "POST /templates/:key/preview" + ] + }, + "adminEventRename.js": { + "decision": "partial", + "signals": [ + "galleries" + ], + "reason": "Successful rename only, not validate-rename. No former/new names or identifiers.", + "route_signatures": [ + "POST /:eventId/rename", + "POST /:eventId/validate-rename" + ] + }, + "adminEvents/archiveBulk.js": { + "decision": "partial", + "signals": [ + "galleries", + "archive_management", + "photo_exports" + ], + "reason": "Admin archive/delete/restore/download initiation only; filenames, histories, storage sizes and polling excluded.", + "route_signatures": [ + "POST /:id/archive", + "POST /bulk-archive", + "POST /bulk-delete" + ] + }, + "adminEvents/crud.js": { + "decision": "partial", + "signals": [ + "galleries", + "gallery_guest_uploads", + "gallery_downloads_restricted", + "gallery_client_access", + "gallery_watermarks", + "gallery_reveal", + "gallery_expiration", + "gallery_sharing", + "custom_css", + "gallery_capture_date_sort" + ], + "reason": "Admin creation/edit/publish etc. set galleries; sharing has its own fixed key. Guest/download/protection/reveal/expiry are configuration only; themes contribute controlled layouts and CSS presence. No gallery metadata or guest action history. v3 adds only: gallery_capture_date_sort. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "POST /", + "GET /", + "GET /:id", + "POST /:id/send-gallery-email", + "POST /:id/publish", + "POST /:id/duplicate", + "PUT /:id", + "POST /:id/reveal", + "DELETE /:id", + "POST /:id/toggle-status", + "POST /:id/extend" + ] + }, + "adminEvents/downloadLimit.js": { + "decision": "excluded", + "signals": [], + "reason": "Download limit usage and reset for one gallery (issue 1560): no product-usage middleware, counters or report triggers; the usage numbers stay on the install.", + "route_signatures": [ + "GET /:id/download-limit", + "POST /:id/download-limit/reset" + ] + }, + "adminEvents/downloadResolutions.js": { + "decision": "configuration", + "signals": [ + "download_resolution_picker" + ], + "reason": "Only whether a picker is configured globally or in a gallery. No chosen resolution, download event or counts.", + "route_signatures": [ + "GET /:id/download-resolutions", + "PATCH /:id/download-resolutions" + ] + }, + "adminEvents/faces.js": { + "decision": "partial", + "signals": [ + "face_recognition" + ], + "reason": "Effective flag plus successful admin faces/people operation. No health polling, embeddings, names, groups, detections or visitor searches.", + "route_signatures": [ + "GET /faces/health", + "GET /:id/faces", + "PATCH /:id/faces", + "GET /:id/people", + "GET /:id/people/suggestions", + "POST /:id/people/suggestions/dismiss", + "PATCH /:id/people/:personId", + "POST /:id/people/merge", + "POST /:id/people/:personId/split", + "GET /:id/people/:personId/faces", + "POST /:id/faces/rescan", + "POST /:id/faces/recluster", + "GET /faces/auto-categories", + "PUT /faces/auto-categories", + "POST /:id/faces/categorize", + "DELETE /:id/faces/categorize", + "DELETE /:id/faces" + ] + }, + "adminEvents/helpers.js": { + "decision": "composition", + "signals": [], + "reason": "Router composition / helpers; decisions are recorded for each mounted family.", + "route_signatures": [] + }, + "adminEvents/index.js": { + "decision": "composition", + "signals": [], + "reason": "Router composition / helpers; decisions are recorded for each mounted family.", + "route_signatures": [] + }, + "adminEvents/listSort.js": { + "decision": "composition", + "signals": [], + "reason": "ORDER BY construction for the admin events list; declares no routes and reads no personal data.", + "route_signatures": [] + }, + "adminEvents/logo.js": { + "decision": "partial", + "signals": [ + "branding_editing" + ], + "reason": "Successful admin logo operation only; image/filename/content excluded.", + "route_signatures": [ + "POST /:id/logo", + "DELETE /:id/logo" + ] + }, + "adminEvents/passwordRecovery.js": { + "decision": "partial", + "signals": [ + "galleries" + ], + "reason": "Admin reveal of a stored gallery password (#1271); the password, the event and who looked are never reported, only that galleries exist.", + "route_signatures": [ + "GET /:id/password-status", + "GET /:id/password" + ] + }, + "adminEvents/qr.js": { + "decision": "partial", + "signals": [ + "gallery_sharing" + ], + "reason": "Admin QR generation only; no scans, tokens or URLs.", + "route_signatures": [ + "GET /:id/qr", + "GET /:id/qr-print" + ] + }, + "adminEvents/resets.js": { + "decision": "partial", + "signals": [ + "galleries", + "gallery_sharing" + ], + "reason": "Admin gallery reset/sharing capability only; no password, recipient, token or reset statistics.", + "route_signatures": [ + "POST /:id/reset-password", + "POST /:id/resend-email" + ] + }, + "adminEvents/slideshow.js": { + "decision": "partial", + "signals": [ + "slideshow" + ], + "reason": "Admin generate/disable/configure only, never kiosk viewers or slide advances.", + "route_signatures": [ + "POST /:id/slideshow/generate", + "POST /:id/slideshow/disable", + "PATCH /:id/slideshow" + ] + }, + "adminEventTypes.js": { + "decision": "partial", + "signals": [ + "event_type_editing" + ], + "reason": "Admin event-type CRUD; preset contents/names excluded.", + "route_signatures": [ + "GET /", + "GET /active", + "GET /:id", + "POST /", + "PUT /:id", + "DELETE /:id", + "POST /reorder" + ] + }, + "adminExpenses.js": { + "decision": "partial", + "signals": [ + "accounting", + "accounting_expenses", + "accounting_incoming_invoices" + ], + "reason": "Admin expense/inbound-invoice operations; no financial values, suppliers, mileage/location, dates, receipt files or OCR text.", + "route_signatures": [ + "GET /categories", + "GET /categories/:id/history", + "POST /categories", + "PATCH /categories/:id", + "DELETE /categories/:id", + "POST /inbound", + "GET /inbound", + "GET /inbound/pending-summary", + "POST /inbound/bill-pending", + "GET /inbound/by-customer/:customerAccountId", + "GET /inbound/:id/file", + "GET /inbound/:id/page/:n", + "GET /inbound/:id/history", + "GET /inbound/:id", + "PATCH /inbound/:id", + "POST /inbound/:id/categorize", + "POST /inbound/:id/rebill", + "POST /inbound/:id/supplier-payment", + "GET /", + "POST /", + "GET /:id/proof", + "GET /:id/history", + "GET /:id", + "PATCH /:id", + "POST /:id/invoice", + "POST /:id/paid" + ] + }, + "adminExternalMedia.js": { + "decision": "partial", + "signals": [ + "share_mounts" + ], + "reason": "Only admin import operation; status/list/browse are not use. Snapshot checks external-path presence, never reports a path.", + "route_signatures": [ + "GET /list", + "POST /events/:id/import-external" + ] + }, + "adminFeatureFlags.js": { + "decision": "configuration", + "signals": [ + "crm", + "crm_quotes", + "crm_invoices", + "crm_contracts", + "crm_projects", + "crm_calendar", + "crm_hours", + "customer_portal", + "accounting", + "workflows", + "newsletters", + "face_recognition", + "slideshow", + "transfers", + "messaging", + "reminder_emails", + "accounting_incoming_invoices", + "accounting_expenses", + "accounting_tax_report", + "accounting_ledger", + "admin_management", + "analytics_dashboard" + ], + "reason": "Only allowlisted effective capability booleans. No marker from reading or saving feature flags. Disabled roadmap/developer flags excluded.", + "route_signatures": [ + "GET /", + "PUT /" + ] + }, + "adminFeedback.js": { + "decision": "partial", + "signals": [ + "feedback_moderation", + "gallery_feedback_likes", + "gallery_feedback_ratings", + "gallery_feedback_comments", + "gallery_feedback_favorites", + "gallery_feedback_reactions", + "gallery_feedback_color_labels", + "gallery_guest_accounts" + ], + "reason": "Admin moderation/word-filter operations only. Visitor feedback is not observed. Master-enabled per-gallery feedback-option booleans only; no contents, ratings, likes, colors, identities or word lists.", + "route_signatures": [ + "GET /events/:eventId/feedback-settings", + "PUT /events/:eventId/feedback-settings", + "GET /events/:eventId/feedback", + "PUT /feedback/:feedbackId/:action", + "DELETE /feedback/:feedbackId", + "GET /events/:eventId/feedback-analytics", + "GET /events/:eventId/feedback/export", + "GET /feedback/pending-moderation", + "GET /word-filters", + "POST /word-filters", + "PUT /word-filters/:id", + "DELETE /word-filters/:id" + ] + }, + "adminGuests.js": { + "decision": "partial", + "signals": [ + "guest_management" + ], + "reason": "Admin guest management/export initiation only. No guest names, invitations, tokens, contact data, guest counts or visitor interactions.", + "route_signatures": [ + "GET /events/:eventId/guests", + "GET /events/:eventId/guests/aggregate", + "GET /events/:eventId/guests/invites", + "POST /events/:eventId/guests/invites", + "DELETE /events/:eventId/guests/invites/:inviteId", + "GET /events/:eventId/guests/export-all", + "GET /events/:eventId/guests/:guestId", + "GET /events/:eventId/guests/:guestId/export", + "DELETE /events/:eventId/guests/:guestId", + "POST /events/:eventId/guests/:keepId/merge" + ] + }, + "adminImageSecurity.js": { + "decision": "configuration", + "signals": [ + "gallery_image_protection" + ], + "reason": "Only gallery/global technical protection configuration existence. No security events, blocked IPs, request counts, threat scores or admin monitoring access.", + "route_signatures": [ + "GET /settings", + "PUT /settings", + "GET /dashboard", + "GET /logs", + "GET /events/:eventId/access-logs", + "POST /block-ip", + "DELETE /logs/cleanup", + "GET /export" + ] + }, + "adminInvoices.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_invoices", + "crm_invoice_import" + ], + "reason": "Admin invoice operations only; no amounts, VAT/customer/payment values or payment-check responses. v3 adds only: crm_invoice_import. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /", + "GET /:id/history", + "GET /:id", + "POST /", + "POST /import", + "PUT /:id", + "GET /:id/rebill-proofs", + "POST /:id/send", + "POST /:id/mark-paid", + "POST /:id/send-reminder", + "POST /:id/test-payment-check", + "POST /:id/reissue", + "POST /:id/release-for-delivery", + "POST /:id/cancel", + "GET /:id/pdf", + "POST /preview", + "GET /:id/payment-log" + ] + }, + "adminLedger.js": { + "decision": "partial", + "signals": [ + "accounting", + "accounting_ledger" + ], + "reason": "Admin ledger-account/VAT/mapping edits and ledger export initiation only; no account/currency/VAT identifiers or exported records.", + "route_signatures": [ + "GET /accounts", + "GET /accounts/:id/history", + "POST /accounts", + "PATCH /accounts/:id", + "DELETE /accounts/:id", + "GET /vat-codes", + "GET /vat-codes/:id/history", + "POST /vat-codes", + "PATCH /vat-codes/:id", + "DELETE /vat-codes/:id", + "GET /mappings", + "PATCH /mappings/category/:id", + "PATCH /mappings/settings", + "GET /export" + ] + }, + "adminNewsletters.js": { + "decision": "partial", + "signals": [ + "newsletters" + ], + "reason": "Admin campaign changes/test/queue/cancel only. Recipient resolution, previews, subscriptions/unsubscribes, delivery/open/click data and automatic sending excluded.", + "route_signatures": [ + "GET /", + "GET /:id", + "GET /:id/recipients", + "POST /", + "PUT /:id", + "DELETE /:id", + "POST /:id/preview", + "POST /:id/recipients/resolve", + "POST /:id/test", + "POST /:id/queue", + "POST /:id/cancel" + ] + }, + "adminNotifications.js": { + "decision": "excluded", + "signals": [], + "reason": "Bootstrap, passwords/MFA/session/profile, per-person notifications, developer helpers and operational health/update/log polling are outside the prioritization purpose.", + "route_signatures": [ + "GET /", + "PUT /:id/read", + "PUT /read-all", + "DELETE /clear-all" + ] + }, + "adminPhotoDimensions.js": { + "decision": "partial", + "signals": [ + "photo_processing" + ], + "reason": "Admin repair/regenerate/configuration initiation, never status polling or processing totals.", + "route_signatures": [ + "POST /repair-dimensions", + "GET /repair-dimensions/status", + "POST /repair-capture-dates", + "GET /repair-capture-dates/status", + "POST /repair-orientation", + "GET /repair-orientation/status", + "POST /repair-credits", + "GET /repair-credits/status" + ] + }, + "adminPhotoExport.js": { + "decision": "partial", + "signals": [ + "photo_exports", + "photo_xmp_export" + ], + "reason": "Admin export initiation only; export filters, selected files, sizes and contents excluded. v3 adds only: photo_xmp_export. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /:eventId/filtered", + "GET /:eventId/filter-summary", + "POST /:eventId/export", + "GET /export-formats" + ] + }, + "adminPhotos.js": { + "decision": "partial", + "signals": [ + "photo_management", + "photo_exports", + "photo_processing", + "video_uploads", + "camera_raw_uploads", + "s3_storage", + "s3_photo_storage", + "photo_replacement", + "photo_admin_marks" + ], + "reason": "Successful admin edits/exports and accepted upload evidence only. Chunk init/status, failed uploads and public downloads excluded. Only video/RAW/S3 booleans survive, never file metadata/EXIF/content. v3 adds only: photo_replacement, photo_admin_marks. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "POST /:eventId/upload", + "GET /uploads/:upload_id/status", + "GET /uploads/:upload_id/stream", + "POST /photos/:photoId/retry", + "DELETE /:eventId/photos/:photoId", + "PUT /:eventId/photos/:photoId/mark", + "PATCH /:eventId/photos/:photoId", + "POST /:eventId/photos/bulk-delete", + "POST /:eventId/photos/bulk-update", + "GET /:eventId/photos/credits", + "PUT /:eventId/photos/:photoId/credit", + "GET /:eventId/photos/:photoId/download", + "GET /:eventId/photos", + "GET /:eventId/photo/:photoId", + "GET /:eventId/thumbnail/:photoId", + "GET /:eventId/preview/:photoId", + "GET /:eventId/debug", + "POST /:eventId/chunked-upload/init", + "POST /:eventId/chunked-upload/:uploadId/chunk/:chunkIndex", + "POST /:eventId/chunked-upload/:uploadId/complete", + "GET /:eventId/chunked-upload/:uploadId/status", + "DELETE /:eventId/chunked-upload/:uploadId" + ] + }, + "adminProjects.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_projects" + ], + "reason": "Admin project operations only; project/person names, business performance, metadata and totals excluded.", + "route_signatures": [ + "GET /", + "POST /", + "GET /:id", + "PUT /:id", + "POST /:id/events", + "POST /:id/quotes", + "POST /:id/contracts", + "GET /:id/overview", + "GET /email/:emailId/preview", + "POST /email/:emailId/resend", + "POST /email/:emailId/cancel", + "POST /email/:emailId/retry", + "POST /email/:emailId/send-now" + ] + }, + "adminQuotes.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_quotes", + "document_templates", + "crm_document_conversion" + ], + "reason": "Admin quote/preset operations only; no quote content, prices, customer acceptance or signatures. v3 adds only: crm_document_conversion. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /", + "GET /:id/history", + "GET /:id", + "POST /", + "PUT /:id", + "POST /:id/send", + "POST /:id/duplicate", + "POST /:id/recalculate-rates", + "POST /:id/accept", + "POST /:id/add-ons", + "POST /:id/decline", + "POST /:id/reissue", + "POST /:id/convert", + "POST /:id/convert-to-invoice", + "POST /:id/convert-to-contract", + "GET /:id/pdf", + "POST /preview", + "GET /presets/line-items", + "POST /presets/line-items", + "PUT /presets/line-items/:id", + "DELETE /presets/line-items/:id", + "GET /presets/payment-terms", + "POST /presets/payment-terms", + "PUT /presets/payment-terms/:id", + "DELETE /presets/payment-terms/:id", + "GET /presets/payment-net-days", + "POST /presets/payment-net-days", + "PUT /presets/payment-net-days/:id", + "DELETE /presets/payment-net-days/:id", + "GET /presets/payment-timing", + "POST /presets/payment-timing", + "PUT /presets/payment-timing/:id", + "DELETE /presets/payment-timing/:id" + ] + }, + "adminRestore.js": { + "decision": "partial", + "signals": [ + "restore" + ], + "reason": "Admin restore initiation only, never file selection, content, progress, errors or timing.", + "route_signatures": [ + "GET /status", + "POST /validate", + "POST /start", + "GET /progress", + "GET /run/:id", + "GET /run/:id/report", + "GET /available-backups", + "POST /list-backups", + "GET /settings", + "PUT /settings" + ] + }, + "adminRoles.js": { + "decision": "partial", + "signals": [ + "admin_management" + ], + "reason": "Admin account/role management capability; no names, permissions, role labels, password reset operations or active-user counts. Auth/self-profile endpoints excluded.", + "route_signatures": [ + "GET /", + "GET /permissions", + "POST /", + "POST /:id/clone", + "PUT /:id", + "DELETE /:id" + ] + }, + "adminSettings.js": { + "decision": "partial", + "signals": [ + "custom_css", + "oauth", + "smtp", + "backup", + "s3_storage", + "video_uploads", + "camera_raw_uploads", + "public_site", + "branding_editing", + "seo_editing", + "slideshow", + "download_resolution_picker", + "gallery_watermarks", + "database_backup", + "s3_auto_import", + "download_original_filenames" + ], + "reason": "Only specified configuration presence/booleans and explicit branding/SEO/slideshow operations. Generic settings reads, security policies, passwords, storage data, SMTP/OIDC credentials, custom HTML/CSS/SEO values excluded. v3 adds only: s3_auto_import, download_original_filenames. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /", + "GET /:type", + "GET /customer-surface", + "PUT /customer-surface", + "PUT /accounting", + "PUT /slideshow", + "GET /transfers", + "PUT /transfers", + "GET /downloads", + "PUT /downloads", + "GET /sso", + "PUT /sso", + "POST /sso/test", + "GET /:type", + "GET /password/complexity", + "PUT /branding", + "POST /logo", + "DELETE /logo", + "POST /branding/watermark-logo", + "PUT /theme", + "PUT /general", + "PUT /security", + "PUT /analytics", + "PUT /seo", + "GET /storage/info", + "POST /favicon", + "PUT /security/rate-limit", + "GET /public-site/default", + "POST /public-site/reset" + ] + }, + "adminShortUrls.js": { + "decision": "partial", + "signals": [ + "gallery_sharing", + "short_links" + ], + "reason": "Admin short-link creation/deletion only; link/token/click metadata excluded.", + "route_signatures": [ + "GET /events/:eventId/short-urls", + "POST /events/:eventId/short-urls", + "DELETE /short-urls/:id" + ] + }, + "adminSystem.js": { + "decision": "excluded", + "signals": [], + "reason": "Bootstrap, passwords/MFA/session/profile, per-person notifications, developer helpers and operational health/update/log polling are outside the prioritization purpose.", + "route_signatures": [ + "GET /version", + "GET /updates", + "GET /updates/whatsnew", + "POST /updates/whatsnew/seen", + "GET /updates/changelog", + "GET /updates/instructions", + "GET /status", + "GET /database", + "GET /updates/notifications", + "PUT /updates/notifications", + "POST /updates/notifications/send", + "POST /updates/notifications/check" + ] + }, + "adminSystemHealth.js": { + "decision": "excluded", + "signals": [], + "reason": "Bootstrap, passwords/MFA/session/profile, per-person notifications, developer helpers and operational health/update/log polling are outside the prioritization purpose.", + "route_signatures": [ + "GET /backup-integrity", + "GET /backup-coverage", + "GET /failures", + "POST /failures/email/:id/retry", + "DELETE /failures/email/:id" + ] + }, + "adminTaxReport.js": { + "decision": "partial", + "signals": [ + "accounting", + "accounting_tax_report" + ], + "reason": "Admin tax report generation/export only; no totals, dates, tax regimes, geography or currency.", + "route_signatures": [ + "GET /", + "GET /pdf", + "GET /csv" + ] + }, + "adminThumbnails.js": { + "decision": "partial", + "signals": [ + "photo_processing" + ], + "reason": "Admin repair/regenerate/configuration initiation, never status polling or processing totals.", + "route_signatures": [ + "GET /settings", + "PUT /settings", + "POST /regenerate", + "POST /regenerate-previews", + "GET /regenerate/status" + ] + }, + "adminTransfers.js": { + "decision": "partial", + "signals": [ + "transfers", + "transfer_upload_links" + ], + "reason": "Admin transfer CRUD/files/link and upload-code management/email resend/download only. Public recipients, received-file data, upload and download statistics excluded. v3 adds only: transfer_upload_links. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /", + "POST /", + "GET /:id", + "PATCH /:id", + "DELETE /:id", + "POST /:id/files", + "DELETE /:id/files/:fileId", + "POST /:id/upload-files", + "DELETE /:id/extra-files/:extraId", + "GET /:id/extra-files/:extraId/download", + "POST /:id/upload-code", + "DELETE /:id/upload-code", + "POST /:id/resend", + "GET /:id/download", + "GET /:id/uploads/:uploadId/download" + ] + }, + "adminUsage.js": { + "decision": "excluded", + "signals": [], + "reason": "Consent, prompt acknowledgement, inspection, export, feedback, voting, deletion and abandoning an unsignable deletion are explicit protocol operations; not product-use signals. Activity only triggers a due fixed report.", + "route_signatures": [ + "POST /activity", + "GET /", + "POST /dismiss", + "POST /prompt-seen", + "POST /enable", + "POST /consent", + "POST /disable", + "POST /abandon", + "POST /retry", + "GET /preview", + "GET /export", + "PUT /feedback-preferences", + "POST /feedback", + "POST /vote", + "POST /portal-session" + ] + }, + "adminUsers.js": { + "decision": "partial", + "signals": [ + "admin_management" + ], + "reason": "Admin account/role management capability; no names, permissions, role labels, password reset operations or active-user counts. Auth/self-profile endpoints excluded.", + "route_signatures": [ + "GET /me/permissions", + "GET /", + "GET /roles", + "GET /invitations", + "POST /invite", + "DELETE /invitations/:id", + "GET /:id", + "PUT /:id", + "POST /:id/deactivate", + "POST /:id/activate", + "DELETE /:id", + "POST /:id/reset-password" + ] + }, + "adminVatCodes.js": { + "decision": "excluded", + "signals": [], + "reason": "Business identity/bank/tax-address configuration and VAT-code helper surface are not separate usage signals. Billing/accounting capabilities are covered without profiling the business.", + "route_signatures": [ + "GET /" + ] + }, + "adminWebhooks.js": { + "decision": "partial", + "signals": [ + "webhooks" + ], + "reason": "Active configuration existence. The manual test/replay enqueue (202) was the v2-v5 evidence of use and is recorded only under those consents; under usage.v6 the delivery worker records the successful delivery itself (issue 1740). Actual network results/subscriptions/destinations excluded.", + "route_signatures": [ + "GET /", + "POST /", + "GET /:id", + "PUT /:id", + "DELETE /:id", + "POST /:id/test", + "GET /:id/deliveries", + "GET /:id/deliveries/:deliveryId", + "POST /:id/deliveries/:deliveryId/replay" + ] + }, + "adminWhatsapp.js": { + "decision": "partial", + "signals": [ + "whatsapp" + ], + "reason": "Effective configured sender and successful manual test only. No automated deliveries, phone numbers, templates or delivery statuses.", + "route_signatures": [ + "GET /config", + "PUT /config", + "POST /test" + ] + }, + "adminWorkflows.js": { + "decision": "partial", + "signals": [ + "workflows", + "workflow_automation_enabled" + ], + "reason": "Admin workflow authoring/approval/test initiation only. Runtime triggers, payloads, execution frequency/results and public approvals excluded. v3 adds only: workflow_automation_enabled. Exact definitions are in features.v4.json; configuration-only signals never observe the public surface.", + "route_signatures": [ + "GET /approvals", + "POST /approvals/:id/:action", + "GET /runs/:runId/steps", + "GET /:id/runs", + "POST /:id/test-run", + "GET /", + "GET /:id", + "POST /", + "PUT /:id", + "PATCH /:id/enabled", + "DELETE /:id" + ] + }, + "analyticsTrackerProxy.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [] + }, + "auth.js": { + "decision": "partial", + "signals": [ + "oauth" + ], + "reason": "Only successful admin OIDC callback sets oauth. Password/gallery authentication, MFA, account claims and provider details excluded.", + "route_signatures": [ + "POST /admin/login", + "POST /admin/login/mfa", + "POST /logout", + "POST /gallery/verify", + "POST /gallery/:slug/client-login", + "POST /gallery/share-login", + "POST /gallery/logout", + "GET /session", + "POST /password-strength", + "GET /admin/sso/login", + "GET /admin/sso/callback" + ] + }, + "customer.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /events", + "GET /events/:slug/access-token", + "GET /profile", + "PUT /profile", + "GET /profile/marketing", + "PUT /profile/marketing", + "POST /profile/password", + "GET /quotes", + "GET /invoices", + "GET /quotes/:id/pdf", + "GET /invoices/:id/pdf", + "GET /contracts", + "POST /contracts/:id/signing-access", + "GET /contracts/:id/pdf", + "GET /contracts/:id/certificate", + "GET /contracts/:id", + "POST /contracts/:id/sign", + "POST /contracts/:id/upload-signed-pdf", + "GET /quotes/:id", + "POST /quotes/:id/respond", + "GET /dashboard", + "GET /events/:slug/overview", + "GET /documents", + "POST /documents", + "GET /document-requests", + "GET /documents/:id", + "DELETE /documents/:id", + "GET /documents/:id/download" + ] + }, + "customerAuth.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /login", + "POST /logout", + "GET /session", + "GET /invite/:token", + "POST /accept-invite", + "GET /password-reset/:token", + "POST /password-reset" + ] + }, + "gallery.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [] + }, + "galleryFeedback.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/feedback-settings", + "GET /:slug/photos/:photoId/feedback", + "POST /:slug/photos/:photoId/feedback", + "GET /:slug/feedback-summary", + "GET /:slug/my-feedback" + ] + }, + "galleryGuests.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:slug/guest", + "GET /:slug/guest/me", + "DELETE /:slug/guest/me", + "POST /:slug/guest/recover", + "POST /:slug/guest/verify", + "POST /:slug/guest/redeem" + ] + }, + "publicCMS.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /pages/:slug" + ] + }, + "publicContracts.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token", + "GET /:token/attachments/:attachmentId", + "POST /:token/sign", + "POST /:token/upload-signed-pdf", + "GET /:token/pdf" + ] + }, + "publicDocumentVerification.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:token/verification", + "POST /:token/verification/confirm" + ] + }, + "publicFonts.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /" + ] + }, + "publicNewsletter.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /unsubscribe/:token", + "POST /unsubscribe/:token" + ] + }, + "publicPaymentCheck.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token", + "POST /:token" + ] + }, + "publicQuotes.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token", + "GET /:token/totals", + "POST /:token/respond" + ] + }, + "publicSettings.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /" + ] + }, + "publicTransfer.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token", + "GET /:token/download", + "GET /:token/download/:fileId" + ] + }, + "publicTransferUpload.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token", + "POST /:token" + ] + }, + "publicWorkflowApprovals.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:token/:action", + "POST /:token/:action" + ] + }, + "setup.js": { + "decision": "excluded", + "signals": [], + "reason": "Bootstrap, passwords/MFA/session/profile, per-person notifications, developer helpers and operational health/update/log polling are outside the prioritization purpose.", + "route_signatures": [ + "GET /status", + "POST /verify-token", + "POST /admin", + "POST /complete" + ] + }, + "v1/events.js": { + "decision": "partial", + "signals": [ + "api_integration" + ], + "reason": "Single bit after successful admin-owned scoped API authentication. No request/response values; API requests do not trigger reports.", + "route_signatures": [ + "POST /events", + "GET /events", + "GET /event-types", + "GET /events/:id", + "POST /events/:id/photos", + "GET /events/:id/share-link", + "GET /events/:id/photos", + "GET /events/:id/photos/download", + "GET /events/:id/photos/:photoId/download", + "GET /events/:id/photos/:photoId/preview" + ] + }, + "gallery/downloads.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/download/:photoId", + "GET /:slug/download-all", + "POST /:slug/download-selected", + "POST /:slug/download-jobs", + "GET /:slug/download-jobs/:token", + "GET /:slug/download-jobs/:token/file" + ] + }, + "gallery/media.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:slug/photo/:photoId/view", + "GET /:slug/photo/:photoId", + "GET /:slug/thumbnail/:photoId", + "GET /:slug/hero/:photoId", + "GET /:slug/preview/:photoId" + ] + }, + "gallery/metadata.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /resolve/:identifier", + "GET /:slug/verify-token/:token", + "GET /:slug/info" + ] + }, + "gallery/photos.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/photos", + "GET /:slug/people", + "PATCH /:slug/photos/:photoId/visibility", + "PATCH /:slug/photos/visibility/bulk" + ] + }, + "gallery/slideshow.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/show/:token/session", + "GET /:slug/show/:token/state" + ] + }, + "gallery/stats.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/stats" + ] + }, + "gallery/styles.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /:slug/css-template" + ] + }, + "gallery/uploads.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "POST /:eventId/upload", + "GET /:slug/uploads/status" + ] + }, + "adminQuoteCatalog.js": { + "decision": "partial", + "signals": [ + "crm", + "crm_quotes", + "document_templates" + ], + "reason": "Admin quote catalogue and template operations only (packages, promotions, text blocks, templates, quote-from-template). No quote content, prices, customer data or text-block wording.", + "route_signatures": [ + "GET /packages", + "POST /packages", + "PUT /packages/:id", + "DELETE /packages/:id", + "GET /promotions", + "POST /promotions", + "PUT /promotions/:id", + "DELETE /promotions/:id", + "GET /text-blocks", + "POST /text-blocks", + "PUT /text-blocks/:id", + "DELETE /text-blocks/:id", + "GET /templates", + "POST /templates", + "GET /templates/:id", + "PUT /templates/:id", + "DELETE /templates/:id", + "POST /templates/:id/publish", + "POST /templates/:id/quotes", + "POST /templates/from-quote/:quoteId" + ] + }, + "adminContractTemplates.js": { + "decision": "partial", + "signals": [ + "document_templates" + ], + "reason": "Admin contract template operations only (drafts, pre-publication check, publishing, versions, default, preview). No clause wording, customer data or contract content.", + "route_signatures": [ + "GET /", + "GET /placeholders", + "POST /", + "GET /:id", + "GET /:id/versions/:version", + "PUT /:id/draft", + "POST /:id/publish-check", + "POST /:id/publish", + "POST /:id/versions/:version/draft", + "POST /:id/duplicate", + "POST /:id/archive", + "POST /:id/restore", + "POST /:id/default", + "GET /:id/preview-content", + "POST /:id/preview" + ] + }, + "adminDocumentAttachments.js": { + "decision": "partial", + "signals": [ + "document_templates" + ], + "reason": "Admin attachment-library operations only (upload, archive, restore, download). No file names, contents or checksums.", + "route_signatures": [ + "GET /", + "POST /", + "GET /:id/download", + "POST /:id/archive", + "POST /:id/restore" + ] + }, + "publicContractSigning.js": { + "decision": "excluded", + "signals": [], + "reason": "Public/customer/gallery/visitor surface or existing optional third-party analytics proxy: no product-usage middleware, callbacks, counters or report triggers.", + "route_signatures": [ + "GET /invite/:token", + "POST /invite/:token/code", + "POST /invite/:token/verify", + "GET /session", + "GET /session/pdf", + "GET /session/attachments/:attachmentId", + "POST /session/sign", + "POST /session/details", + "POST /session/decline", + "POST /session/upload-signed-pdf" + ] + } + }, + "feature_flags": { + "accounting": { + "signals": [ + "accounting", + "accounting_ledger" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "analytics": { + "signals": [ + "analytics_dashboard" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "bills": { + "signals": [ + "crm_invoices", + "crm_invoice_import", + "crm_monthly_billing_manual" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "calendar": { + "signals": [ + "crm_calendar" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "calendarBooking": { + "signals": [], + "reason": "Excluded: disabled roadmap placeholder, not an implemented booking capability." + }, + "clients": { + "signals": [ + "crm" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "contracts": { + "signals": [ + "crm_contracts" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "crmDevelopment": { + "signals": [], + "reason": "Excluded: internal development/test helpers, not product adoption." + }, + "customerPortal": { + "signals": [ + "customer_portal" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "documents": { + "signals": [], + "reason": "Excluded: customer document exchange is not reported as its own capability; no flag value, file names, sizes or counts." + }, + "expenses": { + "signals": [ + "accounting_expenses" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "faces": { + "signals": [ + "face_recognition" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "galleries": { + "signals": [ + "galleries" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "hoursLogging": { + "signals": [ + "crm_hours" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "incomingInvoices": { + "signals": [ + "accounting_incoming_invoices", + "crm_combined_billing" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "incomingMail": { + "signals": [ + "incoming_mail" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "messaging": { + "signals": [ + "messaging" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "newsletters": { + "signals": [ + "newsletters" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "projects": { + "signals": [ + "crm_projects" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "quotes": { + "signals": [ + "crm_quotes", + "crm_document_conversion" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "reminderEmails": { + "signals": [ + "reminder_emails" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "slideshow": { + "signals": [ + "slideshow" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "taxReport": { + "signals": [ + "accounting_tax_report" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "transfers": { + "signals": [ + "transfers", + "transfer_upload_links" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "userManagement": { + "signals": [ + "admin_management" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "whatsapp": { + "signals": [ + "whatsapp" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + }, + "workflows": { + "signals": [ + "workflows", + "workflow_automation_enabled" + ], + "reason": "Only effective configuration boolean; dependency rules apply, no flag values/history beyond this boolean." + } + }, + "excluded_runtime": [ + "Gallery/customer/public events and optional website analytics", + "Automated newsletter, reminder, WhatsApp, webhook and IMAP jobs", + "Security/audit logs, biometric embeddings and recognition results", + "Operational health, migration, update and polling metrics", + "Business/customer/user identities, geography, financial amounts and document contents; only explicit v3 inventory totals are permitted.", + "Disabled calendarBooking and internal crmDevelopment; hosted future product #1111", + "Image fragmentation: removed from current PicPeak, not a live capability" + ], + "configuration_only": [ + "reminder_emails", + "public_site", + "gallery_feedback_likes", + "gallery_feedback_ratings", + "gallery_feedback_comments", + "gallery_feedback_favorites", + "gallery_feedback_reactions", + "gallery_feedback_color_labels", + "gallery_guest_accounts", + "gallery_guest_uploads", + "gallery_downloads_restricted", + "download_resolution_picker", + "gallery_client_access", + "gallery_watermarks", + "gallery_image_protection", + "gallery_reveal", + "gallery_expiration", + "gallery_folders", + "transfer_upload_links", + "workflow_automation_enabled", + "s3_auto_import", + "gallery_capture_date_sort", + "download_original_filenames" + ], + "inventory_totals": { + "galleries": { + "name": { + "en": "Stored galleries" + }, + "description": { + "en": "Current number of gallery records, including drafts, inactive and archived galleries. Deleted galleries are excluded. One total for the installation, no breakdown or identifiers." + } + }, + "photos": { + "name": { + "en": "Stored photo records" + }, + "description": { + "en": "Current number of non-video photo records, including RAW, guest uploads and records of archived galleries. One total for the installation; not unique files, thumbnails, processing success or photo contents. Deleted records are excluded." + } + } + }, + "adoption_audit": { + "crm": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_quotes": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_invoices": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_contracts": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_projects": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_calendar": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_hours": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "customer_portal": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "accounting": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "workflows": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "newsletters": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "face_recognition": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "custom_css": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Applied CSS observed after consent, without observing visitors." + }, + "oauth": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Successful admin SSO login; no account, identity-provider or session details." + }, + "smtp": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "A successful explicitly initiated admin SMTP test/send; no recipients or messages." + }, + "whatsapp": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Successful admin integration test; no recipient, message or delivery history." + }, + "backup": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "s3_storage": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "share_mounts": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "An admin initiated an accepted external-folder import; no scanned paths, files or counts." + }, + "galleries": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "photo_management": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "photo_exports": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "photo_processing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "archive_management": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "gallery_sharing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "short_links": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "category_editing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin created, changed or deleted a category since consent. Seeded categories, reading and unchanged saves do not count. No names, memberships or identifiers are retained." + }, + "event_type_editing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin created, changed or deleted an event type since consent. Seeded presets, reading and unchanged saves do not count. No names, presets or identifiers are retained." + }, + "slideshow": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "transfers": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "video_uploads": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "At least one admin video file was successfully stored/accepted; no names, formats, lengths, sizes or processing/visitor history." + }, + "camera_raw_uploads": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "At least one admin camera RAW upload was stored/accepted; only the capability bit, no filename or metadata." + }, + "messaging": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "incoming_mail": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "A successful explicit admin connection test or non-skipped manual poll; no background intake, messages, attachments or counts." + }, + "reminder_emails": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "email_template_editing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin created a nonempty template or saved a real subject/body change since consent. Defaults, unchanged saves, previews and sending are excluded. This does not establish the current customization of templates edited before consent." + }, + "email_webhook": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Successful explicitly initiated admin send/test through the webhook transport; no recipients, messages or automatic deliveries." + }, + "accounting_incoming_invoices": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "accounting_expenses": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "accounting_tax_report": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "accounting_ledger": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "crm_installments": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "An admin saved an installment plan; no dates, amounts, currencies, payment status or document IDs." + }, + "document_templates": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "cms_content_editing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin saved a real change to an internal CMS page title or body since consent. Unchanged saves, external links, logos, seeded pages and page views do not count. This does not measure whether anyone read the page." + }, + "public_site": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "branding_editing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin changed branding settings, a theme or a logo since consent. Reading settings and unchanged saves do not count. A change can also restore a default; this is not a claim about the current design." + }, + "seo_editing": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin saved a real change to an allowlisted SEO setting since consent. Defaults, reading and unchanged saves do not count; no rules, paths or search-engine activity are collected." + }, + "admin_management": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "api_integration": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Successful authenticated HTTP API capability call; only this bit, never URLs, request values, token/owner IDs or call counts. Does not trigger a report." + }, + "webhooks": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Successful delivery (HTTP 2xx) of any outbound webhook, whether triggered automatically by an event or by an explicit admin test/replay; no destinations, event types, payloads, timestamps or counts." + }, + "restore": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "portable_backup": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "database_backup": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "s3_photo_storage": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Successful admin media storage/accepted upload to S3; no buckets, objects or sizes." + }, + "s3_backups": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "An admin started a backup to the configured S3 destination or a successful S3 test upload; local exports never imply S3 use." + }, + "analytics_dashboard": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "feedback_moderation": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "guest_management": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + }, + "gallery_feedback_likes": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_feedback_ratings": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_feedback_comments": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_feedback_favorites": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_feedback_reactions": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_feedback_color_labels": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_guest_accounts": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_guest_uploads": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_downloads_restricted": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "download_resolution_picker": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_client_access": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_watermarks": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_image_protection": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_reveal": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "gallery_expiration": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "photo_xmp_export": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin successfully generated an XMP export; no sidecars, filenames, ratings, selections or counts." + }, + "photo_replacement": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin upload actually replaced a photo successfully; no filenames, matching values, IDs or counts." + }, + "photo_admin_marks": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "An admin successfully saved their own photo mark; no rating, color, photo or admin identity." + }, + "gallery_folders": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "transfer_upload_links": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "workflow_automation_enabled": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "s3_auto_import": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "crm_invoice_import": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "An admin successfully imported an existing invoice; no PDF, invoice number, amount, currency, customer or payment status." + }, + "crm_combined_billing": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "An admin successfully created a combined bill; no hours, expenses, customer, documents or financial values." + }, + "crm_monthly_billing_manual": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "An admin successfully released a monthly draft for delivery; actual email delivery is not measured. No scheduler activity, customer, cadence or invoice values." + }, + "crm_document_conversion": { + "configuration_decision": "Enabled switch/capability; may be a default.", + "use_decision": "An admin successfully converted a quote or contract into a document or gallery; no content, links, acceptance states or automatic workflows." + }, + "gallery_capture_date_sort": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "download_original_filenames": { + "configuration_decision": "Technical configuration exists; may be a default, not activity.", + "use_decision": "Not collected. No visitor/customer activity inferred." + }, + "email_template_delivery": { + "configuration_decision": "Built in; display a label, not a percentage.", + "use_decision": "At least one real template email was accepted by SMTP or the configured mail webhook since consent, including background sends. Previews, test messages and template-free messages are excluded. Acceptance does not prove receipt or reading. No template key, recipient, contents, message identifier, send time or count is stored in usage markers." + } + } +} diff --git a/package-lock.json b/package-lock.json index 8044655..ee43783 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2084,9 +2084,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "funding": [ { "type": "github", @@ -2452,9 +2452,9 @@ } }, "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", "license": "MIT", "engines": { "node": ">= 12" @@ -2882,9 +2882,9 @@ } }, "node_modules/nodemailer": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.0.tgz", - "integrity": "sha512-wdv+hXBg0iIPIOf108FaZyTxqjQpCvQeAJt9pHHTooPy/y3vQtI9eiImM9AIVKR3e/0FeMA1oueNfBjFJcaK+Q==", + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.13.tgz", + "integrity": "sha512-SzG86OlvcW/NNhUFC6uROMwRTL4n7MswfQqC/T8mhkmnY1YVa23zUEMYi4ijSeXSl9GLz9ZeTJDUatEDuY5FeQ==", "license": "MIT-0", "engines": { "node": ">=20.0.0" diff --git a/protocol/features.v6.json b/protocol/features.v6.json new file mode 100644 index 0000000..6c0dada --- /dev/null +++ b/protocol/features.v6.json @@ -0,0 +1,1313 @@ +{ + "schema_version": "usage.v6", + "consent_version": "usage-consent.v6", + "features": { + "crm": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "clients", + "name": { + "en": "Client management" + }, + "configured": { + "en": "The clients capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_quotes": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "quotes", + "name": { + "en": "Quotes" + }, + "configured": { + "en": "The quotes capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_invoices": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "bills", + "name": { + "en": "Invoices" + }, + "configured": { + "en": "The bills capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_contracts": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "contracts", + "name": { + "en": "Contracts" + }, + "configured": { + "en": "The contracts capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_projects": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "projects", + "name": { + "en": "Projects" + }, + "configured": { + "en": "The projects capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_calendar": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "calendar", + "name": { + "en": "Admin calendar" + }, + "configured": { + "en": "The calendar capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_hours": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "hoursLogging", + "name": { + "en": "Hours logging" + }, + "configured": { + "en": "The hoursLogging capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "customer_portal": { + "category": "crm", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "customerPortal", + "name": { + "en": "Customer portal" + }, + "configured": { + "en": "The customerPortal capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "accounting": { + "category": "accounting", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "accounting", + "name": { + "en": "Accounting" + }, + "configured": { + "en": "The accounting capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "workflows": { + "category": "automation", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "workflows", + "name": { + "en": "Workflows" + }, + "configured": { + "en": "The workflows capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "newsletters": { + "category": "communication", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "newsletters", + "name": { + "en": "Newsletters" + }, + "configured": { + "en": "The newsletters capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "face_recognition": { + "category": "gallery", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "faces", + "name": { + "en": "ML face recognition" + }, + "configured": { + "en": "The faces capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "custom_css": { + "category": "appearance", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Custom CSS" + }, + "configured": { + "en": "Custom CSS is configured globally or applied through a gallery/theme/template; CSS text is not sent." + }, + "used": { + "en": "Applied CSS observed after consent, without observing visitors." + } + }, + "oauth": { + "category": "integration", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Admin SSO" + }, + "configured": { + "en": "Admin OIDC is enabled and issuer/client configuration is present; no provider or credential values." + }, + "used": { + "en": "Successful admin SSO login; no account, identity-provider or session details." + } + }, + "smtp": { + "category": "communication", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "SMTP delivery" + }, + "configured": { + "en": "An outgoing SMTP host is configured; no host, account, address or credentials." + }, + "used": { + "en": "A successful explicitly initiated admin SMTP test/send; no recipients or messages." + } + }, + "whatsapp": { + "category": "communication", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "WhatsApp integration" + }, + "configured": { + "en": "The WhatsApp capability is enabled and a usable configuration is present; no phone number, token or template." + }, + "used": { + "en": "Successful admin integration test; no recipient, message or delivery history." + } + }, + "backup": { + "category": "operations", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Backups" + }, + "configured": { + "en": "A full or database backup schedule is enabled; no schedule, path, storage sizes or backup names." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "s3_storage": { + "category": "integration", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "S3 storage" + }, + "configured": { + "en": "S3 is configured for media or backups; no bucket, endpoint, credentials or object keys." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "share_mounts": { + "category": "integration", + "since": "usage.v1", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "External folders" + }, + "configured": { + "en": "At least one gallery uses an external folder; only existence, no folder paths or gallery identifiers." + }, + "used": { + "en": "An admin initiated an accepted external-folder import; no scanned paths, files or counts." + } + }, + "galleries": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Gallery management" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "photo_management": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Media management" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "photo_exports": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Admin media export" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "photo_processing": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Media maintenance tools" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "archive_management": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Gallery archives" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "gallery_sharing": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Gallery sharing and QR" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "short_links": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Short links" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "category_editing": { + "category": "gallery", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Category customization" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin created, changed or deleted a category since consent. Seeded categories, reading and unchanged saves do not count. No names, memberships or identifiers are retained." + }, + "replaces": "gallery_categories" + }, + "event_type_editing": { + "category": "gallery", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Event type customization" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin created, changed or deleted an event type since consent. Seeded presets, reading and unchanged saves do not count. No names, presets or identifiers are retained." + }, + "replaces": "event_types" + }, + "slideshow": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "slideshow", + "name": { + "en": "Live slideshow" + }, + "configured": { + "en": "The slideshow capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "transfers": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "transfers", + "name": { + "en": "PicTransfer" + }, + "configured": { + "en": "The transfers capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "video_uploads": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Admin video uploads" + }, + "configured": { + "en": "Video extensions are allowed in global upload settings; no uploaded-file metadata." + }, + "used": { + "en": "At least one admin video file was successfully stored/accepted; no names, formats, lengths, sizes or processing/visitor history." + } + }, + "camera_raw_uploads": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Admin camera RAW uploads" + }, + "configured": { + "en": "Camera RAW (DNG) is allowed in global upload settings; no camera models or EXIF." + }, + "used": { + "en": "At least one admin camera RAW upload was stored/accepted; only the capability bit, no filename or metadata." + } + }, + "messaging": { + "category": "communication", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "messaging", + "name": { + "en": "Messaging tools" + }, + "configured": { + "en": "The messaging capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "incoming_mail": { + "category": "communication", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "IMAP intake" + }, + "configured": { + "en": "Incoming mail is enabled and an IMAP configuration is present; no mailbox, server, folders or credentials." + }, + "used": { + "en": "A successful explicit admin connection test or non-skipped manual poll; no background intake, messages, attachments or counts." + } + }, + "reminder_emails": { + "category": "communication", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "flag": "reminderEmails", + "name": { + "en": "Automatic event reminders" + }, + "configured": { + "en": "The reminderEmails capability switch is effectively enabled; only a boolean." + }, + "used": null + }, + "email_template_editing": { + "category": "communication", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Email template customization" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin created a nonempty template or saved a real subject/body change since consent. Defaults, unchanged saves, previews and sending are excluded. This does not establish the current customization of templates edited before consent." + }, + "replaces": "email_templates" + }, + "email_webhook": { + "category": "communication", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Email webhook transport" + }, + "configured": { + "en": "Both email webhook settings are present; no URL or secret." + }, + "used": { + "en": "Successful explicitly initiated admin send/test through the webhook transport; no recipients, messages or automatic deliveries." + } + }, + "accounting_incoming_invoices": { + "category": "accounting", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "incomingInvoices", + "name": { + "en": "Incoming invoices" + }, + "configured": { + "en": "The incomingInvoices capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "accounting_expenses": { + "category": "accounting", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "expenses", + "name": { + "en": "Expenses" + }, + "configured": { + "en": "The expenses capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "accounting_tax_report": { + "category": "accounting", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "taxReport", + "name": { + "en": "Tax reports" + }, + "configured": { + "en": "The taxReport capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "accounting_ledger": { + "category": "accounting", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "accounting", + "name": { + "en": "Ledger and accounting export" + }, + "configured": { + "en": "The accounting capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "crm_installments": { + "category": "crm", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Installment-plan tools" + }, + "configured": { + "en": "Quotes or invoices are enabled; no actual payment plans, amounts or statuses are inspected." + }, + "used": { + "en": "An admin saved an installment plan; no dates, amounts, currencies, payment status or document IDs." + } + }, + "document_templates": { + "category": "crm", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Document presets and blocks" + }, + "configured": { + "en": "Quotes or contracts are enabled, making document presets/blocks available; no template content." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "cms_content_editing": { + "category": "appearance", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "CMS content editing" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin saved a real change to an internal CMS page title or body since consent. Unchanged saves, external links, logos, seeded pages and page views do not count. This does not measure whether anyone read the page." + }, + "replaces": "cms" + }, + "public_site": { + "category": "appearance", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Public landing page" + }, + "configured": { + "en": "The public landing-page setting is enabled; no page HTML, texts, domains or visitors." + }, + "used": null + }, + "branding_editing": { + "category": "appearance", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Branding customization" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin changed branding settings, a theme or a logo since consent. Reading settings and unchanged saves do not count. A change can also restore a default; this is not a claim about the current design." + }, + "replaces": "branding" + }, + "seo_editing": { + "category": "appearance", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "SEO customization" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin saved a real change to an allowlisted SEO setting since consent. Defaults, reading and unchanged saves do not count; no rules, paths or search-engine activity are collected." + }, + "replaces": "seo_customization" + }, + "admin_management": { + "category": "operations", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "userManagement", + "name": { + "en": "Admin and role management" + }, + "configured": { + "en": "The userManagement capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "api_integration": { + "category": "integration", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "HTTP API integration" + }, + "configured": { + "en": "An unrevoked, unexpired API credential exists; no tokens, names, scopes or owner data." + }, + "used": { + "en": "Successful authenticated HTTP API capability call; only this bit, never URLs, request values, token/owner IDs or call counts. Does not trigger a report." + } + }, + "webhooks": { + "category": "integration", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Outbound webhooks" + }, + "configured": { + "en": "At least one active webhook is configured; no destinations, subscriptions, secrets or delivery logs." + }, + "used": { + "en": "Successful delivery (HTTP 2xx) of any outbound webhook, whether triggered automatically by an event or by an explicit admin test/replay; no destinations, event types, payloads, timestamps or counts." + } + }, + "restore": { + "category": "operations", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Restore" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "portable_backup": { + "category": "operations", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Portable PicPeak export/import" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "database_backup": { + "category": "operations", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "Database backups" + }, + "configured": { + "en": "Scheduled database backups are enabled; no schedules, file names or database contents." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "s3_photo_storage": { + "category": "integration", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "S3 media storage" + }, + "configured": { + "en": "S3 is the configured media backend and required credentials are present; no values are sent." + }, + "used": { + "en": "Successful admin media storage/accepted upload to S3; no buckets, objects or sizes." + } + }, + "s3_backups": { + "category": "integration", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "presence", + "name": { + "en": "S3 backup destination" + }, + "configured": { + "en": "The configured backup destination is S3 with a bucket present; no bucket or credentials." + }, + "used": { + "en": "An admin started a backup to the configured S3 destination or a successful S3 test upload; local exports never imply S3 use." + } + }, + "analytics_dashboard": { + "category": "operations", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "flag", + "flag": "analytics", + "name": { + "en": "Existing analytics module" + }, + "configured": { + "en": "The analytics capability switch is effectively enabled; only a boolean." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "feedback_moderation": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Feedback moderation" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "guest_management": { + "category": "gallery", + "since": "usage.v2", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Guest administration tools" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "A documented successful authenticated admin capability operation was observed since consent to this schema. No actor, operation history, parameters or counts." + } + }, + "gallery_feedback_likes": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery likes enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_feedback_ratings": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery star ratings enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_feedback_comments": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery comments enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_feedback_favorites": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery favorites enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_feedback_reactions": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery reactions enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_feedback_color_labels": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery color labels enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_guest_accounts": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Guest identities enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_guest_uploads": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Guest uploads enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_downloads_restricted": { + "category": "gallery_configuration", + "since": "usage.v4", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery downloads restricted" + }, + "configured": { + "en": "At least one gallery has downloads switched off; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "download_resolution_picker": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Download resolution picker enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_client_access": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Client access enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_watermarks": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Watermarks enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_image_protection": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Image protection enabled" + }, + "configured": { + "en": "Enabled beyond the shipped defaults — a stronger protection level, canvas rendering, or right-click disabled — globally or on at least one gallery; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_reveal": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery reveal enabled" + }, + "configured": { + "en": "Enabled in applicable gallery/global configuration; only existence across the installation, never gallery IDs or counts." + }, + "used": null + }, + "gallery_expiration": { + "category": "gallery_configuration", + "since": "usage.v2", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery expiration configured" + }, + "configured": { + "en": "At least one gallery has an expiry configured; no dates, gallery IDs or counts." + }, + "used": null + }, + "photo_xmp_export": { + "category": "gallery", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "XMP export" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin successfully generated an XMP export; no sidecars, filenames, ratings, selections or counts." + } + }, + "photo_replacement": { + "category": "gallery", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Photo replacement" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin upload actually replaced a photo successfully; no filenames, matching values, IDs or counts." + } + }, + "photo_admin_marks": { + "category": "gallery", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Photographer marks" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "An admin successfully saved their own photo mark; no rating, color, photo or admin identity." + } + }, + "gallery_folders": { + "category": "gallery_configuration", + "since": "usage.v3", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Gallery folders configured" + }, + "configured": { + "en": "An applicable global or gallery category is configured as a folder; no names, contents, counts or visitor activity." + }, + "used": null + }, + "transfer_upload_links": { + "category": "gallery_configuration", + "since": "usage.v3", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "PicTransfer upload links enabled" + }, + "configured": { + "en": "PicTransfer is enabled and a non-deleted transfer allows unexpired uploads; no links, tokens, dates, recipients or uploads." + }, + "used": null + }, + "workflow_automation_enabled": { + "category": "automation", + "since": "usage.v3", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Workflow automation enabled" + }, + "configured": { + "en": "The workflows module and at least one workflow are enabled; no names, graphs, triggers, decisions or runs." + }, + "used": null + }, + "s3_auto_import": { + "category": "integration", + "since": "usage.v3", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "S3 automatic import enabled" + }, + "configured": { + "en": "S3 media storage is configured and STORAGE_AUTO_IMPORT is enabled; no bucket, prefix, credentials, polling or imported objects." + }, + "used": null + }, + "crm_invoice_import": { + "category": "crm", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "flag", + "name": { + "en": "Invoice import" + }, + "configured": { + "en": "The required product capabilities are effectively enabled; only a boolean." + }, + "used": { + "en": "An admin successfully imported an existing invoice; no PDF, invoice number, amount, currency, customer or payment status." + }, + "flag": "bills" + }, + "crm_combined_billing": { + "category": "crm", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "capability", + "name": { + "en": "Combined billing" + }, + "configured": { + "en": "The required product capabilities are effectively enabled; only a boolean." + }, + "used": { + "en": "An admin successfully created a combined bill; no hours, expenses, customer, documents or financial values." + } + }, + "crm_monthly_billing_manual": { + "category": "crm", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "flag", + "name": { + "en": "Manual monthly billing" + }, + "configured": { + "en": "The required product capabilities are effectively enabled; only a boolean." + }, + "used": { + "en": "An admin successfully released a monthly draft for delivery; actual email delivery is not measured. No scheduler activity, customer, cadence or invoice values." + }, + "flag": "bills" + }, + "crm_document_conversion": { + "category": "crm", + "since": "usage.v3", + "measurement": "configuration_and_use", + "configuration": "capability", + "name": { + "en": "Document conversion" + }, + "configured": { + "en": "The required product capabilities are effectively enabled; only a boolean." + }, + "used": { + "en": "An admin successfully converted a quote or contract into a document or gallery; no content, links, acceptance states or automatic workflows." + } + }, + "gallery_capture_date_sort": { + "category": "gallery_configuration", + "since": "usage.v3", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Capture-date sorting configured" + }, + "configured": { + "en": "A gallery defaults to sorting by capture date; no capture dates, EXIF or visitor sorting actions." + }, + "used": null + }, + "download_original_filenames": { + "category": "gallery_configuration", + "since": "usage.v3", + "measurement": "configuration", + "configuration": "configuration", + "name": { + "en": "Original download filenames enabled" + }, + "configured": { + "en": "The original-download-filenames switch is enabled; no filenames or downloads are read or sent." + }, + "used": null + }, + "email_template_delivery": { + "category": "integration", + "since": "usage.v5", + "measurement": "configuration_and_use", + "configuration": "builtin", + "name": { + "en": "Emails sent using templates" + }, + "configured": { + "en": "Built-in capability is available; this is not evidence of use." + }, + "used": { + "en": "At least one real template email was accepted by SMTP or the configured mail webhook since consent, including background sends. Previews, test messages and template-free messages are excluded. Acceptance does not prove receipt or reading. No template key, recipient, contents, message identifier, send time or count is stored in usage markers." + } + } + }, + "inventory": { + "galleries": { + "name": { + "en": "Stored galleries" + }, + "description": { + "en": "Current number of gallery records, including drafts, inactive and archived galleries. Deleted galleries are excluded. One total for the installation, no breakdown or identifiers." + } + }, + "photos": { + "name": { + "en": "Stored photo records" + }, + "description": { + "en": "Current number of non-video photo records, including RAW, guest uploads and records of archived galleries. One total for the installation; not unique files, thumbnails, processing success or photo contents. Deleted records are excluded." + } + } + } +} diff --git a/protocol/schema.cjs b/protocol/schema.cjs index 7041799..fc3b0d3 100644 --- a/protocol/schema.cjs +++ b/protocol/schema.cjs @@ -2,10 +2,10 @@ // Vendored byte-identical in PicPeak. Existing wire versions stay immutable; // every expansion requires explicit consent to its own version. -const CATALOG = require("./features.v5.json"); -const CATALOGS = { "usage.v2": require("./features.v2.json"), "usage.v3": require("./features.v3.json"), "usage.v4": require("./features.v4.json"), "usage.v5": CATALOG }; -const CONSENT_VERSIONS = { "usage.v1": "usage-consent.v1", "usage.v2": "usage-consent.v2", "usage.v3": "usage-consent.v3", "usage.v4": "usage-consent.v4", "usage.v5": "usage-consent.v5" }; -const CURRENT_SCHEMA_VERSION = "usage.v5"; +const CATALOG = require("./features.v6.json"); +const CATALOGS = { "usage.v2": require("./features.v2.json"), "usage.v3": require("./features.v3.json"), "usage.v4": require("./features.v4.json"), "usage.v5": require("./features.v5.json"), "usage.v6": CATALOG }; +const CONSENT_VERSIONS = { "usage.v1": "usage-consent.v1", "usage.v2": "usage-consent.v2", "usage.v3": "usage-consent.v3", "usage.v4": "usage-consent.v4", "usage.v5": "usage-consent.v5", "usage.v6": "usage-consent.v6" }; +const CURRENT_SCHEMA_VERSION = "usage.v6"; const CURRENT_CONSENT_VERSION = CONSENT_VERSIONS[CURRENT_SCHEMA_VERSION]; const schemaForConsent = (consent) => Object.keys(CONSENT_VERSIONS).find((version) => CONSENT_VERSIONS[version] === consent); const schemaRank = (version) => Object.keys(CONSENT_VERSIONS).indexOf(version); @@ -49,7 +49,7 @@ const report = (version) => object({ key, object({ configured: boolean, ...(observesUse(key, version) ? { used: boolean } : {}) }) ]))), gallery_layouts: { type: "array", uniqueItems: true, maxItems: LAYOUTS.length, items: { enum: LAYOUTS } }, - ...(["usage.v3", "usage.v4", "usage.v5"].includes(version) ? { inventory: object(Object.fromEntries(INVENTORY_KEYS.map(key => [key, + ...(["usage.v3", "usage.v4", "usage.v5", "usage.v6"].includes(version) ? { inventory: object(Object.fromEntries(INVENTORY_KEYS.map(key => [key, { type: "integer", minimum: 0, maximum: MAX_INVENTORY_COUNT } ]))) } : {}), }); diff --git a/server/weeklyData.js b/server/weeklyData.js index 631cd8c..cedb749 100644 --- a/server/weeklyData.js +++ b/server/weeklyData.js @@ -1,7 +1,7 @@ "use strict"; const { readSnapshot } = require("./database"); const { emptyInventory, addInventory } = require("./inventory"); -const { features } = require("../protocol/features.v5.json"); +const { features } = require("../protocol/features.v6.json"); const { DAY, WEEK } = require("./weeklyConfig"); const { RETENTION_DAYS } = require("./weeklyActivity"); diff --git a/server/weeklyEmail.js b/server/weeklyEmail.js index f87e477..baf697e 100644 --- a/server/weeklyEmail.js +++ b/server/weeklyEmail.js @@ -1,6 +1,6 @@ "use strict"; const messages = require("./weeklyMessages.json"); -const catalog = require("../protocol/features.v5.json"); +const catalog = require("../protocol/features.v6.json"); const german = require("../web/locales/catalog.de.json"); const { DAY } = require("./weeklyConfig"); const escape = value => String(value).replace(/[&<>"']/g, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); diff --git a/test/compatibility.test.js b/test/compatibility.test.js index b1afff6..b50ed44 100644 --- a/test/compatibility.test.js +++ b/test/compatibility.test.js @@ -147,7 +147,7 @@ for (const engine of ['sqlite', ...(process.env.TEST_DATABASE_URL ? ['pg'] : []) const payload = report(); mutate(payload); await request(app).post('/api/envelopes').send(envelope(id, 'report', 1, payload, version)).expect(400); } - for (const version of ['usage.v0', 'usage.v6', '__proto__', 'constructor']) + for (const version of ['usage.v0', 'usage.v7', '__proto__', 'constructor']) await request(app).post('/api/envelopes').send(envelope(id, 'report', 1, report(), version)).expect(400); const tampered = envelope(id, 'report', 1, report({ features: { crm: { used: true } } })); tampered.packet.payload.features.crm.used = false; diff --git a/test/integration.test.js b/test/integration.test.js index b6a0119..3a4f9fb 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -422,12 +422,12 @@ test( "protocol files are byte-identical in both repositories", { skip: !UsageService }, async () => { - for (const file of ["schema.cjs", "protocol.cjs", "features.v2.json", "features.v3.json", "features.v4.json", "features.v5.json"]) + for (const file of ["schema.cjs", "protocol.cjs", "features.v2.json", "features.v3.json", "features.v4.json", "features.v5.json", "features.v6.json"]) assert.equal( await fs.readFile(path.join(root, "backend/src/usage", file), "utf8"), await fs.readFile(path.join(__dirname, "../protocol", file), "utf8"), ); - for (const file of ["usage-coverage.v2.json", "usage-coverage.v3.json", "usage-coverage.v4.json", "usage-coverage.v5.json", "FEATURE_COVERAGE.md"]) + for (const file of ["usage-coverage.v2.json", "usage-coverage.v3.json", "usage-coverage.v4.json", "usage-coverage.v5.json", "usage-coverage.v6.json", "FEATURE_COVERAGE.md"]) assert.equal(await fs.readFile(path.join(root, "docs", file), "utf8"), await fs.readFile(path.join(__dirname, "../docs", file), "utf8")); const client = JSON.parse(await fs.readFile(path.join(root, "frontend/src/i18n/locales/de.json"), "utf8")).productUsage; @@ -646,7 +646,7 @@ test('pending v3 registration retries keep the originally approved scope on a v4 assert.equal((await restarted.preview()).features.gallery_downloads_restricted, undefined); }); -for (const version of ['usage.v1', 'usage.v2', 'usage.v3', 'usage.v4']) test(`${version} to v5 preserves pending packets and starts precise evidence only after confirmed consent`, { skip: !UsageService }, async t => { +for (const version of ['usage.v1', 'usage.v2', 'usage.v3', 'usage.v4']) test(`${version} to the current schema preserves pending packets and starts precise evidence only after confirmed consent`, { skip: !UsageService }, async t => { const { service, local, c, clock, transport, options } = await setup(t); const p = require('../protocol/protocol.cjs'); await service.enable(p.CONSENT_VERSIONS[version]); @@ -660,13 +660,13 @@ for (const version of ['usage.v1', 'usage.v2', 'usage.v3', 'usage.v4']) test(`${ const restarted = new UsageService(local, options); await restarted.deliver(await local('product_usage_state').first()); transport.loseReceipt = true; - await restarted.command('consent', { consent_version: 'usage-consent.v5' }); + await restarted.command('consent', { consent_version: p.CURRENT_CONSENT_VERSION }); await restarted.markUsed(['email_template_editing', 'email_template_delivery']); assert.equal((await restarted.status()).schema_version, version); assert.ok(!(await local('product_usage_markers').pluck('feature')).includes('email_template_delivery')); const again = new UsageService(local, options); await again.deliver(await local('product_usage_state').first()); - assert.equal((await again.status()).schema_version, 'usage.v5'); + assert.equal((await again.status()).schema_version, p.CURRENT_SCHEMA_VERSION); assert.equal((await again.status()).consent_update_available, false); assert.deepEqual(await local('product_usage_markers').pluck('feature'), []); await again.markUsed(['cms', 'email_template_delivery', 'PRIVATE@example.test']); diff --git a/test/v4.test.js b/test/v4.test.js index 09bf4ab..fa34a3b 100644 --- a/test/v4.test.js +++ b/test/v4.test.js @@ -70,7 +70,7 @@ for (const engine of ['sqlite', ...(process.env.TEST_DATABASE_URL ? ['pg'] : []) test(`${engine}: v4 requires new consent and keeps old report retries valid without changing their payload`, async t => { const { app, c, clock, register, envelope, report, send } = await fixture(t, engine); assert.deepEqual((await request(app).get('/api/health').expect(200)).body.supported_schemas, - ['usage.v1', 'usage.v2', 'usage.v3', 'usage.v4', 'usage.v5']); + ['usage.v1', 'usage.v2', 'usage.v3', 'usage.v4', 'usage.v5', 'usage.v6']); assert.deepEqual((await request(app).get('/schema/features.v4.json').expect(200)).body, p.CATALOGS['usage.v4']); for (const version of ['usage.v1', 'usage.v2', 'usage.v3']) { const id = await register(version); diff --git a/test/v6.test.js b/test/v6.test.js new file mode 100644 index 0000000..8754977 --- /dev/null +++ b/test/v6.test.js @@ -0,0 +1,59 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const crypto = require('node:crypto'); +const p = require('../protocol/protocol.cjs'); +const { createDatabase, migrate } = require('../server/database'); +const { createApp } = require('../server/app'); +const signedEnvelope = require('./helpers/signedEnvelope.cjs'); + +test('v6 is closed, size-bounded and leaves v5 validation byte-for-byte immutable', () => { + // Pinned with the pre-v6 protocol: the v5 receiver schema must not move + // when the catalog gains a version that changes only one description. + assert.equal(crypto.createHash('sha256').update(JSON.stringify(p.ingressEnvelopeSchemas['usage.v5'])).digest('hex'), + '373ebe919a536e31529ba80e3a8709d801c3a9b6888d77bce1f1d516d2322587'); + assert.equal(p.CURRENT_SCHEMA_VERSION, 'usage.v6'); + assert.equal(p.CONSENT_VERSIONS['usage.v6'], 'usage-consent.v6'); + // Same keys as v5: the v6 change is the meaning of webhooks.used, not a key. + assert.deepEqual(Object.keys(p.CATALOGS['usage.v6'].features), Object.keys(p.CATALOGS['usage.v5'].features)); + assert.equal(p.FEATURE_KEYS.length, 87); assert.equal(p.ALL_FEATURE_KEYS.length, 94); + assert.notEqual(p.CATALOGS['usage.v6'].features.webhooks.used.en, p.CATALOGS['usage.v5'].features.webhooks.used.en); + const id = p.generateIdentity(), now = new Date('2026-10-01T12:00:00.000Z'); + const payload = { features: p.emptyFeatures(), inventory: { galleries: p.MAX_INVENTORY_COUNT, photos: p.MAX_INVENTORY_COUNT }, + picpeak_version: '3.152.1-beta.0', report_date: '2026-10-01', generated_at: now.toISOString(), gallery_layouts: p.LAYOUTS }; + const signed = fields => p.signPacket(p.makePacket(id, 'report', 1, { ...payload, features: fields }), id, now); + const e = signed(payload.features); + assert.ok(Buffer.byteLength(JSON.stringify(e)) < p.MAX_BYTES); + assert.deepEqual(p.verifyEnvelope(e, now.getTime()).payload, payload); + for (const addition of [ + { webhooks: { configured: true, used: true, destination: 'https://PRIVATE.example.test/hook' } }, + { webhooks: { configured: true, used: true, event_type: 'event.published' } }, + { webhooks: { configured: true, used: 3 } }, + { webhooks: { configured: true, used: true, deliveries: 12 } }, + ]) assert.throws(() => p.verifyEnvelope(signed({ ...payload.features, ...addition }), now.getTime())); +}); + +test('v6 requires consent; a v5 participant keeps its scope until it explicitly upgrades', async t => { + const db = createDatabase({ DATABASE_PATH: ':memory:' }); t.after(() => db.destroy()); await migrate(db); + let now = Date.parse('2026-10-01T12:00:00.000Z'); + const c = createApp({ db, now: () => now, disableRateLimit: true }).locals.collector; + const a = { ...p.generateIdentity(), sequence: -1 }, b = { ...p.generateIdentity(), sequence: -1 }; + const envelope = (id, action, payload, version) => signedEnvelope(p.makePacket(id, action, id.sequence + 1, payload, version), id, now); + async function send(id, action, payload, version) { + const e = envelope(id, action, payload, version), receipt = await c.receive(e); id.sequence++; return { e, receipt }; + } + const report = features => ({ features, report_date: new Date(now).toISOString().slice(0, 10), generated_at: new Date(now).toISOString() }); + await send(a, 'register', { consent_version: 'usage-consent.v5' }, 'usage.v5'); + const old = await send(a, 'report', report({ webhooks: { configured: true, used: false } }), 'usage.v5'); + await assert.rejects(c.receive(envelope(a, 'report', report({ webhooks: { configured: true, used: true } }), 'usage.v6')), { code: 'CONSENT_REQUIRED' }); + await send(b, 'register', { consent_version: 'usage-consent.v6' }, 'usage.v6'); + await send(b, 'report', report({ webhooks: { configured: true, used: true } }), 'usage.v6'); + const summary = await c.summary(); + assert.deepEqual(summary.features.webhooks, { configured: 2, reported: 2, used: 1, used_reported: 2 }); + await send(a, 'consent', { consent_version: 'usage-consent.v6' }, 'usage.v6'); + // One report per installation and day: the upgraded report is tomorrow's. + now += 24 * 60 * 60 * 1000; + await send(a, 'report', report({ webhooks: { configured: true, used: true } }), 'usage.v6'); + assert.deepEqual((await c.summary()).features.webhooks, { configured: 2, reported: 2, used: 2, used_reported: 2 }); + // The accepted v5 packet retries unchanged after the upgrade. + assert.deepEqual(await c.receive(signedEnvelope(old.e.packet, a, now)), old.receipt); +}); diff --git a/web/locales/catalog.de.json b/web/locales/catalog.de.json index eb126b0..6cd5921 100644 --- a/web/locales/catalog.de.json +++ b/web/locales/catalog.de.json @@ -246,7 +246,7 @@ "webhooks": { "name": "Ausgehende Webhooks", "configured": "Mindestens ein aktiver Webhook ist konfiguriert; keine Ziele, Abonnements, Geheimnisse oder Zustellprotokolle.", - "used": "Erfolgreicher expliziter Admin-Webhook-Test/-Replay; keine automatischen oder durch Besucher ausgelösten Zustellungen." + "used": "Erfolgreiche Zustellung (HTTP 2xx) eines beliebigen ausgehenden Webhooks, ob automatisch durch ein Ereignis oder durch einen expliziten Admin-Test/-Replay ausgelöst; keine Ziele, Ereignistypen, Payloads, Zeitstempel oder Zähler." }, "restore": { "name": "Wiederherstellung", diff --git a/web/locales/de.json b/web/locales/de.json index c9bb5c6..c7ef869 100644 --- a/web/locales/de.json +++ b/web/locales/de.json @@ -77,7 +77,7 @@ "allCapabilities": "Funktionen über alle Schemaversionen", "catalogTitle": "Alle 87 Funktionssignale", "catalogSearch": "Funktionsname oder Schlüssel suchen", - "catalogDisclosure": "usage.v5: 64 Konfiguriert/Genutzt-Paare, 23 reine Konfigurationswerte und dieselben zwei Bestandszahlen. Verfügbarkeit und aktivierte Standards belegen keine Nutzung. Änderungssignale schließen unverändertes Speichern und Vorschauen aus. Echter Vorlagenversand umfasst Hintergrundmails und misst die Annahme durch den Transport, weder Empfang noch Lesen. Nur feste Ja/Nein-Werte; keine Inhalte, Empfänger, Aktionszeitpunkte oder Häufigkeiten.", + "catalogDisclosure": "usage.v6: dieselben 87 Funktionen wie v5 — 64 Konfiguriert/Genutzt-Paare, 23 reine Konfigurationswerte — und dieselben zwei Bestandszahlen. Die eine Änderung gegenüber v5: Ausgehende Webhooks gelten nach jeder erfolgreichen Zustellung (HTTP 2xx) als genutzt, ob automatisch durch ein Ereignis oder durch einen expliziten Admin-Test/-Replay ausgelöst, nicht mehr nur nach einem manuellen Test. Verfügbarkeit und aktivierte Standards belegen keine Nutzung. Änderungssignale schließen unverändertes Speichern und Vorschauen aus. Echter Vorlagenversand umfasst Hintergrundmails und misst die Annahme durch den Transport, weder Empfang noch Lesen. Nur feste Ja/Nein-Werte; keine Inhalte, Empfänger, Ziele, Ereignistypen, Aktionszeitpunkte oder Häufigkeiten.", "catalogVersionDisclosure": "v1–v4 behalten ihren ursprünglichen Umfang und ihre Bedeutung. Neue Änderungs-/Versandsignale beginnen erst nach ausdrücklicher v5-Zustimmung. Alte Verwaltungssignale bleiben im Verlauf getrennt. Keine Beobachtung seit Zustimmung belegt weder Standards noch lebenslange Nichtnutzung. Fehlende Angaben älterer Installationen bleiben unbekannt.", "catalogConfigured": "Konfiguriert", "catalogUsed": "Genutzt", @@ -194,7 +194,8 @@ "signalMeaning": "Konfiguriert bedeutet, dass eine Funktion aktiviert ist oder eine relevante Konfiguration vorliegt. Fest integrierte Funktionen gelten als verfügbar, nicht als genutzt. Genutzt bedeutet eine offengelegte Beobachtung (meist eine erfolgreiche Admin-Aktion) seit Zustimmung zum aktuellen Schema. v1 misst seit Teilnahmebeginn; ein ausdrücklich bestätigter Schemawechsel setzt die lokalen Merker zurück. Es ist ein Ja/Nein-Signal, keine Häufigkeit. Reine Konfigurationsfelder enthalten keinen Nutzungswert.", "schemaCompatibility": "Alle fünf Schemaversionen bleiben unterstützt. Bestehende v1/v2/v3/v4-Teilnehmende behalten ihren bisherigen Umfang, bis sie v5 in PicPeak ausdrücklich zustimmen. Der aktuelle v5-Katalog erklärt 87 Funktionssignale und zwei Gesamtbestände. Neue Änderungssignale schließen unverändertes Speichern und Vorschauen aus. Vorlagenversand umfasst echte, vom Mailtransport angenommene Hintergrundsendungen, aber keine Testnachrichten. Frühere breite Bearbeitungsmessungen und die eingestellte v2/v3-Frage nach erlaubten Downloads bleiben im Verlauf getrennt erhalten. Ältere und unvollständige Berichte werden unterstützt; fehlende oder null-Werte bleiben unbekannt.", "layoutValues": "Layouts: grid, masonry, carousel, timeline, mosaic, gallery-premium, gallery-story oder other. Die Anzahl der Galerien je Layout wird nie übermittelt.", - "schemaV5": "JSON-Schema: usage.v5", + "schemaV6": "JSON-Schema: usage.v6", + "schemaV5": "Früheres Schema: usage.v5", "schemaV4": "Früheres Schema: usage.v4", "schemaV3": "Früheres Schema: usage.v3", "schemaV2": "Früheres Schema: usage.v2", diff --git a/web/locales/en.json b/web/locales/en.json index 151ee47..9b6e4dd 100644 --- a/web/locales/en.json +++ b/web/locales/en.json @@ -77,7 +77,7 @@ "allCapabilities": "Capabilities across all schemas", "catalogTitle": "All 87 capability signals", "catalogSearch": "Search capability name or key", - "catalogDisclosure": "usage.v5: 64 configured/used pairs, 23 configuration-only signals, and the same two inventory totals. Built-in availability and enabled defaults do not prove use. Edit signals exclude unchanged saves and previews. Real template mail includes background sends and measures transport acceptance, not receipt or reading. Only fixed booleans; no contents, recipients, per-action times or frequencies.", + "catalogDisclosure": "usage.v6: the same 87 capabilities as v5 — 64 configured/used pairs, 23 configuration-only signals — and the same two inventory totals. The one change from v5: Outbound webhooks counts as used after any successful delivery (HTTP 2xx), automatic event-triggered or explicit admin test/replay, not only after a manual test. Built-in availability and enabled defaults do not prove use. Edit signals exclude unchanged saves and previews. Real template mail includes background sends and measures transport acceptance, not receipt or reading. Only fixed booleans; no contents, recipients, destinations, event types, per-action times or frequencies.", "catalogVersionDisclosure": "v1–v4 retain their original scope and meaning. New edit/delivery signals begin only after explicit v5 consent. Old broad management questions remain separate in history. No observation since consent does not prove defaults or lifetime non-use. Missing values from older reporters stay unknown.", "catalogConfigured": "Configured", "catalogUsed": "Used", @@ -194,7 +194,8 @@ "signalMeaning": "Configured means the capability is enabled or has relevant configuration; built-in capabilities mean available, not used. Used means a disclosed observation (usually a successful admin capability operation) since consent to the current schema. v1 measures since joining; explicitly upgrading the schema restarts local markers. It is a yes/no signal, never a frequency. Configuration-only fields omit used entirely.", "schemaCompatibility": "All five schema versions remain supported. Existing v1/v2/v3/v4 participants keep their previous scope until they explicitly consent to v5 in PicPeak. The current v5 catalog explains 87 capability signals and two inventory totals. New edit signals exclude unchanged saves and previews; template-mail use includes real background sends accepted by the mail transport, never test messages. Historical views preserve earlier broad editor measurements and the retired v2/v3 allowed-downloads question separately. Older and partial reports remain supported; omitted or null measurements are unknown.", "layoutValues": "Layouts: grid, masonry, carousel, timeline, mosaic, gallery-premium, gallery-story, or other. We never include the number of galleries using them.", - "schemaV5": "JSON schema: usage.v5", + "schemaV6": "JSON schema: usage.v6", + "schemaV5": "Legacy schema: usage.v5", "schemaV4": "Legacy schema: usage.v4", "schemaV3": "Legacy schema: usage.v3", "schemaV2": "Legacy schema: usage.v2", diff --git a/web/main.tsx b/web/main.tsx index 925a831..f7c58ce 100644 --- a/web/main.tsx +++ b/web/main.tsx @@ -713,6 +713,9 @@ function Transparency() {
{t.signalMeaning}
{t.schemaCompatibility}
{t.layoutValues}
+ + {t.schemaV6} + {" "} {t.schemaV5} {" "}