LoadRequestedByDocumentID joined document_versions while applying
the unqualified tenant_id scope fragment, which PostgreSQL rejects.
Use the same IN-subquery shape as DeleteRequestedByDocumentID.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Archive and bulk archive now void pending approval quorums and
cancel requested signatures before freezing the document. Unarchive
reverts any leftover pending approval to draft. A migration backfills
archived documents that still carry in-flight workflow rows.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The source headers, LICENSE files, and license metadata had drifted
apart. Align the entire project to MIT:
- Convert every source-file header to the MIT text across all comment
styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including
SPDX-License-Identifier tags
- Set the root and cookie-banner LICENSE files to the MIT text with a
"MIT License" title line
- Switch the package.json license fields, Docker image label, and
cookie-banner README to MIT
- Update docs and the genmodels header generator accordingly
- Normalize copyright lines to a single format
(Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the
hello@getprobo.com and hello@probo.inc emails to hello@probo.com and
the comma-separated years to a hyphenated range
Genuine third-party references are intentionally left untouched: the
Lucide icon attributions (Lucide is ISC) and the trivy dependency
license allowlist.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
When a new minor version is published, still-REQUESTED signature requests
on the previous version now move onto the newly published version, keeping
the same signature row so the notification schedule (count and last-notified
time) is preserved. SIGNED signatures are left untouched.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Replace the immediate per-document approval email and the manual
"send signing notifications" action with a single debounced worker that
batches pending requests per recipient and organization.
The worker (go.gearno.de/kit/worker) polls on an interval (default 5m)
and claims one (organization, recipient) group at a time, sending one
consolidated signing email and/or one approval email per recipient/org
that lists every document awaiting their signature or approval. The
claim is a conditional UPDATE that doubles as concurrency-safe dedup, so
several workers never email the same group twice.
Each request is notified once it has been pending past the debounce
delay (default 15m), then reminded at 1x, 2x and 3x the reminder
interval (default 1 day) after the previous email, after which it stops.
New last_notified_at and notification_count columns on signatures and
approval decisions drive the debounce, the widening reminder cadence and
the four-email cap.
Email copy lists each document with its title, type and a deep link to
the employee page. Removed the inline approval-on-publish email, the
SendSigningNotifications service method/mutation/MCP tool, its IAM action,
and the related console UI and n8n operation.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Employee document signatures recorded an acknowledgment with no
cryptographic proof, unlike document approvals which already create
and accept an electronic signature on every decision.
Mirror the approval flow on the sign path: generate the signed
document PDF, create-and-accept an esign record, and persist its id
on the document_version_signatures row through a new
electronic_signature_id column. Capture the signer IP and user agent
in the resolver, and re-check the published/archived preconditions
inside the transaction so the seal cannot race document state.
Make the consent wording a single backend source of truth shared by
the text that is sealed and the text shown in the UI. Define
DocumentSignatureConsentText and DocumentApprovalConsentText in the
probo service package and the NDA copy in the trust service, each
owned by the flow that uses it, and stop esign from appending the
generic clause to caller-provided consent text so approvals no
longer seal a duplicated sentence.
Expose the resolved consent text through GraphQL on
EmployeeDocumentVersion and DocumentVersionApprovalDecision, and have
the signing, approval, and NDA pages render it from the API instead
of hard-coded strings, mirroring how the NDA page already worked.
Align the wording with the actual interaction: the buttons read
"Review and sign" and "Review and approve", the clauses reference
those actions, and the inaccurate "typing my full name" phrasing is
removed everywhere.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
A signature applies to a whole major: minor publishes keep it and the
export unions signatures across every minor of the major. The request
guard was scoped to a single minor, so re-requesting on a newer minor
(or twice on the same version) inserted duplicate rows and a signatory
appeared several times on the exported signature page.
Deduplicate by loading any existing signature across the major before
inserting, cancel still-pending requests from prior majors when a new
major is published, and restrict the export to active signatories
(comparing contract end dates against the current date). A migration
collapses the duplicate rows already in the table, preferring a signed
row over a pending one and then the most recent.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Disabling a profile now removes pending document signature requests tied to that signatory. This keeps inactive accounts from retaining signature work across manual, archived, and SCIM deactivation flows.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Change AuthorizationAttributer.AuthorizationAttributes to take a slice
of resource ids and return policy.AttributesByID, so a single SQL
round-trip can load condition attributes for a whole batch. All
coredata implementations are migrated to a single
`WHERE id = ANY(@resource_ids::text[])` query that returns only the
rows it finds.
Authorizer gains:
- AuthorizeBatch — all-or-nothing across a homogeneous (same entity
type, same organization) resource set; rejects mixed entity types,
mixed organizations, and empty batches with structured errors.
- AuthorizeMulti — heterogeneous evaluation that returns one error
per item and writes audit log entries in a single bulk insert.
The single-resource Authorize is rewired to delegate to AuthorizeBatch
so all paths share the same condition evaluation and audit logging.
recordAuditLog is split into buildAuditLogEntry plus a batch insert.
Tests cover the new batch and multi paths, mixed/empty/unsupported
resource cases, audit log batching, and dry-run behaviour.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Apply five style rules: convert iota string enums to typed
string constants, replace errors.As with errors.AsType,
merge three-group imports into two groups, fix multiline
parameter/argument formatting, and replace fmt.Sprintf URL
construction with net/url.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce a two-part version scheme (major.minor) for documents.
Drafts start at 0.1 and increment minor on each new draft. Publishing
as minor keeps the current version, publishing as major bumps to the
next major.0. Both current_published_major and current_published_minor
are tracked on the document for exact version lookups.
Signatures and approval quorums aggregate across all versions sharing
the same major number using CTE joins. Approval page mutations spread
the decision fragment so Relay updates the version row state without
requiring a page refresh.
GraphQL, MCP, and service layer expose separate publishMajor and
publishMinor mutations instead of a single mutation with a type enum.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>