Pass/fail was the main device UI signal, but operators need
the agent's observed value. Expose a formatted value per
check, show current postures on the device page, and replace
the Postures tab with paginated report history grouped by
agent push time. Status stays in the model for later rulesets.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Introduce make lint-shell / fmt-shell with shellcheck and
shfmt (-i 2 -ci -bn), normalize first-party scripts, and
document the new targets. Keep them out of make lint / fmt.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Expose AUDIT_START_DATE and AUDIT_END_DATE on AuditOrderField
so list queries can paginate like valid_from and valid_until.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Match valid_from and valid_until on audits and the console
date-only inputs for audit start and end.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Store audit_start_date and audit_end_date as TIMESTAMP WITH
TIME ZONE. Alphabetize n8n audit field collections to satisfy
n8n-node lint.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
ISO audits often span a window distinct from certificate validity.
Store optional audit_start_date and audit_end_date on the audit
record and expose them through GraphQL, MCP, CLI, n8n, and console.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Configure Mermaid to suppress its built-in error renderer in both
interactive UI previews and generated document HTML. Render failures now
surface through the product toast flow with a safe inline fallback instead
of leaving raw Mermaid diagnostics in the document body.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Émile Ré <nemile.re@gmail.com>
Move MagicLinkHandler out of oidc_handler.go. Completing a
magic link for an existing identity now marks the address
verified, matching OIDC behavior.
Signed-off-by: Émile Ré <emile@probo.com>
E2E owners confirm email during setup so password re-login works,
so ID token claim assertions must expect email_verified true.
Signed-off-by: Émile Ré <emile@probo.com>
Resend only runs after an explicit form submit, so a per-address
cooldown is unnecessary overhead compared with forgot-password.
Signed-off-by: Émile Ré <emile@probo.com>
Add a per-address confirmation-email cooldown and disable the
resend/forgot-password submit buttons while the mutation is in
flight so callers cannot flood the mail queue or double-submit.
Signed-off-by: Émile Ré <emile@probo.com>
Unverified password identities were able to open sessions after
signing out. Reject sign-in with EMAIL_NOT_VERIFIED and add a
resend-confirmation flow so users can complete verification.
Signed-off-by: Émile Ré <emile@probo.com>
Add blank lines around the dedup-and-connector-ID block to satisfy
the wsl_v5 linter (blank line after an if-block, above an assignment
following a block, and above an if with no shared variables in the
preceding line).
Signed-off-by: Bryan Frimin <bryan@probo.com>
MergeByCampaignID joined access_review_sources directly from coredata
to resolve live sources, so unrecognized or out-of-scope IDs were
silently dropped instead of erroring, and in the worst case (every ID
invalid) the NOT MATCHED BY SOURCE clause deleted every existing
campaign source. The syncCampaignSources ErrResourceNotFound check
was therefore unreachable dead code.
Add AccessReviewSources.LoadByIDs, matching the existing scoped
LoadByIDs pattern (id = ANY(@ids) plus a resolved-count check), and
have CreateCampaign, AddCampaignSource, and syncCampaignSources
resolve and validate sources up front. MergeByCampaignID now takes
the already-loaded sources and builds its desired-state CTE from an
unnest() of their values instead of joining access_review_sources,
keeping the merge inside the campaign-source entity boundary.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The loader fetched only the first 500 sources with no pageInfo/cursor
follow-up, so organizations with more than 500 scoped sources could
not select sources past the first page when creating or updating a
campaign. Loop over pages using pageInfo.endCursor until exhausted.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Intl.DisplayNames rejects the GLOBAL pseudo-region, so label
resolution now handles it explicitly before rendering the picker.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Report access loads are already tenant-scoped; reject the
extra organization guard. Also remove the PortalAccessRequest
doc comment called out in review.
Signed-off-by: Émile Ré <emile@probo.com>
The single-resource access resolvers each forward exactly one hard-coded
id, so RequestPortalAccess never returns ErrNoAccessTargets on those
paths — it only does so when all three id slices are empty. Remove the
three dead handlers, keeping the live one on the bulk path where the
resolved id slices can legitimately all be empty.
Signed-off-by: Émile Ré <emile@probo.com>
The bar read the raw selection size for its count, so after a request
succeeded and Relay flipped the rows to pending the left count stayed
stale while the action count dropped to zero. Resolve the selection
against the current rows instead, so requested rows leave the count at
once and the bar closes without depending on a clear that may not run.
Signed-off-by: Émile Ré <emile@probo.com>
Drop the request-all shortcut so callers always name the
documents, reports, and files to request. TopBar Get Access
now only signs in; bulk selection is the multi-resource path.
Signed-off-by: Émile Ré <emile@probo.com>
The bulk request skips any id that already has an access record, so
rejected or revoked rows reported success while creating nothing. Treat a
row as requestable only when the viewer is unauthorized and no access
record exists yet, keeping the selection CTA count honest.
Signed-off-by: Émile Ré <emile@probo.com>
The toast viewport stays mounted even with no toasts, as a fixed box in
the bottom-right corner. With default pointer-events it swallowed hover
and clicks over content beneath it, such as the lower edge of a bottom
action bar's right-aligned buttons.
Mark the viewport pointer-events-none and re-enable events on each toast
root so only actual toast cards are interactive.
Signed-off-by: Émile Ré <emile@probo.com>
The bottom selection action bar carried its layout as inline class
strings on the container divs. Move them into a slotted tv in the
documents variants module, matching the documentsLayout pattern, so the
component consumes named slots instead of ad hoc className literals.
Signed-off-by: Émile Ré <emile@probo.com>
Public / already-authorized and already-requested rows could be ticked
even though they never contribute to "Request Access (N)", making the
selection count look inconsistent with the CTA. Restrict selection to
locked rows: their checkbox is disabled otherwise, and "Select all" now
picks only the lockable rows.
Signed-off-by: Émile Ré <emile@probo.com>
Visitors could only request access to one document, report, or file at
a time. Add row checkboxes and a bottom selection toolbar to the
compliance portal documents page so a visitor can select several rows
and request access to all still-locked ones in a single round-trip.
Expose a selection-scoped requestAccesses mutation that forwards the
chosen id lists to the existing RequestPortalAccess service (one
transaction, one NDA/auth gate). The resolver loads and tenant-checks
every target before requesting so a foreign id is rejected before any
access row is written, and echoes the affected nodes so the client
flips each row to pending in place.
Add a styled Base UI Checkbox to the v2 kit, a local selection context
shared by the independent row fragments, and mirror the new selection
strings across all locales.
Signed-off-by: Émile Ré <emile@probo.com>
Declare AccessReviewSource before LoadByID so the package compiles
after RemoveCampaignSource cleanup.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Remove local uniqueGIDs helper in favor of the existing gid set
utility used elsewhere in coredata.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Scoped rows in the MERGE desired_sources CTE already limit
snapshots to sources visible under the scoper.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Remove early return on empty source IDs; count and MERGE always
run so clearing sources uses NOT MATCHED BY SOURCE delete.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Delete scoped snapshots in one statement when the desired source
set is empty; validate and merge only when IDs are present.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Add AccessReviewCampaignSources.MergeByCampaignID and use it from
syncCampaignSources instead of per-row load, upsert, and delete.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Apply draft and lifecycle checks on loaded coredata records
directly in the campaign service.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Introduce accessreview.Campaign with draft/deletable predicates and
operation-specific client errors. Drop errUnlessDraftCampaign and
status-to-sentinel switches in the service layer.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Business-layer errors carry the access review campaign GID in
Error() and match GraphQL sentinels via Is().
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>