Dual-control approvals for Deadlines + Official Court Events (4-eye principle) #3

Open
opened 2026-05-06 12:34:42 +00:00 by mAi · 1 comment
Collaborator

Problem

Today any team member with project visibility can create, edit, complete or delete a Frist or Termin. For HLC's regulated practice that's too loose — a single mistake or unauthorised edit on a Frist can cascade into a missed deadline with malpractice exposure. m wants a 4-eye principle enforced on both entity types: every state-changing action submitted by one person must be signed off by a qualified second person before the change becomes "complete".

Goals

  1. Two-person rule on Deadlines + Official Court Events (Termine) — every state-changing operation (create, edit-of-key-fields, mark-as-done, delete) requires a qualified second person to sign off before the change becomes complete.
  2. Project-scoped approval policies — each project can define its own rule: e.g. "all deadlines need partner approval", "all termin date-changes need senior-PA approval". Easy to author per project.
  3. Role-based with hierarchy — a person with a role above the required role may also sign off. No self-approval ever.
  4. In-app notification inbox — pending approvals surface as actionable items: "please approve new deadline", "please confirm completion", "please sign off date change".
  5. Audit trail with both names — creator/editor and approver are both persisted on the row and surface in the deadline / termin chronology and overviews.

Locked design constraints (m, 2026-05-06)

  • Scope: Deadlines (Fristen) and Official Court Events (Termine). One unified design covering both entity types.
  • Triggers: All four lifecycle events — creation, edit-of-key-fields, mark-as-done, delete.
  • Self-approval: Hard-blocked. Approver must be a different person from the action-taker.
  • Hierarchy: A role above the required role can also sign off (the inventor must define what "above" means concretely).
  • Approver model: Project-scoped. Each project carries its own approval policy. Roles must be easy to edit per project.
  • App scope: paliad-only. Other projects (otto, fdbck, mAi) get separate consideration later if needed.

Out of scope (v1)

  • Email / WhatsApp / Telegram / CalDAV notifications. In-app inbox only.
  • Approval workflows for Projects, Parties, Notes, Documents, Checklists. Only Deadlines + Termine.
  • Multi-step / n-of-m approval chains. Single-approver model only.
  • Cross-project / cross-app generalisation.

Open design questions (for inventor — m will answer in design pass)

  1. Role taxonomy — what are the firm-wide roles in HLC patent practice? Likely candidates: partner / senior-attorney / attorney / senior-PA / PA / paralegal. Where do they live — paliad.user_roles table, an enum on team membership, an array on user profile? Are roles global (firm-wide), per-team, or per-project?
  2. Hierarchy semantics — strict ladder ("partner > senior-attorney > attorney > senior-PA > PA"), or partial order with multiple peers? Concretely: if a project rule says "needs senior-PA approval", does a partner satisfy it? Does an attorney? Inventor: propose, m confirms.
  3. Approval rule shape — per-project granularity, or per-(project, entity-type, lifecycle-event) tuple so the same project can say "deadline:create needs PA, deadline:done needs attorney, termin:edit needs senior-PA"? Inventor: design the rule grammar.
  4. What counts as an "edit" trigger — only date / due-date changes, or any field? Cosmetic edits (typo fix in the title) may not warrant 4-eye. Hard-block on safety-critical fields, soft-skip on cosmetic? Define the field allowlist.
  5. Pending-state UX — between "submitted" and "approved" the change exists. Does the row appear in regular project views with a "pending approval" badge, hide from non-approvers, or live in a dedicated "pending" tab? Does it count toward the project's deadline traffic-light?
  6. Inbox surface — top-bar bell with count + dedicated /inbox page, both, or one? Existing deadline reminder mail flow is unrelated — keep distinct or share infrastructure?
  7. Approval revocation — can an approver revoke their sign-off (within a window)? Once "complete" can the row still be reverted (requiring a fresh approval cycle)?
  8. Single-qualified-approver edge case — if the action-taker is the only person on the team holding the qualifying role, the workflow deadlocks. Behaviour: refuse to submit, allow sign-off with a "single-eye" audit flag, or escalate to a higher role?
  9. Audit / chronology — paliad already has paliad.deadline_events for chronology. Should approvals piggyback on that, or get a dedicated paliad.approvals table? Both names must surface in views.
  10. RLS — pending changes need to be visible to the approver but possibly hidden from regular team members. Extend can_see_project() with a can_approve_in_project() companion, or push the gating into the application layer?
  11. Migration of existing rows — paliad has hundreds of existing Fristen + Termine without a created_by / approved_by. Backfill created_by from audit log if available, leave approved_by NULL with a "legacy — pre-approval" marker?

References

  • paliad.deadline_events — existing audit trail (chronology source)
  • paliad.can_see_project() — team-based RLS function
  • internal/services/deadline_service.go, internal/services/appointment_service.go — service layer
  • internal/services/event_deadline_service.go — unified Deadlines + Termine read path (good integration point)
  • paliad.projects — project hierarchy + team membership (RLS foundation)
  • t-paliad-122 — last entity-model expansion (courts/countries) — reference pattern for migration shape

Inventor brief

  • Worker: cronus (inventor role) — same shape as t-paliad-122 / t-paliad-131 / t-paliad-136
  • Branch convention: mai/cronus/inventor-approvals (head will scope; cronus may rename — head should verify with git -C worktree branch --show-current)
  • Deliverable: design doc at docs/design-approvals-2026-05-06.md archiving the locked constraints, open-question answers from m, role taxonomy, rule grammar, schema sketch, migration plan, RLS plan, UI sketch (inbox + pending-state badges), implementation step order.
  • Inventor STOPs after design — no implementation, no /mai-coder shift unless m gives explicit go on the design.
  • m is available during design to answer the open questions above.
## Problem Today any team member with project visibility can create, edit, complete or delete a Frist or Termin. For HLC's regulated practice that's too loose — a single mistake or unauthorised edit on a Frist can cascade into a missed deadline with malpractice exposure. m wants a 4-eye principle enforced on both entity types: every state-changing action submitted by one person must be signed off by a qualified second person before the change becomes "complete". ## Goals 1. **Two-person rule on Deadlines + Official Court Events (Termine)** — every state-changing operation (create, edit-of-key-fields, mark-as-done, delete) requires a qualified second person to sign off before the change becomes complete. 2. **Project-scoped approval policies** — each project can define its own rule: e.g. "all deadlines need partner approval", "all termin date-changes need senior-PA approval". Easy to author per project. 3. **Role-based with hierarchy** — a person with a role above the required role may also sign off. **No self-approval ever.** 4. **In-app notification inbox** — pending approvals surface as actionable items: "please approve new deadline", "please confirm completion", "please sign off date change". 5. **Audit trail with both names** — creator/editor and approver are both persisted on the row and surface in the deadline / termin chronology and overviews. ## Locked design constraints (m, 2026-05-06) - **Scope**: Deadlines (Fristen) **and** Official Court Events (Termine). One unified design covering both entity types. - **Triggers**: All four lifecycle events — creation, edit-of-key-fields, mark-as-done, delete. - **Self-approval**: Hard-blocked. Approver must be a different person from the action-taker. - **Hierarchy**: A role *above* the required role can also sign off (the inventor must define what "above" means concretely). - **Approver model**: Project-scoped. Each project carries its own approval policy. Roles must be easy to edit per project. - **App scope**: paliad-only. Other projects (otto, fdbck, mAi) get separate consideration later if needed. ## Out of scope (v1) - Email / WhatsApp / Telegram / CalDAV notifications. **In-app inbox only.** - Approval workflows for Projects, Parties, Notes, Documents, Checklists. **Only Deadlines + Termine.** - Multi-step / n-of-m approval chains. **Single-approver model only.** - Cross-project / cross-app generalisation. ## Open design questions (for inventor — m will answer in design pass) 1. **Role taxonomy** — what are the firm-wide roles in HLC patent practice? Likely candidates: `partner` / `senior-attorney` / `attorney` / `senior-PA` / `PA` / `paralegal`. Where do they live — `paliad.user_roles` table, an enum on team membership, an array on user profile? Are roles global (firm-wide), per-team, or per-project? 2. **Hierarchy semantics** — strict ladder ("partner > senior-attorney > attorney > senior-PA > PA"), or partial order with multiple peers? Concretely: if a project rule says "needs senior-PA approval", does a partner satisfy it? Does an attorney? Inventor: propose, m confirms. 3. **Approval rule shape** — per-project granularity, or per-(project, entity-type, lifecycle-event) tuple so the same project can say "deadline:create needs PA, deadline:done needs attorney, termin:edit needs senior-PA"? Inventor: design the rule grammar. 4. **What counts as an "edit" trigger** — only date / due-date changes, or any field? Cosmetic edits (typo fix in the title) may not warrant 4-eye. Hard-block on safety-critical fields, soft-skip on cosmetic? Define the field allowlist. 5. **Pending-state UX** — between "submitted" and "approved" the change exists. Does the row appear in regular project views with a "pending approval" badge, hide from non-approvers, or live in a dedicated "pending" tab? Does it count toward the project's deadline traffic-light? 6. **Inbox surface** — top-bar bell with count + dedicated `/inbox` page, both, or one? Existing deadline reminder mail flow is unrelated — keep distinct or share infrastructure? 7. **Approval revocation** — can an approver revoke their sign-off (within a window)? Once "complete" can the row still be reverted (requiring a fresh approval cycle)? 8. **Single-qualified-approver edge case** — if the action-taker is the only person on the team holding the qualifying role, the workflow deadlocks. Behaviour: refuse to submit, allow sign-off with a "single-eye" audit flag, or escalate to a higher role? 9. **Audit / chronology** — paliad already has `paliad.deadline_events` for chronology. Should approvals piggyback on that, or get a dedicated `paliad.approvals` table? Both names must surface in views. 10. **RLS** — pending changes need to be visible to the approver but possibly hidden from regular team members. Extend `can_see_project()` with a `can_approve_in_project()` companion, or push the gating into the application layer? 11. **Migration of existing rows** — paliad has hundreds of existing Fristen + Termine without a `created_by` / `approved_by`. Backfill `created_by` from audit log if available, leave `approved_by` NULL with a "legacy — pre-approval" marker? ## References - `paliad.deadline_events` — existing audit trail (chronology source) - `paliad.can_see_project()` — team-based RLS function - `internal/services/deadline_service.go`, `internal/services/appointment_service.go` — service layer - `internal/services/event_deadline_service.go` — unified Deadlines + Termine read path (good integration point) - `paliad.projects` — project hierarchy + team membership (RLS foundation) - t-paliad-122 — last entity-model expansion (courts/countries) — reference pattern for migration shape ## Inventor brief - Worker: cronus (inventor role) — same shape as t-paliad-122 / t-paliad-131 / t-paliad-136 - Branch convention: `mai/cronus/inventor-approvals` (head will scope; cronus may rename — head should verify with `git -C worktree branch --show-current`) - Deliverable: design doc at `docs/design-approvals-2026-05-06.md` archiving the locked constraints, open-question answers from m, role taxonomy, rule grammar, schema sketch, migration plan, RLS plan, UI sketch (inbox + pending-state badges), implementation step order. - **Inventor STOPs after design** — no implementation, no `/mai-coder` shift unless m gives explicit go on the design. - m is available during design to answer the open questions above.
mAi self-assigned this 2026-05-06 12:35:19 +00:00
Author
Collaborator

Inventor design committed @ 7d1ddb9docs/design-approvals-2026-05-06.md (828 lines).

m's locked answers (2026-05-06) and design summary:

# Question m's call
Q1 Qualification axis paliad.project_teams.role per-project (NO new firm-wide column). Adds senior_pa value to enum.
Q1+ Default ladder lead > of_counsel > associate > senior_pa > pa. Default required_role=associate. PA-approves-PA possible per-project.
Q2 Hierarchy Strict ladder.
Q3 Rule grammar Per-(project, entity_type, lifecycle_event), up to 8 rows per project.
Q4 Edit allowlist Date-bearing fields only. Frist: due_date/original_due_date/warning_date. Termin: start_at/end_at. Cosmetic + rule_code + location bypass.
Q5 Architecture Write-then-approve. Delete is the one stage-then-write exception.
Q6 Inbox Bell icon + dedicated /inbox page with two tabs.
Q7 Revocation Pending-only; after approval only path back is a new request.
Q8 Deadlock Refuse + global_admin override (audit-marked decision_kind='admin_override').
Q9 Audit Both — operational approval_requests + 4 new event_types in paliad.project_events. Verlauf renders pair-cards.
Q10 RLS Same can_see_project predicate. Service-layer gates the approve/reject action. CHECK blocks self-approval.
Q11 Legacy Mark-legacy + skip backfill. New mutations on legacy rows trigger normal flow.

Plus m's interjection: pending state visualised everywhere — list views, agenda, dashboard traffic-light, project detail, CalDAV (`[PENDING] ` title prefix), email reminders. Silence on pending creates more risk than visible-but-flagged.

Schema: migration 054 adds (1) senior_pa to project_teams.role CHECK, (2) paliad.approval_role_level() SQL function, (3) paliad.approval_policies (8 rows max per project), (4) paliad.approval_requests (with pre_image jsonb for revert-on-reject), (5) approval_status / pending_request_id / approved_by / approved_at columns on deadlines + appointments, (6) appointments.completed_at (new — needed for appointment:complete lifecycle).

8-commit implementation phasing: schema → ApprovalService core → wire into Deadline+Appointment services → policy authoring page → inbox → pending pills → CalDAV+email integration → Verlauf rendering. Each commit testable in isolation.

Inventor recommends cronus same worktree for the impl shift. Alternative: split into cronus (commits 1-3 schema+service core) then curie/fritz (commits 4-8 UI). Head decides.

Inventor parked. Awaiting m go/no-go before any coder shift.

**Inventor design committed @ 7d1ddb9** — `docs/design-approvals-2026-05-06.md` (828 lines). m's locked answers (2026-05-06) and design summary: | # | Question | m's call | |---|---|---| | Q1 | Qualification axis | `paliad.project_teams.role` per-project (NO new firm-wide column). Adds `senior_pa` value to enum. | | Q1+ | Default ladder | lead > of_counsel > associate > senior_pa > pa. Default required_role=associate. PA-approves-PA possible per-project. | | Q2 | Hierarchy | Strict ladder. | | Q3 | Rule grammar | Per-(project, entity_type, lifecycle_event), up to 8 rows per project. | | Q4 | Edit allowlist | Date-bearing fields only. Frist: due_date/original_due_date/warning_date. Termin: start_at/end_at. Cosmetic + rule_code + location bypass. | | Q5 | Architecture | Write-then-approve. Delete is the one stage-then-write exception. | | Q6 | Inbox | Bell icon + dedicated /inbox page with two tabs. | | Q7 | Revocation | Pending-only; after approval only path back is a new request. | | Q8 | Deadlock | Refuse + global_admin override (audit-marked decision_kind='admin_override'). | | Q9 | Audit | Both — operational approval_requests + 4 new event_types in paliad.project_events. Verlauf renders pair-cards. | | Q10 | RLS | Same can_see_project predicate. Service-layer gates the approve/reject action. CHECK blocks self-approval. | | Q11 | Legacy | Mark-legacy + skip backfill. New mutations on legacy rows trigger normal flow. | Plus m's interjection: **pending state visualised everywhere** — list views, agenda, dashboard traffic-light, project detail, CalDAV (\`[PENDING] \` title prefix), email reminders. Silence on pending creates more risk than visible-but-flagged. Schema: migration 054 adds (1) `senior_pa` to project_teams.role CHECK, (2) `paliad.approval_role_level()` SQL function, (3) `paliad.approval_policies` (8 rows max per project), (4) `paliad.approval_requests` (with pre_image jsonb for revert-on-reject), (5) `approval_status` / `pending_request_id` / `approved_by` / `approved_at` columns on deadlines + appointments, (6) `appointments.completed_at` (new — needed for appointment:complete lifecycle). 8-commit implementation phasing: schema → ApprovalService core → wire into Deadline+Appointment services → policy authoring page → inbox → pending pills → CalDAV+email integration → Verlauf rendering. Each commit testable in isolation. Inventor recommends cronus same worktree for the impl shift. Alternative: split into cronus (commits 1-3 schema+service core) then curie/fritz (commits 4-8 UI). Head decides. **Inventor parked. Awaiting m go/no-go before any coder shift.**
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#3
No description provided.