Files
paliad/internal/db/migrations/055_hierarchy_aggregation.down.sql
m a61c1490e3 feat(t-paliad-139): Phase 3 — derived_peer authority extension to t-138 approval gate
Wires DerivationService.EffectiveProjectRole into the t-paliad-138
approval ladder so partner-unit-derived members with derive_grants_authority=true
can act as approvers (per design §4.2). When they sign off, the audit row
records decision_kind='derived_peer' — a third value alongside the existing
'peer' and 'admin_override' — so the chronology discloses the derivation
chain.

Schema (migration 055 update)
-----------------------------
  - paliad.approval_requests.decision_kind CHECK extended to accept
    'derived_peer'. Down migration restores the t-138 two-value CHECK.
    Live SQL dry-run confirmed the new value is accepted.

Service layer
-------------
  - approval_levels.go: new constant DecisionKindDerivedPeer.
  - approval_service.go (4 sites widened with the derivation EXISTS branch):
      1. canApprove — third resolution step after global_admin + direct/
         ancestor team membership: matches partner-unit-derived members
         on path with derive_grants_authority=true and a unit_role whose
         approval_role_from_unit_role mapping meets the threshold.
         Returns DecisionKindDerivedPeer when this branch is the one that
         passed.
      2. hasQualifiedApprover (the deadlock-check at submit time) —
         widened so a project with no direct approvers but an authority-
         granting unit attachment is still submittable.
      3. ListPendingForApprover (the /inbox query) — third UNION ALL
         branch so derived authority sees their queue.
      4. PendingCountForUser (the bell-badge query) — same widening so
         derived authority sees the count tick.
  All four queries reuse paliad.approval_role_from_unit_role(text) added
  by Phase 2 of migration 055.

Frontend
--------
  - 2 i18n keys (DE+EN): approvals.decision_kind.derived_peer →
    "Genehmigt durch abgeleitetes Mitglied (Partner Unit)" / "Approved by
    derived member (Partner Unit)". Verlauf rendering of the third
    decision_kind value works through the existing translateEvent /
    decision_kind switch with no other change. 1606 keys total.

Strict-default unchanged
------------------------
Derived members are visibility-only by default. Authority requires the
project lead/admin to explicitly flip derive_grants_authority=true on the
project_partner_units row (UI on /projects/{id} Team tab, Phase 2). This
preserves the m-locked Q12 stance.

Phase 3 closes the t-paliad-139 implementation. m's bug closes (Phase 1),
the derivation schema is in place (Phase 2), and approval authority
flows through the new ladder (Phase 3).
2026-05-06 16:45:19 +02:00

54 lines
2.3 KiB
PL/PgSQL

-- Down migration for t-paliad-139 (055_hierarchy_aggregation).
--
-- Reverses the schema additions in lockstep with the up migration:
-- 1. Restore can_see_project to the migration-023 body (drop derivation
-- branch).
-- 2. Drop paliad.approval_role_from_unit_role helper.
-- 3. Drop paliad.project_partner_units (cascades the policies + index).
-- 4. Drop paliad.partner_unit_members.unit_role.
--
-- If any project has project_partner_units rows with derive_grants_authority=true
-- AND any approval_request was ever signed using a derived_peer decision_kind
-- (t-paliad-139 Phase 3), the down does NOT roll those back — the audit rows
-- stay valid; only the schema is reverted. Down is intentionally lossy on
-- in-flight derivation state.
-- Restore the migration-054 decision_kind CHECK (without 'derived_peer').
-- Any existing rows with decision_kind='derived_peer' would fail the
-- restored CHECK; the down deliberately doesn't update them — operators
-- must reconcile before applying the down migration.
ALTER TABLE paliad.approval_requests DROP CONSTRAINT IF EXISTS approval_requests_decision_kind_check;
ALTER TABLE paliad.approval_requests ADD CONSTRAINT approval_requests_decision_kind_check
CHECK (decision_kind IS NULL OR decision_kind IN ('peer', 'admin_override'));
-- 1. Restore migration-023 can_see_project body (no derivation branch).
CREATE OR REPLACE FUNCTION paliad.can_see_project(_project_id uuid)
RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = paliad, public
AS $$
SELECT EXISTS (
SELECT 1 FROM paliad.users u
WHERE u.id = auth.uid() AND u.global_role = 'global_admin'
)
OR EXISTS (
SELECT 1
FROM paliad.projects target
JOIN paliad.project_teams pt
ON pt.user_id = auth.uid()
AND pt.project_id = ANY(string_to_array(target.path, '.')::uuid[])
WHERE target.id = _project_id
);
$$;
-- 2. Drop the unit_role → project_role mapping helper.
DROP FUNCTION IF EXISTS paliad.approval_role_from_unit_role(text);
-- 3. Drop the project↔unit junction (CASCADE clears policies + index).
DROP TABLE IF EXISTS paliad.project_partner_units;
-- 4. Drop the unit_role column.
ALTER TABLE paliad.partner_unit_members DROP COLUMN IF EXISTS unit_role;