delegation-approve-hosted.mjs
114 lines 3.6 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Gateway hook: after canister proposal approve, apply bridge delegation indexes (7C-L1b).
3 */
4
5 import { proposalIdFromApprovePath } from '../../lib/muse-thin-bridge.mjs';
6 import {
7 applyApprovedDelegationProposalFromCanister,
8 fetchCanisterProposalForDelegation,
9 isDelegationProposalIntent,
10 } from '../../lib/agent/delegation-hosted-proposal.mjs';
11
12 const PROPOSAL_APPROVE_RE = /^\/api\/v1\/proposals\/[^/]+\/approve\/?$/;
13
14 /**
15 * @param {{
16 * method: string,
17 * pathOnly: string,
18 * upstreamStatus: number,
19 * canisterUrl: string,
20 * bridgeUrl: string,
21 * authorization: string|undefined,
22 * vaultId: string,
23 * effectiveUserId: string,
24 * actorUserId: string,
25 * canisterAuthHeaders: () => Record<string, string>,
26 * }} ctx
27 * @returns {Promise<{ applied: boolean, error?: string, code?: string, payload?: Record<string, unknown> }|null>}
28 */
29 export async function maybeApplyHostedDelegationAfterApprove(ctx) {
30 if (ctx.method !== 'POST' || !PROPOSAL_APPROVE_RE.test(ctx.pathOnly)) return null;
31 if (ctx.upstreamStatus < 200 || ctx.upstreamStatus >= 300) return null;
32
33 const proposalId = proposalIdFromApprovePath(ctx.pathOnly);
34 if (!proposalId || !ctx.bridgeUrl || !ctx.canisterUrl) return null;
35
36 const headers = {
37 ...ctx.canisterAuthHeaders(),
38 'X-User-Id': ctx.effectiveUserId,
39 'X-Actor-Id': ctx.actorUserId,
40 'X-Vault-Id': ctx.vaultId,
41 };
42
43 const fetched = await fetchCanisterProposalForDelegation({
44 canisterUrl: ctx.canisterUrl,
45 headers,
46 proposalId,
47 });
48 if (!fetched.ok) return null;
49
50 const intent = typeof fetched.proposal.intent === 'string' ? fetched.proposal.intent : '';
51 if (!isDelegationProposalIntent(intent)) return null;
52
53 const bridgeRes = await fetch(
54 `${ctx.bridgeUrl.replace(/\/$/, '')}/api/v1/delegation/proposals/${encodeURIComponent(proposalId)}/apply-approved`,
55 {
56 method: 'POST',
57 headers: {
58 Accept: 'application/json',
59 'Content-Type': 'application/json',
60 ...(ctx.authorization ? { Authorization: ctx.authorization } : {}),
61 'X-Vault-Id': ctx.vaultId,
62 },
63 body: JSON.stringify({}),
64 },
65 );
66
67 const text = await bridgeRes.text();
68 /** @type {Record<string, unknown>} */
69 let json = {};
70 try {
71 json = text ? JSON.parse(text) : {};
72 } catch {
73 json = {};
74 }
75
76 if (!bridgeRes.ok) {
77 return {
78 applied: false,
79 error: typeof json.error === 'string' ? json.error : text.slice(0, 200),
80 code: typeof json.code === 'string' ? json.code : 'DELEGATION_APPLY_FAILED',
81 };
82 }
83
84 return {
85 applied: true,
86 payload: json && typeof json === 'object' ? /** @type {Record<string, unknown>} */ (json) : {},
87 };
88 }
89
90 /**
91 * Merge delegation apply outcome into canister approve JSON for the Hub client.
92 *
93 * @param {string} responseText
94 * @param {{ applied: boolean, error?: string, code?: string, payload?: Record<string, unknown> }|null} applyOutcome
95 * @returns {string}
96 */
97 export function mergeDelegationApplyIntoApproveResponse(responseText, applyOutcome) {
98 if (!applyOutcome) return responseText;
99 try {
100 const body = JSON.parse(responseText);
101 if (!body || typeof body !== 'object') return responseText;
102 body.delegation_index_applied = applyOutcome.applied;
103 if (applyOutcome.applied && applyOutcome.payload) {
104 body.delegation_apply = applyOutcome.payload;
105 }
106 if (!applyOutcome.applied) {
107 body.delegation_apply_error = applyOutcome.error ?? 'Delegation index apply failed';
108 body.delegation_apply_code = applyOutcome.code ?? 'DELEGATION_APPLY_FAILED';
109 }
110 return JSON.stringify(body);
111 } catch {
112 return responseText;
113 }
114 }
File History 2 commits
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago
sha256:d8c648b20a4d53b2673c5c082ee7edfa7b2fc9b11080832da1f38807b6bf940b fix(7C-L1b): route hosted delegation proposals through cani… Human minor 30 days ago