Skip to content

feat(regulations): add the regulations.* router with permissions, procedure and deleghe - #114

Draft
lorenzocorallo wants to merge 2 commits into
feat/regulations-collabfrom
feat/regulations-router
Draft

lorenzocorallo wants to merge 2 commits into
feat/regulations-collabfrom
feat/regulations-router

Conversation

@lorenzocorallo

Copy link
Copy Markdown
Member

Stacked on #113. The regulations.* router (src/regulations/router.ts, mounted at regulations) and its service.
It implements every procedure of the contract in #110. The one rename is merge.apply → merge.execute, because
apply is a reserved tRPC name; the admin thread knows.

Access

  • Policy: { user: { scope: backend:admin } } on every procedure. People who only have an assignment use the
    dashboard too, so the service checks the rest.
  • Role per draft: regulations:write → editor. Otherwise the assignment. regulations:read → at least viewer.
    Nothing else → the draft is NOT_FOUND.
  • Permissions:
    • regulations:read: documents and versions;
    • regulations:write: create, assign, merge, send to approval, delete;
    • regulations:manage: approve, (next PRs) emanate and regenerate the diff, documents.sync;
    • regulations:rectify or an active delega covering the document: start and emanate a rettifica.
  • drafts.get returns myRole and can (edit, comment, assign, merge, transitions with blockedBy, approve,
    emanate, rectify, regenerateDiff, delete, rename, snapshot, restore), plus deadlines, procedure, delibera,
    diffStatus and emanationStatus.
  • me.get (no permission): assigned drafts, active deleghe, permissions, and rectifiable documents with their
    latest version.
  • Refusals are TRPCErrors with Italian messages the dashboard shows as-is.

Procedure (procedure.ts)

Configuration per document type. RRI:

  • bozza → in_approvazione (write): requires the text to parse. The collab service inserts ids and freezes all
    connections, and the text is stored in frozen_files. One per document (checked, and backed by the partial
    unique index). Hook for the LLM diff (next PR).
  • in_approvazione → bozza (write): reopen.
  • in_approvazione → approvata (manage): the delibera { body, date (not in the future), verbaleUrl? }.
  • approvata → emanata and bozza → emanata (rettifica): emanation only (next PRs).
  • Deadlines: emanation within 3 days of the delibera (warning, overdue), and the 10-day opposition window from
    notification (info). Both are calendar days, ending 23:59:59 Europe/Rome.
  • A snapshot on every state change.

Rest

  • Drafts from a version or another draft. The text at fork time is the merge base. The creator gets an
    editor assignment. Soft delete.
  • Merge: base = the source's text at the last merge into this draft, else the fork point, else the shared
    published version. preview → conflicts and applied changes. execute needs every conflict resolved and a
    result that parses, then replaces the live text through the collab service, with snapshots before and after.
  • Snapshots: named and restore. A restore keeps the current text as a snapshot first.
  • Comments: threaded, resolvable. Deleted ones keep their place.
  • Collab tickets (readOnly unless editor + editable). compile.status/pdf.
  • Deleghe: create and revoke (rectify only), with an optional document and expiry. Hook for the Direttivo
    email (next PRs).
  • documents.sync (manage) imports legal.json and, for a document without versions, its sources and asset
    bundle as the first published version. The source is REGULATIONS_LEGAL_DIR for now, the GitHub App later.
  • versions.pdf serves the official PDF, or compiles the version through the worker once and keeps it.
  • Fix: Yjs compaction kept moving compacted_through back to 0 when the log was empty.

Verified

  • tests/regulations-service.fixture.ts (real Postgres, faked collab over the stored Yjs state). It checks:
    1. import from a legal checkout, once;
    2. who can create and assign;
    3. assignees see only their drafts, with role-based capabilities and read-only tickets for commenters;
    4. threaded comments;
    5. a clean merge, a conflict that must be resolved, and the last-merge base;
    6. a named snapshot and a non-destructive restore;
    7. freeze and the one-in-approval rule;
    8. approval with a delibera, the deadline at 22:59:59Z, overdue after;
    9. delega → rettifica → revoke;
    10. soft delete.
  • tests/regulations-router.test.ts: the policy on every procedure, refusing tokenless, service and wrong-scope
    callers, and the exact list of procedure names.
  • Full suite: 342 passed locally against Postgres 18.

@coderabbitai

coderabbitai Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

…cedure and deleghe

Every procedure of the contract shared with the admin dashboard. Access combines
the IdP permissions (regulations:read|write|manage|rectify), per-draft
assignments (viewer, commenter, editor) and the Segretario's deleghe; drafts a
caller can't see don't exist for them, and drafts.get returns the caller's role
and capabilities so the dashboard doesn't repeat the rules.

The procedure is configuration per document type (states, transitions, who
takes them, deadlines). Sending to approval freezes the live text through the
collab service (one draft in approval per document), the approval records the
delibera, and the 3-day emanation and 10-day opposition deadlines are computed
in Italian time. Merges use the last merge or the shared version as base,
restores keep the current text as a snapshot, comments are threaded and
resolvable, and documents and their first version are imported from legal.json.

Also keeps the Yjs compaction marker from moving back when the log is empty.
@lorenzocorallo
lorenzocorallo force-pushed the feat/regulations-collab branch from 1995770 to b3cefe5 Compare November 2, 2026 23:02
@lorenzocorallo
lorenzocorallo force-pushed the feat/regulations-router branch from 4ece422 to e549a9c Compare November 2, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant