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).
54 lines
2.3 KiB
PL/PgSQL
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;
|