Replace the deny-based restriction on granting OWNER with role-scoped allow policies so authorization fails closed: admins may create and update memberships only when the assigned role is not OWNER, and the absence of a target role no longer implies permission. To keep console UI gating accurate without loosening the base grants, the permission field gains an optional typed options argument (PermissionOptionsInput) that forwards target_role into the dry-run authorization. Only the two role-related console calls (create user, update membership) pass it; the OWNER option stays hidden for admins via the existing assignable-roles helper. Add a non-regression test that an admin cannot promote a member to OWNER while still being able to change members between non-owner roles.
43 KiB
43 KiB
Changelog
All notable changes to probod (the server, including the bundled @probo/console, @probo/trust, and @probo/ui frontends) will be documented in this file.
Unreleased
Fixed
- Enforced owner-only member removal:
removeUser(API resolver and MCPRemoveUserTool) now requires the owner-onlyiam:membership:deletegate instead of the weakeriam:membership-profile:delete, so an organization ADMIN can no longer remove members (including OWNERs)
Changed
- Consolidated ownership-grant authorization into policy: granting OWNER (via
createUserorupdateMembership) is now restricted to organization owners through role-scoped allow policies conditioned on the assigned role, replacing the per-resolver custom checks and the now-removediam:membership-role:set-owneraction. Thepermissionfield gained an optional genericattributeskey/value argument so the console can refine dry-run checks (e.g. by target role) without loosening the base grants
[0.223.3] - 2026-07-06
Fixed
- Fixed IP address recording for NDA acceptance, document signing/approval events, and session creation behind a layer-7 proxy; affected endpoints now read the real client IP from
Forwarded/X-Forwarded-Forheaders
[0.223.2] - 2026-07-03
Fixed
- Fixed a privilege-escalation gap where an organization ADMIN could mint an OWNER membership through
createUser, bypassing the owner-only authorization enforced elsewhere;createUser(both the API resolver and the MCPCreateUserTool) now requires set-owner authorization when the requested role is OWNER
[0.223.1] - 2026-07-03
Fixed
- Fixed a cross-tenant IDOR where a Finding's linked Risk or a Processing Activity's Data Protection Officer could disclose another organization's data (GHSA-c74x-79w6-63jh): affected resolvers now authorize the referenced object itself instead of its parent, and the write paths validate the reference against the caller's organization
[0.223.0] - 2026-07-02
Added
- Served files now support HTTP range requests, enabling seeking and resumable downloads
- Document lifecycle webhook events (
document.*, including theversion,signature, andapprovalsub-events) can now be subscribed to
Changed
- Public files are served from a stable URL so CDN infrastructure can cache them properly
- Compliance report upload limit raised to 30MB
- OAuth token and consent UIs now display friendly names for the
v1:resource-aliasscopes instead of the raw scope string
Fixed
- Compliance page no longer treats every Slack connector as connected
- Long Mermaid flowchart labels now wrap instead of being clipped in risk assessment diagrams
- Dialogs no longer close when dismissing a nested dropdown or select whose pointer lands inside the dialog, preserving form state
[0.222.2] - 2026-07-01
Changed
- Bootstrap config output now omits empty fields and unset LLM provider blocks, producing cleaner generated YAML
[0.222.1] - 2026-07-01
Changed
- String configuration defaults now come from
probod's built-in values when the corresponding environment variable is unset, ensuring bootstrap-generated and directly-configured deployments use the same defaults
[0.222.0] - 2026-06-30
Added
- OAuth2 loopback redirect URIs now match regardless of port, enabling native OAuth clients such as Claude Code that use ephemeral ports at authorization time (RFC 8252 section 7.3)
- Access review campaigns can now be closed when entries are in a failed state
Changed
- Third-party risk assessment vetting notes now persist the full structured breakdown (risk classification, per-category analysis, privacy and data-processing practices, AI governance, contractual clauses, professional standing) instead of a short summary only
Fixed
- Deleting a user still referenced elsewhere (e.g. as an asset owner) now returns a 409 Conflict instead of an internal error
- GraphQL endpoint is now protected against alias-flooding DoS (GHSA-prh2-g8pv-m7p9): parser token limit, field complexity cap, LRU query cache, and field suggestion suppression added to all three GraphQL handlers
Removed
- Access review campaigns no longer expose a framework-controls field
pendingEntryCountfield removed from access review campaigns
[0.221.0] - 2026-06-30
Added
- Compliance portal home page sections
- v2 UI component library: Text, Heading, Avatar, Button, IconButton, Badge, Callout, Dropdown menu, Card, Anchor, and Link components
- Webhook sender now runs on the kit worker framework
[0.220.0] - 2026-06-25
Added
- Four new API-key access-review connectors: Pylon, OpenRouter, incident.io, and Brevo
Fixed
- Advertised scopes for OAuth2 protected resources
[0.219.0] - 2026-06-24
Added
- DocuSign partner OAuth2 with PKCE: full authorization-code flow with account picker; the selected account is persisted and its data-center base URI resolved from /oauth/userinfo
- Five new API-key access-review connectors: Mercury, Apollo.io, Deepgram, ClickHouse Cloud, and Langfuse
- APIKeyBasicAuthUserPass auth mode for API-key connectors supporting username:password credentials
- Read actions on all unprefixed OAuth scopes
Changed
- Pending signature requests on a superseded version are moved to the newly published minor version, preserving the notification schedule
Fixed
- Signature requests are now restricted to the current published version
- Heroku connection probe sends the versioned
Accept: application/vnd.heroku+json; version=3header, correctly detecting revoked tokens - Third party assessment header display
[0.218.1] - 2026-06-23
Fixed
- Bump
golang.org/x/imageto v0.43.0, remediating CVE-2026-33813 (denial of service via malformed WEBP parsing) and CVE-2026-46602 (missing tile-size limit inx/image/tiff)
[0.218.0] - 2026-06-23
Added
- RFC 6750
WWW-Authenticatechallenges on OAuth bearer APIs (MCP, Console and Connect GraphQL, Files, OAuth2 userinfo): responses now advertiseresource_metadata,invalid_token, andinsufficient_scopewith the required scopes
[0.217.0] - 2026-06-22
Added
- Resource aliases: trust center entries support a custom URL slug; alias field and set/remove mutations exposed in the console API, trust API, and MCP tools, with alias-based navigation in the trust center
Changed
- Agent tool JSON schemas normalize required fields for OpenAI compatibility
Fixed
- Alias resolver, field blur, and sitemap URL generation
[0.216.1] - 2026-06-19
Fixed
- OAuth2 scope registration for CIMD client identifiers was missing; CIMD actions are now correctly gated by their corresponding scopes
[0.216.0] - 2026-06-19
Added
- OAuth2 Client ID Metadata Document (CIMD) support: MCP connectors such as ChatGPT and Claude can now register via HTTPS client_id URLs instead of pre-provisioned GIDs; metadata documents are fetched and cached, clients are upserted on first use, and CIMD is advertised in OIDC discovery when allowed URLs are configured
[0.215.1] - 2026-06-19
Fixed
- Tracker-mapping no longer reprocesses sibling patterns O(N^2) times per banner; the re-enqueue now skips siblings already linked to a common third party or marked first-party, and routine mapping logs are demoted from INFO to Debug
[0.215.0] - 2026-06-19
Added
- Tracker pattern category is now editable from the pattern detail page (matching the table-row behaviour)
- Trackers page filter now offers the HTTP cookie source, and extension-sourced rows render a proper source badge
Changed
- Local storage, IndexedDB, and cache-storage trackers without an expiry now display as "persistent" rather than "session"
Fixed
- Fixed a deadlock between concurrent tracker-mapping workers processing sibling patterns on the same banner
- An HTTP server-set cookie now outranks a pre-existing detection, re-arming mapping so the pattern is identified instead of being skipped
[0.214.0] - 2026-06-19
Added
- OAuth2 API scope enforcement: v1:* scopes registered and advertised in OIDC discovery and protected-resource metadata, enforced in the IAM authorizer before policy evaluation
- Identity-scoped OAuth token management: users can create, list, and revoke manual bearer tokens from
/me/oauth-tokensand the console UI - Auditor role now includes the
v1:iam:readscope
Changed
- OAuth consent screen groups API scopes under an accordion
Fixed
- MCP API now accepts OAuth bearer tokens (was previously rejected)
- Notifications are skipped for inactive users
[0.213.0] - 2026-06-18
Added
- Tracker-pattern catalog rows now carry a terminal attribution verdict (UNDETERMINED, THIRD_PARTY, FIRST_PARTY); FIRST_PARTY short-circuits the mapping pipeline so first-party and generic artifacts are never re-attributed
Changed
- Document signing and approval emails are now batched per recipient by a debounced worker that sends one consolidated email and widening reminders, replacing the immediate per-document approval email and the manual "send signing notifications" action
- Deterministic vendor adoption in tracker mapping is gated behind a confidence/trust bar; lower-confidence rows are reused as hints and re-confirmed by an independent agent, and attributions must cite concrete evidence
Fixed
- Tracker-pattern attribution is kept consistent with the vendor link: a first-party reclassification clears the stale org vendor link, and FIRST_PARTY rows are excluded from the enrichment requeue
[0.212.0] - 2026-06-18
Added
- OIDC authentication now opens a child session when assuming an organization
Fixed
- OIDC organization access errors now return a 404 instead of an internal error
[0.211.2] - 2026-06-18
Fixed
- Exit codes
[0.211.1] - 2026-06-18
Fixed
- Missing OS exit code on error
[0.211.0] - 2026-06-18
Added
- Document delete confirmation dialog in the console
Changed
- Error responses during a server panic are now always serialized as JSON
- Enrichment tracking unified with outcome-based status; enrichment state, attempts, and run outcome now recorded per field
Fixed
- Enrichment re-arm and migration backfill gaps corrected
- Google Workspace access review source-name resolution no longer loops on 403 responses
[0.210.0] - 2026-06-16
Added
- Electronic signature on employee document signings: the signed PDF is generated and an esign record is created and accepted (capturing signer IP and user agent), mirroring the document approval flow; consent wording is now a single backend source of truth rendered consistently across the signing, approval, and NDA pages
- Structured authorization decision logging: every authorizer evaluation (allow, deny, no_match, assumption error) emits a decision line with policy id and reason using opaque ids
Changed
- Access review campaign sources reworked: sources are first-class with a per-campaign snapshot (name, connector) taken at start time, fetch attempts recorded as an append-only log, the unused source category removed, and the deleted-source badge dropped from the campaign detail
- Access review roles render as up to three badges with a "+X more" popover instead of one long comma-separated string
Fixed
- Access review connection status now probes all providers (static, dynamic, and custom) so bad API keys and expired OAuth tokens no longer show as Connected
- Cursor access review driver marks an account inactive when either
isRemovedor roleremovedis set, fixing accounts reported active despite removal - MCP profile output no longer fails schema validation when a profile has no additional email addresses
[0.209.0] - 2026-06-12
Added
- Common third-party enricher worker that fills the global catalog (legal name, headquarters, canonical website, compliance docs, certifications, logo, owned domains) with per-field provenance and confidence thresholds; opt-in, no-ops without an agent provider
- Tracker mapping and common-pattern enrichment agents can now open pages with a read-only headless browser (gated on Chrome endpoint) to read setters from cookie-database and policy pages
- Discovery and persistence of common third-party owned domains, used to re-resolve previously unmapped tracker patterns
Cache-ControlandETagon/api/files/v1/staticbrand assets; startup validation of required assets
Changed
- Tracker enrichment agent now restates source-page descriptions in its own words rather than copying them verbatim
- Common third-party enrichment agents run in parallel after website resolution; prompts rewritten in role/task/instructions XML style with a calibrated confidence rubric
- Oversized logo responses are rejected instead of truncated; ownership substring matching tightened with a length-ratio guard; per-agent error text sanitized and bounded before persistence
Fixed
- Activate-login path
find_links_matchingbrowser tool double-encoded its pattern, starving any agent using it- Worker confidence threshold of
0no longer dropped by Helm falsy-numeric truthiness
[0.208.1] - 2026-06-12
Fixed
- Trust center file creation in console
- S3 filename header escaping
[0.208.0] - 2026-06-11
Added
activestatus field on access entries- Import action for catalog vendors in trackers;
importThirdPartyFromCommonmutation to pull a catalog vendor into an org - Catalog vendors surfaced in tracker policy documents
- File download URLs for console file fields
Changed
- Trust and MCP connector logos now use the File type
- Third parties deduplicated by name; unique index enforced per org
- Tracker mapping no longer auto-creates org third parties; explicit import required
- Tracker row and category select restyled; move-to-category confirm dialog removed
- Tracker mapping restored to link existing patterns; "create only" mode removed
- Document major version publishing requires explicit
approver_ids - References updated to probo.com
Removed
- Third-party disambiguation agent and automatic matching removed
Fixed
- DNS TXT lookup retried over TCP on truncated UDP response
[0.207.0] - 2026-06-10
Added
- Neon access-review connector (organization members via Neon API, API-key auth)
- Render access-review connector (workspace members, API-key + Workspace ID)
- Qovery access-review connector (organization members, configurable
TokenAuthorization scheme) - API-key connector providers can now declare a custom Authorization token scheme (defaults to
Bearer) regulationSource(DETECTED/DEFAULT) on cookie consent records, with GDPR/OPT_IN applied as the safe default when geolocation does not resolve a known regulation--keywordscoping on the banner tracker-reset operator path: rebuilds only patterns whose pattern or display name contains the substringparent_third_party_idforeign key on third parties for arbitrary sub-third-party nesting depth;level(int, 1+) replaces thefirstLevelboolean
Changed
- Tracker-mapping agent ignores cookie-database/consent-directory operators (Cookipedia, cookiedatabase.org, CookieServe, …) as vendor attributions; CMP own-cookie attributions (OneTrust, Cookiebot, …) still survive
- Tracker-mapping agent ignores own-domain tracker attributions (patterns embedding the scanned site's own eTLD+1) with a deterministic backstop
- Relinking a common tracker pattern to a different third party now updates the confidence on linked org patterns
- Rename console label "Detected Count" to "Distinct Trackers Detected"
proboctl common-tracker-pattern reenrichnow accepts catalog-wide filters with no selection anchor (e.g.--without-descriptionre-enriches every pattern lacking a description)
Fixed
- Null out stale
initiator_url/initiator_domainrows ondetected_trackersthat point at the @probo/cookie-banner bundle, so genuine third-party initiators repopulate on next detection - Cookie-database denylist now matches domain and URL forms (e.g.
cookiedatabase.org,https://www.cookiepedia.co.uk/list), not just bare brand names
Removed
createThirdPartyThirdPartyMappinganddeleteThirdPartyThirdPartyMappingmutations and MCP tools; create a child third party by passingparentThirdPartyIdoncreateThirdParty
[0.206.0] - 2026-06-09
Added
RiskAssessmentBoundaryfirst-class entity to group nodes within a risk assessment scope, with self-nesting parent boundary, scope-membership validation, nested-subgraph Mermaid rendering, and dedicated IAM actionsregenerateCookieBannerTrackerPolicymutation/MCP tool to re-trigger tracker policy generation on a banner that already has a published version, gated by a dedicatedregenerate-policyaction- Better Stack access-review connector (Uptime API team members + pending invitations)
- SigNoz access-review connector (organization members, region/tenant or self-hosted base URL)
commonTrackerPatternIdfield onTrackerPatternto indicate whether a pattern is linked to the global common-tracker catalog- Files API: public endpoint
GET /api/files/v1/public/{fileID}(unauthenticated, public files only) and private endpointGET /api/files/v1/{fileID}(session/API key/OAuth2,core:file:getenforced); IAM and not-found errors both return 404 - Static brand assets served via
/api/files/v1/staticinstead of S3
Changed
- Connector provider infos promoted from
Organization.connectorProviderInfosto a root-levelaccessReviewDriversquery, listable by any authenticated identity - Tracker-mapping, common-pattern enrichment, and third-party disambiguation agents each get their own config (own timeout, own max-turns, own optional provider slot, with fallback to the tracker-mapping slot when unset)
- Console: tracker pages now surface common-tracker/third-party links with a "common" badge and updated pattern properties display
Fixed
- Cookie tracker pattern analysis: removed unused sync re-enrich path and tightened reset/remap scoping
Removed
ActionFileDownloadUrl(replaced byActionFileGet) and the standalonepkg/filesignpackage (folded intofile.Service)
[0.205.0] - 2026-06-08
Added
submitAgentRunApprovalmutation to merge human approval decisions into an interrupted agent run and resume it- Suspendable agent-tool subtrees: nested agent runs can now checkpoint and restore across multi-level tool calls
Changed
- Agent-run worker no longer relies on leases and heartbeats: a graceful suspend returns the run to
PENDING, an approval interruption parks it inAWAITING_APPROVAL, and crashed runs are leftRUNNINGfor manual recovery - AWS credentials now resolve through the full standard AWS SDK credential chain
Fixed
- Auditors can now read the organization context and see the Context page in the console
- NDA upload now correctly sets the organization ID
- Logo updates no longer wipe unspecified fields on partial update
- Cookie tracker pattern analysis now splits on
:and.so UUID-bearing keys collapse to a single template
[0.204.0] - 2026-06-05
Added
- Dedicated error page when a magic link has already been used
Changed
- Improved error page layout and messaging
[0.203.0] - 2026-06-05
Added
- Zendesk access-review connector with subdomain URL normalization
- Okta access-review connector with API-key (SSWS) authentication
- Clerk access-review connector
- SendGrid access-review connector with 2FA enforcement checks
- Datadog access-review connector with region selector and OAuth support
- PostHog access-review connector with Cloud OAuth, self-hosted OAuth, and API-key support
- Public-client (CIMD) OAuth support with auto-registration and client metadata document
SMTP_HELLO_NAMEenvironment variable to configure the EHLO/HELO hostname- Dedicated expired magic link error page
- Audit reports are now stored as files
Changed
- Clarify trust center access rejection emails
- Cookie banner now supports Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Turkish, Ukrainian, and Chinese
Fixed
- Fix login redirect for password-only authentication flows
[0.202.2] - 2026-06-03
No user-facing changes; tag-only release.
[0.202.1] - 2026-06-03
No user-facing changes; tag-only release.
[0.202.0] - 2026-06-03
Added
- Trigger tracker-policy document generation on banner publish; a background worker regenerates it on every snapshot
- Show tracker type in the cookie tracking policy document
- Include the website origin in the tracker policy title
Changed
- Restrict queries and mutations to session scope
- Move the Display tab first on the cookie banner configuration page
- Link to the generated cookie policy document from tracker rows; revamp tracker row layout
- Number tracker policy section titles
Fixed
- Use stable API URLs for vendor logo fields
[0.201.0] - 2026-06-02
Added
- Add async third-party vetting worker with PENDING/PROCESSING/COMPLETED/FAILED states, exposed through GraphQL and MCP; the third-party detail page polls while vetting runs
- Tune the third-party vetting worker (interval, concurrency, stale-after, agent timeout, max-turns) via config
Changed
- Downgrade access-source instance name resolution failures from error to warning
Fixed
- Guard the GitHub access-source name resolver against empty organization to stop the source-name worker from flooding logs with 404s
[0.200.1] - 2026-06-01
Fixed
- Raise tracker mapping and common-pattern enrichment agent max turns to 10 to prevent
MaxTurnsExceededErrorwhen the tool-call budget exceeded the limit
[0.200.0] - 2026-06-01
Added
- Add tracker description enrichment worker
- Promote tracker patterns to organization third parties via worker, with first-party origin filtering and sibling-based mapping
- Surface third-party links on
TrackerPatternin GraphQL, with batch loaders - Filter banner trackers by linked third party and show third parties on the banner trackers page
- Expose HTTP cookie source through the console API
- Add document archive row action
- Add stale recovery to the tracker mapping worker
- Tune tracker workers: expose worker interval, concurrency, stale-after, agent timeout, and max-turns as config
Changed
- Deactivate SCIM users when delete is blocked
- Rework tracker and resource row actions
- Reuse the mapping agent to attribute trackers in the enricher
- Raise default agent token budget for reasoning models (1024/512 → 4096)
- Harden catalog vendor resolution and the tracker mapping agent prompt
- Skip shared infrastructure in domain matching during tracker mapping
- Backfill tracker description from the common catalog
- Run tracker mapping outside the persist transaction to remove cross-network row locks
Fixed
- Stop tracker agents from inventing vendors
- Drop sampling params unsupported by the model
- Tolerate source fetch failures during tracker mapping
- Skip mapping when a tracker pattern is deleted concurrently
- Guard
LinkToCommonagainst overwriting an existing catalog link - Take resolver scope from
Authorizerather than the GID - Copy default LLM pointers when resolving agents
[0.199.1] - 2026-05-28
Fixed
- Fix missing icons in the UI
- Fix Metabase user listing in access reviews
- Fix PostHog resolver name
[0.199.0] - 2026-05-28
Added
- Add PostHog access-review connector
- Add Metabase access-review connector
- Add Grafana access-review connector
- Add Cursor access-review connector
- Support HTTP Basic auth in API-key connections
- Cancel pending signature requests when a contract ends or a connector is deactivated
Changed
- Reject demotion of the last owner of an organization
- Scope document signatures to the major version
Fixed
- Fix Microsoft 365 access review returning too many accounts
[0.198.0] - 2026-05-28
Added
- Add Tailscale connector
- Add Anthropic connector (authenticated via API key)
- Add personal account support for the Heroku connector
- Add Global region option to the vendor country picker
- Allow ordering organization members by email address
Changed
- Connector deletion is now best-effort: remaining steps proceed even when one cleanup step fails
Fixed
- Fix role column in the people list rendered as non-sortable to prevent runtime failures
- Surface an actionable error when a stored Sentry organization slug is no longer accessible to the connected OAuth token
- Stop the source-name worker from retrying indefinitely on a stale Sentry organization slug
- Stop the source-name worker from retrying indefinitely on a stale Heroku personal-account slug
[0.197.0] - 2026-05-28
Added
- Add
invitingOrganizationsfield on the viewer to expose organizations that have sent a pending invitation to the current user
Fixed
- Show SCIM error message in the connector UI
[0.196.1] - 2026-05-27
Fixed
- Fix serialization of SCIM bridge
SYNCINGandDISABLEDstates in the GraphQL API
[0.196.0] - 2026-05-27
Added
- Expose bridge sync errors in the SCIM API and on Google Workspace and Microsoft 365 connector cards
- Expose profile source field on users in the MCP API
[0.195.0] - 2026-05-27
Added
- Add
archiveUseroperation to deactivate a user profile while keeping them in the organization; exposed across the console UI, MCP, CLI, and n8n - Expire pending invitations for a user when they are archived
- Grant owners full
iam:scim-bridge:*and admins read-only SCIM bridge access in IAM policies
Fixed
- Preserve archived and deactivated HubSpot users in access reviews instead of dropping them
- Fix common third-party logo URL returning resource-not-found in the combo box query
[0.194.0] - 2026-05-26
Added
- Add
probo-agentCLI and device agent library for endpoint compliance checks - Add screen lock detection support for i3, KDE, and more Linux desktop environments
Fixed
- Skip unconnectable providers in provider listing
- Reject shell-unsafe paths in FreeBSD rc.d service installer
- Make Windows service uninstall idempotent
- Use platform-specific atomic key replacement on Windows
- Handle FreeBSD check command failures before reading status
[0.193.1] - 2026-05-26
Security
- Fix open redirect bypass in safe redirect
[0.193.0] - 2026-05-26
Added
- Add measure ↔ third-party many-to-many link with tabs on both detail pages
- Add self-referential third-party relations with a
first_levelfilter on the third-party list - Track source on detected storage trackers (localStorage, sessionStorage, indexedDB, cacheStorage)
- Promote tracker pattern source on detection and trigger a draft banner version when adopting uncategorised patterns
Changed
- Allow initial minor publishing of documents
- Mark page-world extension writes (MV3 main world, userscripts with
@grant none) with the newEXTENSIONcookie source - Surface the measure state as a header badge and remove the measure detail right-hand drawer
Fixed
- Fix timing attack on signin
- Reject separator-only glob templates (e.g.
__*) in tracker pattern analysis
[0.192.0] - 2026-05-25
Changed
- Enforce IAM authorization on all console resolvers — every data-bearing field now goes through the policy engine and produces an audit log entry; adds
ActionCommonThirdPartyGet,ActionCommonThirdPartyList, andActionElectronicSignatureGetactions wired into Viewer and Auditor policies
Fixed
- Fix signature count mismatch between the document version badge and the signatures tab — both now filter by
activeContract: trueandstate: ACTIVE, so deactivated signers and ended-contract signers are consistently excluded - Fix MCP server resolvers after the signature filter and authorization changes
[0.191.0] - 2026-05-22
Added
- Add a tracker pattern detail page in the console with a properties section and a list of detected tracker resources
Fixed
- Strip empty ProseMirror text nodes from third-party list documents (and migrate existing
document_versions.contentto drop them) so Tiptap renders them instead of erroring with "Empty text nodes are not allowed" - Tailor signature certificate email copy for document approvals — store the per-signature email subject on creation so the certificate worker uses "Your approved