Approval-policy authoring UI — make the t-138 4-eye system reachable #13

Open
opened 2026-05-07 20:59:42 +00:00 by mAi · 1 comment
Collaborator

The 4-eye approval pipeline (t-paliad-138) is shipped on the backend but dormant in production: paliad.approval_policies has 0 rows DB-wide because there is no UI to author policies. As a result, every Frist/Termin lifecycle event (create/edit/done/delete) bypasses the approval gate and the /approvals page is permanently empty. m hit this 2026-05-07 22:55: created a deadline expecting an approval request, got nothing.

Backend that already exists

  • internal/services/approval_service.go — full lifecycle wiring (Request, Approve, Reject, Revoke, hard-delete on approved-delete).
  • Admin-only HTTP API (handlers.go:421-426):
    • GET /api/projects/{id}/approval-policies — list
    • PUT /api/projects/{id}/approval-policies/{entity_type}/{lifecycle} — set
    • DELETE /api/projects/{id}/approval-policies/{entity_type}/{lifecycle} — clear
  • paliad.approval_policies table: (id, project_id, entity_type, lifecycle_event, required_role, created_at, updated_at, created_by).
  • Inheritance/hierarchy semantics: defined in cronus's design (docs/design-approvals-2026-05-06.md).

What's missing

A frontend surface to author/edit/clear policies. Today an admin would have to curl the API. That's why no policy exists.

Open design questions for the inventor pass

  1. Surface placement — dedicated /admin/approval-policies admin page, or a Settings tab on each /projects/{id} page (so each project's lead can author its own policies), or both?
  2. Grid shape — entity_type × lifecycle_event matrix is small (deadline×4 + appointment×4 = 8 cells per project). Inline editor with required_role dropdowns? Bulk "apply to descendants"?
  3. Default-policy concept — does m want org-wide defaults that individual projects can override? Or strictly per-project author?
  4. Inheritance display — when a child project has no policy but an ancestor does, surface the inherited rule (read-only) so admins see what's currently in force. Editing the child should override; clearing the override should fall back.
  5. Required-role taxonomy — what role labels are valid here? Mirror project_teams.responsibility from t-148? Use a separate approver-role enum?
  6. Migration / backfill — should we seed a default policy for existing projects (e.g. "all deadline.create needs lead approval") on first deploy of the UI, or leave the DB empty and rely on admins to opt in? m's call.
  7. Audit / event surface — when a policy changes, should that emit a paliad.partner_unit_events or project_events row so the change is auditable in /verlauf?
  8. Empty-state UX — for projects without any policy, the /approvals page renders empty forever. Should we show a one-tap "author policy" affordance from /approvals when the user is admin?
  9. Per-policy enable/disable toggle vs delete/recreate — soft-disable lets m audit "this rule used to apply".
  10. i18n — DE/EN labels for the role enum + lifecycle event names.

DOGMA (paliad memory paliad/dogma): surface open questions to m via AskUserQuestion or PWA form per-question, sequentially — NOT as a markdown §X.Y dump. m's call on each is what locks the design.

Locked constraints (m, 2026-05-07)

  • Inventor pass first; design doc + AskUserQuestion sequence; coder shift gated on m's go.
  • Out of scope: re-architecting cronus's t-138 design itself (the backend stays). This is purely the missing UI.

Deliverable: docs/design-approval-policy-ui-2026-05-07.md. Inventor STOPs after design.

NOT cronus per memory directive (paliad). NOT noether (parked on t-151). NOT godel (just fired). Pick a fresh inventor name.

The 4-eye approval pipeline (t-paliad-138) is shipped on the backend but **dormant** in production: `paliad.approval_policies` has 0 rows DB-wide because there is no UI to author policies. As a result, every Frist/Termin lifecycle event (create/edit/done/delete) bypasses the approval gate and the /approvals page is permanently empty. m hit this 2026-05-07 22:55: created a deadline expecting an approval request, got nothing. ## Backend that already exists - `internal/services/approval_service.go` — full lifecycle wiring (Request, Approve, Reject, Revoke, hard-delete on approved-delete). - Admin-only HTTP API (handlers.go:421-426): - `GET /api/projects/{id}/approval-policies` — list - `PUT /api/projects/{id}/approval-policies/{entity_type}/{lifecycle}` — set - `DELETE /api/projects/{id}/approval-policies/{entity_type}/{lifecycle}` — clear - `paliad.approval_policies` table: `(id, project_id, entity_type, lifecycle_event, required_role, created_at, updated_at, created_by)`. - Inheritance/hierarchy semantics: defined in cronus's design (`docs/design-approvals-2026-05-06.md`). ## What's missing A frontend surface to author/edit/clear policies. Today an admin would have to `curl` the API. That's why no policy exists. ## Open design questions for the inventor pass 1. **Surface placement** — dedicated `/admin/approval-policies` admin page, or a Settings tab on each `/projects/{id}` page (so each project's lead can author its own policies), or both? 2. **Grid shape** — entity_type × lifecycle_event matrix is small (deadline×4 + appointment×4 = 8 cells per project). Inline editor with required_role dropdowns? Bulk "apply to descendants"? 3. **Default-policy concept** — does m want org-wide defaults that individual projects can override? Or strictly per-project author? 4. **Inheritance display** — when a child project has no policy but an ancestor does, surface the inherited rule (read-only) so admins see what's currently in force. Editing the child should override; clearing the override should fall back. 5. **Required-role taxonomy** — what role labels are valid here? Mirror project_teams.responsibility from t-148? Use a separate approver-role enum? 6. **Migration / backfill** — should we seed a default policy for existing projects (e.g. "all deadline.create needs lead approval") on first deploy of the UI, or leave the DB empty and rely on admins to opt in? m's call. 7. **Audit / event surface** — when a policy changes, should that emit a `paliad.partner_unit_events` or `project_events` row so the change is auditable in /verlauf? 8. **Empty-state UX** — for projects without any policy, the /approvals page renders empty forever. Should we show a one-tap "author policy" affordance from /approvals when the user is admin? 9. **Per-policy enable/disable toggle** vs delete/recreate — soft-disable lets m audit "this rule used to apply". 10. **i18n** — DE/EN labels for the role enum + lifecycle event names. DOGMA (paliad memory `paliad/dogma`): surface open questions to m via `AskUserQuestion` or PWA form **per-question, sequentially** — NOT as a markdown §X.Y dump. m's call on each is what locks the design. ## Locked constraints (m, 2026-05-07) - Inventor pass first; design doc + AskUserQuestion sequence; coder shift gated on m's go. - Out of scope: re-architecting cronus's t-138 design itself (the backend stays). This is purely the missing UI. Deliverable: `docs/design-approval-policy-ui-2026-05-07.md`. Inventor STOPs after design. NOT cronus per memory directive (paliad). NOT noether (parked on t-151). NOT godel (just fired). Pick a fresh inventor name.
Author
Collaborator

t-paliad-154 implementation merged into main as f820aa8, bundling 8 commits from mai/hilbert/inventor-approval-policy:

  • bb03555 — design doc (docs/design-approval-policy-ui-2026-05-07.md, 912 lines, 13 m-locked decisions)
  • f7908f0 — migration 062: paliad.approval_policies unit-defaults schema + 'none' sentinel + tree-walking resolver SQL function + 88-row unit-default seed
  • e92c56b — migration 062 extension: paliad.policy_audit_log table
  • e6067c7ApprovalService rewire: resolver delegation + scope-split CRUD + audit emission
  • 0f87d73 — HTTP handlers: admin APIs + form-hint endpoint + audit-log union
  • 028423b/admin/approval-policies admin page + admin-index card + CSS + i18n
  • 5df4285/inbox empty-state nudge for admins + form-time hints on deadline/appointment new pages

Head-side review checks all green:

  • CSS tokens: only --color-* and --status-* (paliad's standard token system, no dark-mode contrast leaks).
  • Migration 062: no RAISE EXCEPTION, idempotent on re-run, validated via BEGIN..ROLLBACK against live DB (88 unit seeds + 1 preserved project policy from earlier SQL hotfix).
  • Auto-merge clean: handlers.go, i18n.ts, i18n-keys.ts all 3-way merged with no conflicts.
  • 22 files / +3535/-65 across backend services + admin UI + form-time hints + tests.
  • 13 m-locked design decisions honoured verbatim (per hilbert's debrief).

Production behavior on next deploy: migration 062 seeds 88 unit-default rows (one per (partner_unit, entity_type, lifecycle_event) combination per design §1). The 4-eye gate flips from dormant to firm-wide-on. The hotfix policy I inserted earlier on Siemens AG (1530281c-60da-4415-a79d-c74e29c00cef — deadline.create requires partner) is preserved.

Closing the loop on m's original frustration ("I add a deadline, /approvals shows nothing"): on the next paliad redeploy, every Frist creation will fire an approval request gated by the seeded unit defaults.

t-paliad-154 implementation merged into main as [`f820aa8`](https://mgit.msbls.de/m/paliad/commit/f820aa8), bundling 8 commits from `mai/hilbert/inventor-approval-policy`: - [`bb03555`](https://mgit.msbls.de/m/paliad/commit/bb03555) — design doc (`docs/design-approval-policy-ui-2026-05-07.md`, 912 lines, 13 m-locked decisions) - [`f7908f0`](https://mgit.msbls.de/m/paliad/commit/f7908f0) — migration 062: `paliad.approval_policies` unit-defaults schema + `'none'` sentinel + tree-walking resolver SQL function + 88-row unit-default seed - [`e92c56b`](https://mgit.msbls.de/m/paliad/commit/e92c56b) — migration 062 extension: `paliad.policy_audit_log` table - [`e6067c7`](https://mgit.msbls.de/m/paliad/commit/e6067c7) — `ApprovalService` rewire: resolver delegation + scope-split CRUD + audit emission - [`0f87d73`](https://mgit.msbls.de/m/paliad/commit/0f87d73) — HTTP handlers: admin APIs + form-hint endpoint + audit-log union - [`028423b`](https://mgit.msbls.de/m/paliad/commit/028423b) — `/admin/approval-policies` admin page + admin-index card + CSS + i18n - [`5df4285`](https://mgit.msbls.de/m/paliad/commit/5df4285) — `/inbox` empty-state nudge for admins + form-time hints on deadline/appointment new pages Head-side review checks all green: - CSS tokens: only `--color-*` and `--status-*` (paliad's standard token system, no dark-mode contrast leaks). - Migration 062: no `RAISE EXCEPTION`, idempotent on re-run, validated via `BEGIN..ROLLBACK` against live DB (88 unit seeds + 1 preserved project policy from earlier SQL hotfix). - Auto-merge clean: handlers.go, i18n.ts, i18n-keys.ts all 3-way merged with no conflicts. - 22 files / +3535/-65 across backend services + admin UI + form-time hints + tests. - 13 m-locked design decisions honoured verbatim (per hilbert's debrief). **Production behavior on next deploy:** migration 062 seeds 88 unit-default rows (one per `(partner_unit, entity_type, lifecycle_event)` combination per design §1). The 4-eye gate flips from dormant to firm-wide-on. The hotfix policy I inserted earlier on Siemens AG (`1530281c-60da-4415-a79d-c74e29c00cef` — deadline.create requires partner) is preserved. Closing the loop on m's original frustration ("I add a deadline, /approvals shows nothing"): on the next paliad redeploy, every Frist creation will fire an approval request gated by the seeded unit defaults.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#13
No description provided.