feat: add governed ownership workflows and admin tree navigation

This commit is contained in:
2026-07-30 17:42:05 +02:00
parent 9b88ae388b
commit f0898fcdee
17 changed files with 2857 additions and 1 deletions
+142
View File
@@ -0,0 +1,142 @@
import { apiFetch } from "./client";
import type { ApiSettings } from "../types";
export type OwnershipSubject = {
type: string;
id: string;
};
export type OwnershipResource = {
module_id: string;
resource_type: string;
resource_id: string;
};
export type OwnershipDecision = {
sequence: number;
action: string;
actor_type?: string | null;
actor_id?: string | null;
decided_at: string;
status: string;
details: Record<string, unknown>;
};
export type OwnershipTransfer = {
id: string;
tenant_id: string;
resource_module: string;
resource_type: string;
resource_id: string;
kind:
| "owner_initiated"
| "target_requested"
| "administrative_recovery";
status: string;
current_owner: OwnershipSubject;
target_owner: OwnershipSubject;
initiated_by: OwnershipSubject;
owner_approved_by?: OwnershipSubject | null;
target_accepted_by?: OwnershipSubject | null;
reason?: string | null;
assurance_profile?: string | null;
required_approvals: number;
approvals: Array<Record<string, unknown>>;
decisions: OwnershipDecision[];
expires_at: string;
execute_after?: string | null;
completed_at?: string | null;
declined_at?: string | null;
cancelled_at?: string | null;
expired_at?: string | null;
revision: number;
metadata: Record<string, unknown>;
created_at: string;
updated_at: string;
};
export type OwnershipTransferStart = {
resource: OwnershipResource;
target_owner: OwnershipSubject;
idempotency_key: string;
reason?: string | null;
expiry_days?: number | null;
};
export type OwnershipRequestStart = {
resource: OwnershipResource;
target_owner?: OwnershipSubject | null;
idempotency_key: string;
reason?: string | null;
expiry_days?: number | null;
};
export async function listOwnershipTransfers(
settings: ApiSettings,
filters: {
resourceType?: string;
resourceId?: string;
status?: string;
limit?: number;
} = {}
): Promise<OwnershipTransfer[]> {
const query = new URLSearchParams();
if (filters.resourceType) {
query.set("resource_type", filters.resourceType);
}
if (filters.resourceId) query.set("resource_id", filters.resourceId);
if (filters.status) query.set("status", filters.status);
if (filters.limit) query.set("limit", String(filters.limit));
const suffix = query.toString();
const result = await apiFetch<{ transfers: OwnershipTransfer[] }>(
settings,
`/api/v1/ownership/transfers${suffix ? `?${suffix}` : ""}`
);
return result.transfers;
}
export function startOwnershipTransfer(
settings: ApiSettings,
payload: OwnershipTransferStart
): Promise<OwnershipTransfer> {
return apiFetch<OwnershipTransfer>(
settings,
"/api/v1/ownership/transfers",
{
method: "POST",
body: JSON.stringify(payload)
}
);
}
export function requestResourceOwnership(
settings: ApiSettings,
payload: OwnershipRequestStart
): Promise<OwnershipTransfer> {
return apiFetch<OwnershipTransfer>(
settings,
"/api/v1/ownership/transfers/requests",
{
method: "POST",
body: JSON.stringify(payload)
}
);
}
export function decideOwnershipTransfer(
settings: ApiSettings,
transferId: string,
action:
| "owner-approval"
| "acceptance"
| "decline"
| "cancel"
| "recovery-approval"
| "recovery-execution"
): Promise<OwnershipTransfer> {
return apiFetch<OwnershipTransfer>(
settings,
`/api/v1/ownership/transfers/${transferId}/${action}`,
{ method: "POST" }
);
}