| 1 |
Feature
|
A signer with no account is now routed from the signer portal to a token-gated signup (SignerPortalNewView._route_anonymous_signer in src/signatures/views.py), instead of a login page they had no account for. A signer who does have an account is still sent to login with next set back to the portal. The signup page states that an account is required to sign, names the document, and pins the email field to the address the request was sent to |
Medium
|
Yes — changes the route by which a signer establishes the account their signature is bound to |
| 2 |
Change
|
InvitedSignerSignupView (src/signatures/signer_auth.py) marks the signer's address verified via allauth's stash_verified_email, so setup_user_email creates the EmailAddress with verified=True and no confirmation mail is sent. Applied only after the submitted address is confirmed equal to the token's signer_email; a mismatch is rejected as a form error and no account is created. ACCOUNT_EMAIL_VERIFICATION = "mandatory" is unchanged and still governs every other signup path |
Medium
|
Yes — substitutes token possession for a confirmation round-trip as proof of address ownership |
| 3 |
Change
|
Signup no longer creates a Tenant. GxPSignAccountAdapter.save_user (src/tenants/adapters.py) loses its personal-org branch and its signer-detection branch, keeping only the pending-invitation branch added in 82f333c/eec5cbf. New accounts are tenantless until they accept an invitation (inline at the invite link, or by signing up with the invited address — save_user still honours that), are provisioned by SSO (sso_hooks), or create an organization at /app/onboarding/ (NoOrganizationView, unchanged and already present). User.tenant was already nullable — no migration |
Low
|
No — no change to signature capture, audit, or records |
| 4 |
Change
|
DashboardView routes a tenantless user by intent (src/tenants/views.py): to the signer inbox when they have signing assignments, otherwise to /app/onboarding/ to create or join an organization. The no-organization page no longer claims an organization is required to use GxPSign, and links to the signer inbox |
Low
|
No — navigation only |
| 5 |
Fix
|
RequestReviewView (src/signatures/views.py) no longer marks a signature request sent — nor writes a request_sent audit entry — when zero signer emails could be queued; it now reports the failure and leaves the request a draft to retry. Previously the request was recorded as sent, with an audit entry saying so, having reached nobody |
Medium
|
Yes — removes a path that produced a false `request_sent` audit record |
| 6 |
Cleanup
|
Removed InvitedSignerLoginRedirectView and its URL (unreachable; superseded by next). Removed the signature_token / expected_email session writes from the signer portal, whose only reader was the deleted org-creation branch in save_user |
None
|
No |
| 7 |
Removal
|
Retired the superseded v1 send/portal path, which duplicated the wizard path in parallel: deleted SignerPortalView, SignatureRequestSendView, signatures.utils.send_signature_request_emails (v1) and build_signer_url, the signer_portal and request_send URLs, and the signer_portal.html / request_send.html templates. The Send button on the request-detail page now links to the review step, which owns sending and re-checks signers and fields first. Verified unused in production before removal — see below |
Low
|
No — the retained path is the one production already uses for every send |
| 8 |
Cleanup
|
Renamed send_signature_request_emails_v2 to send_signature_request_emails now that the name is free and the suffix contrasts with nothing. The request_sent audit description retains its existing "(v2 wizard)" wording deliberately: historical rows carry it, so changing new rows would split one action across two wordings without removing the string from the trail |
None
|
No — audit content unchanged |
| 9 |
Feature
|
Signature reasons are now data, not code. SignatureReasonTemplate (platform-wide, superuser-only in Django Admin) holds the set copied into each organization at creation; SignatureReason (tenant-isolated, RLS policy in signatures/0016) is the organization's own editable copy. SignatureField.SIGNATURE_MEANING_CHOICES and the choices= on both signature_meaning fields are removed — the valid set is per-organization data, which a model-level choices list cannot express without a migration per edit. SignatureReason.choices_for(tenant) returns the same (code, label) shape the templates already iterate, so the signer-setup and field-placement dropdowns are unchanged |
Medium
|
Yes — the reason is the meaning of the signature (§11.50(a)(2)); its source of truth moves from code to per-tenant data |
| 10 |
Feature
|
Organization Admins/Owners manage their reason list at /app/settings/signature-reasons/ (SignatureReason*View in src/tenants/views.py): add, relabel, re-order with ↑/↓, and hide. Gated on can_administer_org() and scoped to the caller's own organization by RLS plus an explicit tenant= filter. Reasons are never hard-deleted — hiding sets is_active=False, so a draft that already references a retired reason still resolves |
Medium
|
Yes — controls which meanings can be bound to a signature |
| 11 |
Compliance
|
A sent request's reason wording is now immutable. SignatureRequest.mark_sent() freezes each signer's and field's resolved label into a new signature_meaning_text column, and signature_meaning_display returns that frozen value in preference to any live lookup. Without this, an Organization Admin relabelling a reason in Settings would retroactively change what already-sent and already-signed requests display. The signed PDF, the completion certificate, and SignatureAuditLog.metadata were already frozen at signing time; this closes the gap for the live request-detail, signer-portal, and signer-list views. Both existing edit paths already refused to modify a non-draft request; mark_sent() is the single write path for the freeze |
Medium
|
Yes — preserves the meaning bound to an executed signature (§11.50(a)(2), §11.10(e)) |
| 12 |
Feature
|
Each new organization is seeded with its own copy of the reason set via a post_save receiver on Tenant (src/signatures/signals.py), rather than a call at each creation site — so "an organization always has a reason list" holds for the onboarding view, the demo-data command, and the test factories alike. Data migrations are unaffected: they build Tenant through apps.get_model, which is a different class from the one the receiver is registered against. signatures/0016 seeds the global set from the previously hardcoded list and back-fills every existing organization with an identical copy, so no organization sees a changed dropdown at deploy |
Low
|
No — provisioning only; the seeded set is byte-identical to the previous hardcoded list |
| 13 |
Fix
|
The signed PDF's metadata text no longer prints over the signature image. The block always renders five lines (signer, email, meaning wrapped to two, date) — 45.5 pt at the 7 pt font floor — but only 48 % of the block height was reserved for it, so the bottom-aligned text overflowed upward into the image zone. The reserved share is now TEXT_ZONE_FRAC = 0.60, sized so the text fits at that floor. Pre-existing defect, found by rendering a signed PDF rather than by the change itself |
Low
|
No — appearance only; the signature bytes, reason, and audit trail are unaffected |
| 14 |
Enhancement
|
The signed signature block is less bulky: internal margins cut from 5 pt to 2 pt, and SIG_MIN_WIDTH_PTS from 165 to 135 (~18 % narrower). Its border is now a 0.5 pt light-grey hairline instead of pyHanko's 1 pt solid black, drawn by a TextStamp subclass because TextStampStyle exposes only an integer border_width and no stroke colour. SIG_MIN_HEIGHT_PTS stays at 84 and INITIAL_MIN_WIDTH_PTS at 73: both are floors set by the metadata that must remain legible, not by taste — verified by rendering, and documented at the constants |
Low
|
No — appearance only; signatures re-validated as intact with `ENTIRE_FILE` coverage after the change |
| 15 |
Compliance
|
The PDF signing path now has automated coverage; it previously had none, because there were no certificate fixtures and so nothing in the suite ever produced a signed PDF. tests/conftest.py gains a session-scoped self-signed signing_keypair, an active platform_certificate, and signature-image / PDF fixtures; tests/test_pdf_signing.py signs real documents through PDFSigningService and asserts the signature is intact, valid and covers the entire file, that the frozen reason reaches the PDF's crypto /Reason, that signing without a certificate raises rather than emitting an unsigned document, that under-sized boxes are expanded to the legible minimum, and that the stamp's border operators are the grey hairline and are balanced. Each assertion was confirmed to fail under a deliberate mutation |
Low
|
Indirect — adds verification of an existing 21 CFR Part 11 control (signature binding and meaning) rather than changing it |
| 16 |
Removal
|
Removed the unused User.training_status field and every reference (admin fieldset, UserEditForm, the user-list filter, the Training column and badges on the user list/detail/edit pages, docs/test_plan.md rows S-4.4.1/S-4.4.2). tenants/0017 drops the column — existing training-status data is deleted and not recoverable. The field fed no logic: nothing gated signing, permissions, or audit on it |
Low
|
No — the field was recorded but never enforced or referenced by any signature, audit, or access-control path |